├── .babelrc
├── .eslintrc
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── package.json
├── src
├── index.js
├── only-if.js
└── only-if.spec.js
└── webpack.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "react"]
3 | }
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env": {
4 | "mocha": true
5 | },
6 | "rules": {
7 | "react/prefer-es6-class": 0,
8 | "react/prefer-stateless-function": 0
9 | },
10 | }
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | umd
4 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "6"
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Michele Bertoli
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/MicheleBertoli/react-only-if)
2 |
3 | # React Only If
4 |
5 | Sometimes we want to check if the right data has been loaded into the props, the state or the context before rendering our [React.js](https://facebook.github.io/react/)
6 | components. In the meanwhile, we usually show a loading indicator.
7 |
8 | React Only If is a [higher order component](https://medium.com/@dan_abramov/mixins-are-dead-long-live-higher-order-components-94a0d2f9e750#.euq910vh3)
9 | that simplifies the process and makes it more declarative.
10 |
11 | Before:
12 |
13 | ```jsx
14 | const UserContainer = props => {
15 | if (!props.user) {
16 | return ;
17 | }
18 | return ;
19 | };
20 | ```
21 |
22 | After:
23 |
24 | ```jsx
25 | const UserContainer = props => ;
26 | const UserContainerOnlyIf = onlyIf(props => props.user, Spinner)(UserContainer);
27 | ```
28 |
29 | ## Installation
30 |
31 | ### Npm
32 |
33 | ```bash
34 | $ npm install react-only-if --save
35 | ```
36 |
37 | ### Umd
38 |
39 | ```html
40 |
41 | ```
42 |
43 | ## API
44 |
45 | Parameter | Type | Description
46 | ----------- | ------- | -----------
47 | condition | func | The condition function. It receives props, context and state.
48 | Placeholder | element | (optional) The component to render when the condition is false.
49 |
50 | ### Note for version 0.x users
51 |
52 | Following a discussion in [#2](https://github.com/MicheleBertoli/react-only-if/pull/2#issuecomment-241388231),
53 | the library has been recently rewritten (thanks [Frederik](https://github.com/m90)).
54 |
55 | The version 1.x introduces some breaking changes in order to enforce consistency for stateless functional components
56 | and to make the library play nicely when using functional composition on multiple higher order components.
57 |
58 | ```javascript
59 | // v0.x
60 | const ComponentOnlyIf = onlyIf(Component, (props, state, context) => {...}, Placeholder);
61 |
62 | // v1.x
63 | const ComponentOnlyIf = onlyIf((props, context, state) => {...}, Placeholder)(Component);
64 | ```
65 |
66 | ## Test
67 |
68 | ```bash∏
69 | $ npm test
70 | ```
71 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-only-if",
3 | "version": "1.0.0",
4 | "description": "Render a React.js component only if",
5 | "main": "dist/index.js",
6 | "files": [
7 | "dist",
8 | "umd"
9 | ],
10 | "scripts": {
11 | "prebuild": "npm test",
12 | "prepublish": "npm run build",
13 | "lint": "eslint ./src",
14 | "test": "mocha --compilers js:babel-register ./src/**/*.spec.js",
15 | "prebuild:dist": "rimraf ./dist",
16 | "prebuild:umd": "rimraf ./umd",
17 | "build:dist": "babel ./src --out-dir ./dist --ignore *.spec.js",
18 | "build:umd": "webpack -p ./src ./umd/only-if.min.js",
19 | "build": "npm run build:dist && npm run build:umd"
20 | },
21 | "repository": {
22 | "type": "git",
23 | "url": "git+https://github.com/MicheleBertoli/react-only-if.git"
24 | },
25 | "keywords": [
26 | "react-component"
27 | ],
28 | "authors": [
29 | "Michele Bertoli",
30 | "Frederik Ring"
31 | ],
32 | "license": "MIT",
33 | "bugs": {
34 | "url": "https://github.com/MicheleBertoli/react-only-if/issues"
35 | },
36 | "homepage": "https://github.com/MicheleBertoli/react-only-if#readme",
37 | "devDependencies": {
38 | "babel-cli": "^6.7.5",
39 | "babel-loader": "^6.2.4",
40 | "babel-plugin-add-module-exports": "^0.1.2",
41 | "babel-preset-es2015": "^6.6.0",
42 | "babel-preset-react": "^6.5.0",
43 | "babel-register": "^6.7.2",
44 | "enzyme": "^2.2.0",
45 | "eslint": "^2.8.0",
46 | "eslint-config-airbnb": "^7.0.0",
47 | "eslint-plugin-jsx-a11y": "^0.6.2",
48 | "eslint-plugin-react": "^4.3.0",
49 | "mocha": "^2.4.5",
50 | "react": "^15.0.1",
51 | "react-addons-test-utils": "^15.0.1",
52 | "react-dom": "^15.0.1",
53 | "rimraf": "^2.5.2",
54 | "sinon": "^1.17.3",
55 | "webpack": "^1.13.0"
56 | },
57 | "peerDependencies": {
58 | "react": "^15.0.1",
59 | "react-dom": "^15.0.1"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import onlyIf from './only-if.js';
2 | export default onlyIf;
3 |
--------------------------------------------------------------------------------
/src/only-if.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const getDisplayName = (Component) => (
4 | Component.displayName || Component.name || 'Component'
5 | );
6 |
7 | const isStateless = (Target) => (
8 | !(Target.prototype && Target.prototype.isReactComponent)
9 | );
10 |
11 | const wrapStateless = (stateless) => {
12 | class Wrapped extends React.Component {
13 | render() {
14 | return stateless(this.props, this.context);
15 | }
16 | }
17 |
18 | Wrapped.contextTypes = stateless.contextTypes;
19 | Wrapped.propTypes = stateless.propTypes;
20 |
21 | return Wrapped;
22 | };
23 |
24 | export default (condition, Placeholder) => (Target) => {
25 | const Super = isStateless(Target) ? wrapStateless(Target) : Target;
26 |
27 | class Enhanced extends Super {
28 | render() {
29 | if (condition(this.props, this.context, this.state)) {
30 | return super.render();
31 | }
32 | return Placeholder ? React.createElement(Placeholder) : null;
33 | }
34 | }
35 |
36 | Enhanced.displayName = `OnlyIf(${getDisplayName(Target)})`;
37 |
38 | return Enhanced;
39 | };
40 |
--------------------------------------------------------------------------------
/src/only-if.spec.js:
--------------------------------------------------------------------------------
1 | /* eslint react/no-multi-comp: 0 */
2 |
3 | import assert from 'assert';
4 | import sinon from 'sinon';
5 | import { shallow } from 'enzyme';
6 | import React, { Component } from 'react';
7 | import onlyIf from './only-if';
8 |
9 | class ExtendsComponentDummy extends Component {
10 | render() {
11 | return
Dummy
;
12 | }
13 | }
14 | ExtendsComponentDummy.contextTypes = {
15 | test: React.PropTypes.bool,
16 | };
17 |
18 | const CreateClassDummy = React.createClass({
19 | contextTypes: {
20 | test: React.PropTypes.bool,
21 | },
22 | render() {
23 | return Dummy
;
24 | },
25 | });
26 |
27 | const StatelessDummy = () => (
28 | Stateless Dummy
29 | );
30 | StatelessDummy.contextTypes = {
31 | test: React.PropTypes.bool,
32 | };
33 |
34 | const Placeholder = () => Placeholder
;
35 |
36 | const runTest = (Dummy, description) => {
37 | describe(description, () => {
38 | it('calls the condition function with the props', () => {
39 | const callback = sinon.spy();
40 | const props = { test: true };
41 | const DummyOnlyIf = onlyIf(callback)(Dummy);
42 | shallow();
43 | assert(callback.calledWith(props));
44 | });
45 |
46 | it('calls the condition function with the context', () => {
47 | const callback = sinon.spy();
48 | const context = { test: true };
49 | const DummyOnlyIf = onlyIf(callback)(Dummy);
50 | shallow(, { context });
51 | assert(callback.calledWith({}, context, null));
52 | });
53 |
54 | it('calls the condition function with the state', () => {
55 | const callback = sinon.spy();
56 | const state = { test: true };
57 | const DummyOnlyIf = onlyIf(callback)(Dummy);
58 | const wrapper = shallow();
59 | wrapper.setState(state);
60 | assert(callback.calledWith({}, { test: undefined }, state));
61 | });
62 |
63 | it('renders the component if the condition is true', () => {
64 | const DummyOnlyIf = onlyIf(() => true)(Dummy);
65 | const wrapper = shallow();
66 | assert(wrapper.containsMatchingElement(Dummy));
67 | });
68 |
69 | it('renders the placeholder if the condition is false', () => {
70 | const DummyOnlyIf = onlyIf(() => false, Placeholder)(Dummy);
71 | const wrapper = shallow();
72 | assert(wrapper.contains());
73 | });
74 |
75 | it('renders null if the placeholder does not exist', () => {
76 | const DummyOnlyIf = onlyIf(() => false)(Dummy);
77 | const wrapper = shallow();
78 | assert.equal(wrapper.type(), null);
79 | });
80 | });
81 | };
82 |
83 | describe('onlyIf', () => {
84 | runTest(ExtendsComponentDummy, '`extends Component` component');
85 | runTest(CreateClassDummy, '`createClass` component');
86 | runTest(StatelessDummy, 'stateless functional component');
87 | });
88 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |
3 | output: {
4 | library: 'onlyIf',
5 | libraryTarget: 'umd',
6 | },
7 |
8 | externals: [{
9 | react: {
10 | root: 'React',
11 | commonjs2: 'react',
12 | commonjs: 'react',
13 | amd: 'react',
14 | },
15 | }],
16 |
17 | module: {
18 | loaders: [{
19 | test: /\.jsx?$/,
20 | exclude: /(node_modules|bower_components)/,
21 | loader: 'babel',
22 | query: {
23 | plugins: ['add-module-exports'],
24 | },
25 | }],
26 | },
27 |
28 | };
29 |
--------------------------------------------------------------------------------