├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── README.md ├── index.d.ts ├── index.js ├── package.json ├── src ├── If.js ├── If.spec.js ├── Omit.js ├── Omit.spec.js ├── Sort.js ├── Sort.spec.js ├── __snapshots__ │ ├── If.spec.js.snap │ ├── Omit.spec.js.snap │ └── Sort.spec.js.snap └── index.js ├── static └── preview.png ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-es2015-modules-umd" 4 | ], 5 | "presets": [ 6 | "env", 7 | "react", 8 | "stage-2" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "extends": "airbnb", 4 | "plugins": [ 5 | "jest" 6 | ], 7 | "env": { 8 | "jest/globals": true 9 | }, 10 | rules: { 11 | "comma-dangle": ["error", { 12 | "arrays": "always-multiline", 13 | "objects": "always-multiline", 14 | "imports": "always-multiline", 15 | "exports": "always-multiline", 16 | "functions": "never" 17 | }], 18 | "indent": [1, 4], 19 | "function-paren-newline": [0], 20 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 21 | "react/jsx-indent": [1, 4], 22 | "react/jsx-indent-props": [1, 4], 23 | "react/require-default-props": [0] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn-error.log 3 | dist 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-render-fam 2 | 3 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](CONTRIBUTING.md#pull-requests) 4 | 5 | A family of 🔥 components to make conditional rendering in React kool again. 6 | 7 | ![preview](static/preview.png) 8 | 9 | ## Installation 10 | 11 | **npm:** `$ npm install --save react-render-fam` 12 | 13 | _or_ 14 | 15 | **Yarn:** `$ yarn add react-render-fam` 16 | 17 | ## Usage 18 | 19 | ### ` ` 20 | 21 | Conditionally renders the children nodes when the predicate(s) return `true`. 22 | 23 | **Props:** 24 | 25 | - `predicate` A boolean expression 26 | 27 | **Example:** 28 | 29 | ```jsx 30 | import { If } from 'react-render-fam'; 31 | 32 | function shouldISayHello() { 33 | return true; 34 | } 35 | 36 | 37 |

Hello World!

38 |
39 | ``` 40 | 41 | ### `` 42 | 43 | Renders a subset of elements which return truthy for all supplied predicates. 44 | 45 | **Props:** 46 | 47 | - `predicates` A function **or** an array of functions. Current value is passed to each predicate for evaluation 48 | - `values` An array of elements to be evaluated and rendered 49 | - `render` Called for every value that satisfies the supplied predicates 50 | 51 | **Example:** Renders all values between 6 and 99 52 | 53 | ```jsx 54 | import { Omit } from 'react-render-fam'; 55 | 56 | const data = [ 57 | { id: 2, value: 1 }, 58 | { id: 3, value: 10 }, 59 | { id: 4, value: 20 }, 60 | { id: 5, value: 99 }, 61 | { id: 7, value: 2000 }, 62 | ]; 63 | 64 | const greaterThanFive = ({ value }) => value > 5; 65 | const lessThanOneHundred = ({ value }) => value < 100; 66 | 67 | ( 74 |

{value}

75 | )} 76 | /> 77 | 78 | ``` 79 | 80 | ### `` 81 | 82 | Sorts the elements in the order specified by the supplied comparison function. Internally uses [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) to determine the correct order of elements. 83 | 84 | Defaults to ascending order if `compare` or `descending` props are omitted. 85 | 86 | **Props:** 87 | 88 | - `by` The key which is evaluated when comparing values 89 | - `compare` A user supplied comparison function. For more information on using `compare` please see: [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 90 | - `descending` When supplied orders the elements in defending order (Assuming the comparison value is an `integer` or `string`) 91 | - `values` An array of objects to be compared and rendered 92 | - `render` A function used to render each sorted element 93 | 94 | **Example:** Renders the list of names in alphabetical order 95 | 96 | ```jsx 97 | import { Sort } from 'react-render-fam'; 98 | 99 | const data = [ 100 | { name: 'Edward', id: 1 }, 101 | { name: 'Sharpe', id: 2 }, 102 | { name: 'And', id: 3 }, 103 | { name: 'The', id: 4 }, 104 | { name: 'Magnetic', id: 5 }, 105 | { name: 'Zeros', id: 6 }, 106 | ]; 107 | 108 | const compare = (by, a, b) => { 109 | const nameA = a[by].toUpperCase(); 110 | const nameB = b[by].toUpperCase(); 111 | 112 | if (nameA < nameB) { 113 | return -1; 114 | } 115 | 116 | if (nameA > nameB) { 117 | return 1; 118 | } 119 | 120 | return 0; 121 | }; 122 | 123 | ( 128 |

{name}

129 | )} 130 | /> 131 | 132 | ``` 133 | 134 | ## License 135 | 136 | MIT © [Daniel Del Core](https://github.com/danieldelcore) 137 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { Component, ReactNode } from 'react'; 2 | 3 | interface RenderProp { 4 | (value: any, i: number): ReactNode; 5 | } 6 | 7 | interface IfProps { 8 | children?: ReactNode; 9 | predicate?: boolean; 10 | } 11 | 12 | export class If extends Component {} 13 | 14 | interface OmitProps { 15 | predicates?: Function | Array; 16 | render?: RenderProp; 17 | values?: Array; 18 | } 19 | 20 | export class Omit extends Component {} 21 | 22 | interface SortProps { 23 | by: string; 24 | compare?: Function; 25 | descending?: boolean; 26 | render?: RenderProp; 27 | values?: Array; 28 | } 29 | 30 | export class Sort extends Component {} 31 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export * from './src'; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-render-fam", 3 | "version": "1.0.11", 4 | "description": "Lit components for conditional rendering, Fam", 5 | "author": "Daniel Del Core", 6 | "repository": "github:danieldelcore/react-render-fam", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "rm -rf dist && webpack", 10 | "test": "jest", 11 | "test:watch": "jest --watch", 12 | "lint": "eslint index.js src", 13 | "preversion": "yarn lint && yarn test", 14 | "version": "yarn build", 15 | "postversion": "git push && git push --tags" 16 | }, 17 | "main": "./dist/index.js", 18 | "typings": "./index.d.ts", 19 | "files": [ 20 | "dist", 21 | "index.d.ts" 22 | ], 23 | "keywords": [ 24 | "component", 25 | "components", 26 | "conditional", 27 | "helper", 28 | "helpers", 29 | "if", 30 | "omit", 31 | "react", 32 | "render", 33 | "sort", 34 | "switch", 35 | "util", 36 | "utilities", 37 | "utils" 38 | ], 39 | "devDependencies": { 40 | "babel-cli": "^6.26.0", 41 | "babel-eslint": "^8.2.3", 42 | "babel-jest": "^22.4.3", 43 | "babel-loader": "^7.1.4", 44 | "babel-plugin-transform-es2015-modules-umd": "^6.24.1", 45 | "babel-preset-env": "^1.6.1", 46 | "babel-preset-react": "^6.24.1", 47 | "babel-preset-stage-2": "^6.24.1", 48 | "eslint": "^4.19.1", 49 | "eslint-config-airbnb": "^16.1.0", 50 | "eslint-plugin-import": "^2.8.0", 51 | "eslint-plugin-jest": "^21.15.1", 52 | "eslint-plugin-jsx-a11y": "^6.0.2", 53 | "eslint-plugin-react": "^7.4.0", 54 | "jest": "^22.4.3", 55 | "react": "^16.3.2", 56 | "react-test-renderer": "^16.3.2", 57 | "webpack": "^4.8.2", 58 | "webpack-cli": "^2.1.3" 59 | }, 60 | "peerDependencies": { 61 | "react": ">=16" 62 | }, 63 | "dependencies": { 64 | "prop-types": "^15.6.1" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/If.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | const If = ({ 4 | predicate = false, 5 | children, 6 | }) => (predicate ? children : ''); 7 | 8 | If.propTypes = { 9 | predicate: PropTypes.bool, 10 | children: PropTypes.node, 11 | }; 12 | 13 | export default If; 14 | -------------------------------------------------------------------------------- /src/If.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import { If } from '.'; 4 | 5 | test('Renders the content if predicate is true', () => { 6 | const value = true; 7 | const component = renderer.create( 8 | 9 |

Hello!

10 |
11 | ); 12 | 13 | const tree = component.toJSON(); 14 | 15 | expect(tree).toMatchSnapshot(); 16 | }); 17 | 18 | test('Renders the content if predicate supplied as a boolean attribute', () => { 19 | const component = renderer.create( 20 | 21 |

Hello!

22 |
23 | ); 24 | 25 | const tree = component.toJSON(); 26 | 27 | expect(tree).toMatchSnapshot(); 28 | }); 29 | 30 | test('Omits the content if predicate not supplied', () => { 31 | const component = renderer.create( 32 | 33 |

Hello!

34 |
35 | ); 36 | 37 | const tree = component.toJSON(); 38 | 39 | expect(tree).toMatchSnapshot(); 40 | }); 41 | 42 | test('Omits the content if predicate is false', () => { 43 | const value = false; 44 | const component = renderer.create( 45 | 46 |

Hello!

47 |
48 | ); 49 | 50 | const tree = component.toJSON(); 51 | 52 | expect(tree).toMatchSnapshot(); 53 | }); 54 | -------------------------------------------------------------------------------- /src/Omit.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | function evaluatePredicates(predicates, value, index) { 4 | const conditions = (typeof predicates === 'function') 5 | ? [predicates] 6 | : predicates; 7 | 8 | return conditions.reduce((accum, predicate) => ( 9 | accum ? predicate(value, index) : false 10 | ), true); 11 | } 12 | 13 | const Omit = ({ 14 | predicates = () => true, 15 | render = () => {}, 16 | values = [], 17 | }) => values.map((value, i) => ( 18 | evaluatePredicates(predicates, value, i) 19 | ? render(value, i) 20 | : null 21 | )); 22 | 23 | Omit.propTypes = { 24 | predicates: PropTypes.oneOfType([ 25 | PropTypes.func, 26 | PropTypes.arrayOf(PropTypes.func), 27 | ]), 28 | render: PropTypes.func, 29 | values: PropTypes.arrayOf( 30 | PropTypes.objectOf(PropTypes.any) 31 | ), 32 | }; 33 | 34 | export default Omit; 35 | -------------------------------------------------------------------------------- /src/Omit.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import { Omit } from '.'; 4 | 5 | test('Renders the content if predicate is true', () => { 6 | const data = [ 7 | { id: 1, value: 0 }, 8 | { id: 2, value: 1 }, 9 | { id: 3, value: 5 }, 10 | { id: 4, value: 6 }, 11 | { id: 5, value: 2000 }, 12 | ]; 13 | 14 | const component = renderer.create( 15 | value > 5} 18 | render={({ id, value }) => ( 19 |

{value}

20 | )} 21 | /> 22 | ); 23 | 24 | const tree = component.toJSON(); 25 | 26 | expect(tree).toMatchSnapshot(); 27 | }); 28 | 29 | test('Renders the content if all predicates are true', () => { 30 | const data = [ 31 | { id: 1, value: 0 }, 32 | { id: 2, value: 1 }, 33 | { id: 3, value: 5 }, 34 | { id: 4, value: 6 }, 35 | { id: 5, value: 99 }, 36 | { id: 6, value: 100 }, 37 | { id: 7, value: 2000 }, 38 | ]; 39 | 40 | const greaterThanFive = ({ value }) => value > 5; 41 | const lessThanOneHundred = ({ value }) => value < 100; 42 | 43 | const component = renderer.create( 44 | ( 51 |

{value}

52 | )} 53 | /> 54 | ); 55 | 56 | const tree = component.toJSON(); 57 | 58 | expect(tree).toMatchSnapshot(); 59 | }); 60 | 61 | test('Renders all items if predicates are omitted', () => { 62 | const data = [ 63 | { id: 1, value: 0 }, 64 | { id: 2, value: 1 }, 65 | { id: 3, value: 5 }, 66 | ]; 67 | 68 | const component = renderer.create( 69 | ( 72 |

{value}

73 | )} 74 | /> 75 | ); 76 | 77 | const tree = component.toJSON(); 78 | 79 | expect(tree).toMatchSnapshot(); 80 | }); 81 | -------------------------------------------------------------------------------- /src/Sort.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | function ascendingOrder(by, a, b) { 4 | return a[by] - b[by]; 5 | } 6 | 7 | function descendingOrder(by, a, b) { 8 | return b[by] - a[by]; 9 | } 10 | 11 | function sort(values, by, comparisonFn) { 12 | return [...values] 13 | .sort((a, b) => comparisonFn(by, a, b)); 14 | } 15 | 16 | const Sort = ({ 17 | render, 18 | by, 19 | compare, 20 | descending, 21 | values = [], 22 | }) => { 23 | let comparisonFn = ascendingOrder; 24 | 25 | if (compare) { 26 | comparisonFn = compare; 27 | } else if (descending) { 28 | comparisonFn = descendingOrder; 29 | } 30 | 31 | return sort(values, by, comparisonFn) 32 | .map((value, i) => render(value, i)); 33 | }; 34 | 35 | Sort.propTypes = { 36 | render: PropTypes.func, 37 | by: PropTypes.string.isRequired, 38 | compare: PropTypes.func, 39 | descending: PropTypes.bool, 40 | values: PropTypes.arrayOf( 41 | PropTypes.objectOf(PropTypes.any) 42 | ), 43 | }; 44 | 45 | export default Sort; 46 | -------------------------------------------------------------------------------- /src/Sort.spec.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import renderer from 'react-test-renderer'; 3 | import { Sort } from '.'; 4 | 5 | test('Renders the content in ascending order', () => { 6 | const data = [ 7 | { id: 1, value: -10 }, 8 | { id: 10, value: 0 }, 9 | { id: 5, value: 2000 }, 10 | { id: 4, value: 6 }, 11 | { id: 2, value: 1 }, 12 | ]; 13 | 14 | const component = renderer.create( 15 | ( 19 |

{value}

20 | )} 21 | /> 22 | ); 23 | 24 | const tree = component.toJSON(); 25 | 26 | expect(tree).toMatchSnapshot(); 27 | }); 28 | 29 | test('Renders the content in ascending order if all comparison options are omitted', () => { 30 | const data = [ 31 | { id: 1, value: -10 }, 32 | { id: 10, value: 0 }, 33 | { id: 5, value: 2000 }, 34 | { id: 4, value: 6 }, 35 | { id: 2, value: 1 }, 36 | ]; 37 | 38 | const component = renderer.create( 39 | ( 43 |

{value}

44 | )} 45 | /> 46 | ); 47 | 48 | const tree = component.toJSON(); 49 | 50 | expect(tree).toMatchSnapshot(); 51 | }); 52 | 53 | test('Renders the content in descending order', () => { 54 | const data = [ 55 | { id: 1, value: -10 }, 56 | { id: 10, value: 0 }, 57 | { id: 5, value: 2000 }, 58 | { id: 4, value: 6 }, 59 | { id: 2, value: 1 }, 60 | ]; 61 | 62 | const component = renderer.create( 63 | ( 68 |

{value}

69 | )} 70 | /> 71 | ); 72 | 73 | const tree = component.toJSON(); 74 | 75 | expect(tree).toMatchSnapshot(); 76 | }); 77 | 78 | test('Renders the content in the order specified by the supplied compare function', () => { 79 | const data = [ 80 | { name: 'Edward', id: 1 }, 81 | { name: 'Sharpe', id: 2 }, 82 | { name: 'And', id: 3 }, 83 | { name: 'The', id: 4 }, 84 | { name: 'Magnetic', id: 5 }, 85 | { name: 'Zeros', id: 6 }, 86 | ]; 87 | 88 | const compare = (by, a, b) => { 89 | const nameA = a[by].toUpperCase(); 90 | const nameB = b[by].toUpperCase(); 91 | 92 | if (nameA < nameB) { 93 | return -1; 94 | } 95 | 96 | if (nameA > nameB) { 97 | return 1; 98 | } 99 | 100 | return 0; 101 | }; 102 | 103 | const component = renderer.create( 104 | ( 109 |

{name}

110 | )} 111 | /> 112 | ); 113 | 114 | const tree = component.toJSON(); 115 | 116 | expect(tree).toMatchSnapshot(); 117 | }); 118 | -------------------------------------------------------------------------------- /src/__snapshots__/If.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Omits the content if predicate is false 1`] = `""`; 4 | 5 | exports[`Omits the content if predicate not supplied 1`] = `""`; 6 | 7 | exports[`Renders the content if predicate is true 1`] = ` 8 |

9 | Hello! 10 |

11 | `; 12 | 13 | exports[`Renders the content if predicate supplied as a boolean attribute 1`] = ` 14 |

15 | Hello! 16 |

17 | `; 18 | -------------------------------------------------------------------------------- /src/__snapshots__/Omit.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Renders all items if predicates are omitted 1`] = ` 4 | Array [ 5 |

6 | 0 7 |

, 8 |

9 | 1 10 |

, 11 |

12 | 5 13 |

, 14 | ] 15 | `; 16 | 17 | exports[`Renders the content if all predicates are true 1`] = ` 18 | Array [ 19 |

20 | 6 21 |

, 22 |

23 | 99 24 |

, 25 | ] 26 | `; 27 | 28 | exports[`Renders the content if predicate is true 1`] = ` 29 | Array [ 30 |

31 | 6 32 |

, 33 |

34 | 2000 35 |

, 36 | ] 37 | `; 38 | -------------------------------------------------------------------------------- /src/__snapshots__/Sort.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Renders the content in ascending order 1`] = ` 4 | Array [ 5 |

6 | -10 7 |

, 8 |

9 | 0 10 |

, 11 |

12 | 1 13 |

, 14 |

15 | 6 16 |

, 17 |

18 | 2000 19 |

, 20 | ] 21 | `; 22 | 23 | exports[`Renders the content in ascending order if all comparison options are omitted 1`] = ` 24 | Array [ 25 |

26 | -10 27 |

, 28 |

29 | 0 30 |

, 31 |

32 | 1 33 |

, 34 |

35 | 6 36 |

, 37 |

38 | 2000 39 |

, 40 | ] 41 | `; 42 | 43 | exports[`Renders the content in descending order 1`] = ` 44 | Array [ 45 |

46 | 2000 47 |

, 48 |

49 | 6 50 |

, 51 |

52 | 1 53 |

, 54 |

55 | 0 56 |

, 57 |

58 | -10 59 |

, 60 | ] 61 | `; 62 | 63 | exports[`Renders the content in the order specified by the supplied compare function 1`] = ` 64 | Array [ 65 |

66 | And 67 |

, 68 |

69 | Edward 70 |

, 71 |

72 | Magnetic 73 |

, 74 |

75 | Sharpe 76 |

, 77 |

78 | The 79 |

, 80 |

81 | Zeros 82 |

, 83 | ] 84 | `; 85 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export { default as If } from './If'; 2 | export { default as Omit } from './Omit'; 3 | export { default as Sort } from './Sort'; 4 | -------------------------------------------------------------------------------- /static/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danieldelcore/react-render-fam/8ab86689c666d0f485c94036384aa1c8731752d4/static/preview.png -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | mode: 'production', 5 | entry: './index.js', 6 | output: { 7 | path: path.resolve(__dirname, './dist'), 8 | filename: 'index.js', 9 | library: 'react-render-fam', 10 | libraryTarget: 'umd2', 11 | }, 12 | module: { 13 | rules: [{ 14 | test: /\.js$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: 'babel-loader', 18 | }, 19 | }], 20 | }, 21 | externals: ['react'], 22 | devtool: 'source-map', 23 | }; 24 | --------------------------------------------------------------------------------