diff --git a/README.md b/README.md index 507f431..8d31952 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,17 @@ const decorators = { ``` +#### makeKeyProp +Optional prop to generate your own [React key props](https://reactjs.org/docs/lists-and-keys.html). Function that takes the node as the only param. Example: +```jsx + node.yourUniqueId} +/> +``` + ### Data Attributes ```javascript diff --git a/__tests__/Container.test.js b/__tests__/Container.test.js index f4c60d3..79ee37a 100644 --- a/__tests__/Container.test.js +++ b/__tests__/Container.test.js @@ -29,13 +29,7 @@ describe('', () => { describe('when terminal is true', () => { it('should contains a decorators.Header into their children', () => { const wrapper = renderComponent({terminal: true}); - expect( - wrapper - .children() - .contains( - - ) - ).toBe(true); + expect(wrapper.find('Header').dive().text()).toBe('react-treebeard'); }); }); describe('when terminal is false', () => { diff --git a/__tests__/Treebeard.test.js b/__tests__/Treebeard.test.js index 4efdab7..92a1417 100644 --- a/__tests__/Treebeard.test.js +++ b/__tests__/Treebeard.test.js @@ -17,4 +17,9 @@ describe('', () => { const wrapper = renderComponent(); expect(wrapper).toMatchSnapshot(); }); + it('should handle custom makeKeyProp', () => { + const wrapper = renderComponent({ makeKeyProp: (node) => node.sha }); + const firstNodeKey = wrapper.find('TreeNode').first().key(); + expect(firstNodeKey).toBe('9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'); + }); }); diff --git a/__tests__/__snapshots__/Treebeard.test.js.snap b/__tests__/__snapshots__/Treebeard.test.js.snap index 97e94ad..114f161 100644 --- a/__tests__/__snapshots__/Treebeard.test.js.snap +++ b/__tests__/__snapshots__/Treebeard.test.js.snap @@ -46,6 +46,7 @@ exports[` should match default snapshot 1`] = ` } } key="1" + makeKeyProp={[Function]} node={ Object { "children": Array [ @@ -53,26 +54,33 @@ exports[` should match default snapshot 1`] = ` "children": Array [ Object { "name": "app.js", + "sha": "828270ffd0e8ee6f32eb2a83bc3da31f3a9ac73f4833de481f65337c0802a9aa", }, Object { "name": "data.js", + "sha": "3ae815b93875786c05cba19df444662a789640e83ab65f4b84e9c16474cb1ed0", }, Object { "name": "index.html", + "sha": "f82ec4ae606600a57396ca47e21e0f013bded8523dfaffee040eefa4ea815962", }, Object { "name": "styles.js", + "sha": "3ba988e33afe3c6e453fc86bf26813ad90e5fe2dd7f55527a8c1408f861bfeac", }, Object { "name": "webpack.config.js", + "sha": "017cbc93d594c0f410b0c4c86c6b160a421674ddf72ab10201a16646f02fcb9c", }, ], "name": "example", + "sha": "f05bcd2dffa3e047ce58c0f1a1877dcbe4d868cf0eac58bc2f7f9ab805289b0b", }, Object { "children": Array [], "loading": true, "name": "node_modules", + "sha": "bd9d2e8d246bc323081327ef12d3d40e0fc09cc7d36ea05bc286ea74e47c3515", }, Object { "children": Array [ @@ -80,42 +88,53 @@ exports[` should match default snapshot 1`] = ` "children": Array [ Object { "name": "decorators.js", + "sha": "cc37c52929a8f16cfbf7b1881119f963e5a332507fac7d7f4fa9a271ad58534e", }, Object { "name": "treebeard.js", + "sha": "177c1b4a2b85d1c2a15ed481dbcefd9a43a901d6efa05c41dbb7e1859d5547e2", }, ], "name": "components", }, Object { "name": "index.js", + "sha": "cc02786ca507e473781c0c08a2738e921eaa49c061515598d0ef1dff3054ea7b", }, ], "name": "src", + "sha": "da5bd1bf868d4b2a68ce84c5e13545523ddc58232fe59e74a3d8ccb9be9e6db6", }, Object { "children": Array [ Object { "name": "animations.js", + "sha": "1fde2cd5a18a6bd17660167f0fe5296a2ea1275da38961632b5630abf9c7a85d", }, Object { "name": "default.js", + "sha": "a59dc347bb2eb8f5a51f3b7f65dd63580829bcdfee89d5331f9ff063438b6dbd", }, ], "name": "themes", + "sha": "8c382185c33911dbc2e2a76094b73a02644635ed9de1e9995a8b06f96bdbae32", }, Object { "name": "gulpfile.js", + "sha": "f68fe2b19685fb03204cfb3a0c7e58c7698fa9abc595185e39028fbc6e0531e1", }, Object { "name": "index.js", + "sha": "08f982599837827c163a787c6ae0a5cf1b493829493c1f29d707a5026b28ed0e", }, Object { "name": "package.json", + "sha": "c31e4a244c319e43a03cd3fbfabd0c6d976567c5b79e8b06a218b2aae3637839", }, ], "id": 1, "name": "react-treebeard", + "sha": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "toggled": true, } } diff --git a/__tests__/mocks/data.js b/__tests__/mocks/data.js index cae006f..4efa39b 100644 --- a/__tests__/mocks/data.js +++ b/__tests__/mocks/data.js @@ -2,44 +2,55 @@ export default { name: 'react-treebeard', id: 1, toggled: true, + sha: '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', children: [ { name: 'example', + sha: 'f05bcd2dffa3e047ce58c0f1a1877dcbe4d868cf0eac58bc2f7f9ab805289b0b', children: [ - { name: 'app.js' }, - { name: 'data.js' }, - { name: 'index.html' }, - { name: 'styles.js' }, - { name: 'webpack.config.js' } + { name: 'app.js', sha: '828270ffd0e8ee6f32eb2a83bc3da31f3a9ac73f4833de481f65337c0802a9aa' }, + { name: 'data.js', sha: '3ae815b93875786c05cba19df444662a789640e83ab65f4b84e9c16474cb1ed0' }, + { name: 'index.html', sha: 'f82ec4ae606600a57396ca47e21e0f013bded8523dfaffee040eefa4ea815962' }, + { name: 'styles.js', sha: '3ba988e33afe3c6e453fc86bf26813ad90e5fe2dd7f55527a8c1408f861bfeac' }, + { name: 'webpack.config.js', sha: '017cbc93d594c0f410b0c4c86c6b160a421674ddf72ab10201a16646f02fcb9c' } ] }, { name: 'node_modules', loading: true, + sha: 'bd9d2e8d246bc323081327ef12d3d40e0fc09cc7d36ea05bc286ea74e47c3515', children: [] }, { name: 'src', + sha: 'da5bd1bf868d4b2a68ce84c5e13545523ddc58232fe59e74a3d8ccb9be9e6db6', children: [ { name: 'components', children: [ - { name: 'decorators.js' }, - { name: 'treebeard.js' } + { + name: 'decorators.js', + sha: 'cc37c52929a8f16cfbf7b1881119f963e5a332507fac7d7f4fa9a271ad58534e' + }, + { + name: 'treebeard.js', + sha: '177c1b4a2b85d1c2a15ed481dbcefd9a43a901d6efa05c41dbb7e1859d5547e2' + } ] }, - { name: 'index.js' } + { name: 'index.js', sha: 'cc02786ca507e473781c0c08a2738e921eaa49c061515598d0ef1dff3054ea7b' } ] }, { name: 'themes', + sha: '8c382185c33911dbc2e2a76094b73a02644635ed9de1e9995a8b06f96bdbae32', children: [ - { name: 'animations.js' }, - { name: 'default.js' } + { name: 'animations.js', sha: '1fde2cd5a18a6bd17660167f0fe5296a2ea1275da38961632b5630abf9c7a85d' }, + { name: 'default.js', sha: 'a59dc347bb2eb8f5a51f3b7f65dd63580829bcdfee89d5331f9ff063438b6dbd' } ] }, - { name: 'gulpfile.js' }, - { name: 'index.js' }, - { name: 'package.json' } + { name: 'gulpfile.js', sha: 'f68fe2b19685fb03204cfb3a0c7e58c7698fa9abc595185e39028fbc6e0531e1' }, + { name: 'index.js', sha: '08f982599837827c163a787c6ae0a5cf1b493829493c1f29d707a5026b28ed0e' }, + { name: 'package.json', sha: 'c31e4a244c319e43a03cd3fbfabd0c6d976567c5b79e8b06a218b2aae3637839' } ] }; diff --git a/__tests__/utils.test.js b/__tests__/utils.test.js new file mode 100644 index 0000000..9a47660 --- /dev/null +++ b/__tests__/utils.test.js @@ -0,0 +1,10 @@ +import {makeKeyProp} from '../src/util'; + +describe('makeKeyProp', () => { + it('should return id first', () => { + expect(makeKeyProp({id: 'uniqueId'})).toBe('uniqueId'); + }); + it('should return random string if no id is present', () => { + expect(typeof makeKeyProp({})).toBe('string'); + }); +}); diff --git a/package.json b/package.json index 03cba8a..219337b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-treebeard", - "version": "3.2.4", + "version": "3.2.5", "description": "React Tree View Component", "main": "index.js", "scripts": { diff --git a/src/components/TreeNode/index.js b/src/components/TreeNode/index.js index 574a7b0..f9caa2e 100644 --- a/src/components/TreeNode/index.js +++ b/src/components/TreeNode/index.js @@ -4,7 +4,7 @@ import styled from '@emotion/styled'; import {isArray, isFunction} from 'lodash'; import defaultAnimations from '../../themes/animations'; -import {randomString} from '../../util'; +import {makeKeyProp} from '../../util'; import {Ul} from '../common'; import NodeHeader from '../NodeHeader'; import Drawer from './Drawer'; @@ -45,7 +45,7 @@ class TreeNode extends PureComponent { renderChildren(decorators) { const { - animations, decorators: propDecorators, node, style, onToggle, onSelect, customStyles + animations, decorators: propDecorators, node, style, onToggle, onSelect, customStyles, makeKeyProp } = this.props; if (node.loading) { @@ -69,7 +69,7 @@ class TreeNode extends PureComponent { style={style} customStyles={customStyles} decorators={propDecorators} - key={child.id || randomString()} + key={makeKeyProp(child)} node={child} /> ))} @@ -113,11 +113,13 @@ TreeNode.propTypes = { animations: PropTypes.oneOfType([ PropTypes.object, PropTypes.bool - ]).isRequired + ]).isRequired, + makeKeyProp: PropTypes.func }; TreeNode.defaultProps = { - customStyles: {} + customStyles: {}, + makeKeyProp }; export default TreeNode; diff --git a/src/components/index.js b/src/components/index.js index 553af5e..fb5aca0 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -4,13 +4,13 @@ import {castArray} from 'lodash'; import defaultTheme from '../themes/default'; import defaultAnimations from '../themes/animations'; -import {randomString} from '../util'; +import {makeKeyProp} from '../util'; import {Ul} from './common'; import defaultDecorators from './Decorators'; import TreeNode from './TreeNode'; const TreeBeard = ({ - animations, decorators, data, onToggle, style, onSelect, customStyles + animations, decorators, data, onToggle, style, onSelect, customStyles, makeKeyProp }) => (
    {castArray(data).map(node => ( @@ -21,7 +21,8 @@ const TreeBeard = ({ animations={animations} onSelect={onSelect} customStyles={customStyles} - key={node.id || randomString()} + makeKeyProp={makeKeyProp} + key={makeKeyProp(node)} style={{...defaultTheme.tree.node, ...style.tree.node}} /> ))} @@ -41,14 +42,16 @@ TreeBeard.propTypes = { ]), onToggle: PropTypes.func, onSelect: PropTypes.func, - decorators: PropTypes.object + decorators: PropTypes.object, + makeKeyProp: PropTypes.func }; TreeBeard.defaultProps = { style: defaultTheme, animations: defaultAnimations, decorators: defaultDecorators, - customStyles: {} + customStyles: {}, + makeKeyProp }; export default TreeBeard; diff --git a/src/util/index.js b/src/util/index.js index 661c6cd..3f77fb1 100644 --- a/src/util/index.js +++ b/src/util/index.js @@ -1,3 +1,7 @@ +import makeKeyProp from './makeKeyProp'; import randomString from './randomString'; -export {randomString}; +export { + makeKeyProp, + randomString +}; diff --git a/src/util/makeKeyProp.js b/src/util/makeKeyProp.js new file mode 100644 index 0000000..8c00c58 --- /dev/null +++ b/src/util/makeKeyProp.js @@ -0,0 +1,5 @@ +import randomString from './randomString'; + +const makeKeyProp = (node) => node.id || randomString(); + +export default makeKeyProp;