diff --git a/README.md b/README.md index 1fac113..41e7406 100644 --- a/README.md +++ b/README.md @@ -430,18 +430,24 @@ See [Array operations](#array-operations) for convenient ways to let the user ma ```js function MyComponent(props) { const form = useDendriform({ - colours: ['Red', 'Green', 'Blue'] + colours: [ + {colour: 'Red'}, + {colour: 'Green'}, + {colour: 'Blue'} + ] }); return
{form.renderAll('colours', colourForm => { - const colour = colourForm.useValue(); + const colour = colourForm.branch('colour').useValue(); return
Colour: {colour}
; })}
; } ``` +Please note that if you are rendering an array that contains any primitive values (undefined, number, string) then the automatic keying will not be able to be as accurate as if you were to render an array of objects / arrays / class instances / sets / maps. This is because object references are used to track the movement of array elements through time, and primitive elements cannot be tracked in this way. + Array element forms can also opt-in to updates regarding their indexes using the `.useIndex()` hook. If you'll be allowing users to re-order items in an array, then please note that you'll get better performance if array element components don't know about their indexes. If the `.useIndex()` hook is used, a element that has moved its position inside of its parent array will need to update, even if it is otherwise unchanged. @@ -1214,7 +1220,11 @@ const dndReorder = (result) => (draft) => { function DragAndDrop() { const form = useDendriform({ - colours: ['Red', 'Green', 'Blue'] + colours: [ + {colour: 'Red'}, + {colour: 'Green'}, + {colour: 'Blue'} + ] }); const onDragEnd = useCallback(result => { @@ -1254,7 +1264,7 @@ function DragAndDropList(props) { {...provided.draggableProps} {...provided.dragHandleProps} > - + } ; diff --git a/packages/dendriform-demo/components/Demos.tsx b/packages/dendriform-demo/components/Demos.tsx index f097543..bf7d431 100644 --- a/packages/dendriform-demo/components/Demos.tsx +++ b/packages/dendriform-demo/components/Demos.tsx @@ -820,14 +820,18 @@ const offsetElement = (form: Dendriform, offset: number): void => { function ArrayOperations(): React.ReactElement { const form = useDendriform({ - colours: ['Red', 'Green', 'Blue'] + colours: [ + {colour: 'Red'}, + {colour: 'Green'}, + {colour: 'Blue'} + ] }); const coloursForm = form.branch('colours'); const shift = useCallback(() => coloursForm.set(array.shift()), []); const pop = useCallback(() => coloursForm.set(array.pop()), []); - const unshift = useCallback(() => coloursForm.set(array.unshift('Puce')), []); - const push = useCallback(() => coloursForm.set(array.push('Puce')), []); + const unshift = useCallback(() => coloursForm.set(array.unshift({colour: 'Puce'})), []); + const push = useCallback(() => coloursForm.set(array.push({colour: 'Puce'})), []); const move = useCallback(() => coloursForm.set(array.move(-1,0)), []); return @@ -838,8 +842,11 @@ function ArrayOperations(): React.ReactElement { const moveUp = useCallback(() => offsetElement(colourForm, -1), []); return - - + {colourForm.render('colour', form => ( + + + + ))} @@ -861,14 +868,18 @@ const offsetElement = (form, offset) => { function MyComponent(props) { const form = useDendriform({ - colours: ['Red', 'Green', 'Blue'] + colours: [ + {colour: 'Red'}, + {colour: 'Green'}, + {colour: 'Blue'} + ] }); const coloursForm = form.branch('colours'); const shift = useCallback(() => coloursForm.set(array.shift()), []); const pop = useCallback(() => coloursForm.set(array.pop()), []); - const unshift = useCallback(() => coloursForm.set(array.unshift('Puce')), []); - const push = useCallback(() => coloursForm.set(array.push('Puce')), []); + const unshift = useCallback(() => coloursForm.set(array.unshift({colour: 'Puce'})), []); + const push = useCallback(() => coloursForm.set(array.push({colour: 'Puce'})), []); const move = useCallback(() => coloursForm.set(array.move(-1,0)), []); return
@@ -879,7 +890,9 @@ function MyComponent(props) { const moveUp = useCallback(() => offsetElement(colourForm, -1), []); return
- + {colourForm.render('colour', form => ( + + ))} @@ -903,12 +916,16 @@ function MyComponent(props) { function ArrayIndexes(): React.ReactElement { const form = useDendriform({ - colours: ['Red', 'Green', 'Blue'] + colours: [ + {colour: 'Red'}, + {colour: 'Green'}, + {colour: 'Blue'} + ] }); return {form.renderAll('colours', colourForm => { - const colour = colourForm.useValue(); + const colour = colourForm.branch('colour').useValue(); const index = colourForm.useIndex(); const moveDown = useCallback(() => offsetElement(colourForm, 1), []); const moveUp = useCallback(() => offsetElement(colourForm, -1), []); @@ -930,12 +947,16 @@ const offsetElement = (form, offset) => { function MyComponent(props) { const form = useDendriform({ - colours: ['Red', 'Green', 'Blue'] + colours: [ + {colour: 'Red'}, + {colour: 'Green'}, + {colour: 'Blue'} + ] }); return
{form.renderAll('colours', colourForm => { - const colour = colourForm.useValue(); + const colour = colourForm.branch('colour').useValue(); const index = colourForm.useIndex(); const moveDown = useCallback(() => offsetElement(colourForm, 1), []); const moveUp = useCallback(() => offsetElement(colourForm, -1), []); @@ -1402,7 +1423,11 @@ function dndReorder(result: DropResult): ((draft: Draft) function DragAndDrop(): React.ReactElement { const form = useDendriform({ - colours: ['Red', 'Green', 'Blue'] + colours: [ + {colour: 'Red'}, + {colour: 'Green'}, + {colour: 'Blue'} + ] }); const onDragEnd = useCallback(result => { @@ -1410,7 +1435,7 @@ function DragAndDrop(): React.ReactElement { }, []); const onAdd = useCallback(() => { - form.branch('colours').set(array.push('Puce')); + form.branch('colours').set(array.push({colour: 'Puce'})); }, []); return @@ -1431,7 +1456,7 @@ function DragAndDrop(): React.ReactElement { } type DragAndDropListProps = { - form: Dendriform; + form: Dendriform<{colour: string}[]>; }; function DragAndDropList(props: DragAndDropListProps): React.ReactElement { @@ -1448,7 +1473,11 @@ function DragAndDropList(props: DragAndDropListProps): React.ReactElement { {...provided.dragHandleProps} > - + {eachForm.render('colour', form => ( + + + + ))}
} @@ -1473,7 +1502,11 @@ const dndReorder = (result) => (draft) => { function DragAndDrop() { const form = useDendriform({ - colours: ['Red', 'Green', 'Blue'] + colours: [ + {colour: 'Red'}, + {colour: 'Green'}, + {colour: 'Blue'} + ] }); const onDragEnd = useCallback(result => { @@ -1481,7 +1514,7 @@ function DragAndDrop() { }, []); const onAdd = useCallback(() => { - form.branch('colours').set(array.push('Puce')); + form.branch('colours').set(array.push({colour: 'Puce'})); }, []); return
@@ -1513,7 +1546,9 @@ function DragAndDropList(props) { {...provided.draggableProps} {...provided.dragHandleProps} > - + {eachForm.render('colour', form => ( + + ))}
} ; @@ -1561,7 +1596,7 @@ function PluginSubmitExample(): React.ReactElement { }, onError: e => e.message }) - }); + }); const form = useDendriform(() => initialValue, {plugins}); @@ -1632,7 +1667,7 @@ function PluginSubmitExample() { await fakeSave(newValue); } }) - }); + }); const form = useDendriform(() => initialValue, {plugins}); @@ -1691,7 +1726,7 @@ function Cancel(): React.ReactElement { } }); - + // eslint-disable-next-line no-console ageForm.useCancel(reason => console.warn(reason)); diff --git a/packages/dendriform-immer-patch-optimiser/src/optimise.ts b/packages/dendriform-immer-patch-optimiser/src/optimise.ts index 5498b70..3e5d3df 100644 --- a/packages/dendriform-immer-patch-optimiser/src/optimise.ts +++ b/packages/dendriform-immer-patch-optimiser/src/optimise.ts @@ -127,6 +127,11 @@ export const optimiseArray = (base: B[], patches: ImmerPatch[]): DendriformPa if(!Array.isArray(value)) return patches; targetIds = value.map(addItem); + } else if(base.some(b => typeof b !== 'object')) { + // if any primitives are in the array, we cant reliably track by reference + // so skip the optimisation + return patches; + } else { const replacedPatches = patches.map(patch => { const {op, value, path} = patch; diff --git a/packages/dendriform-immer-patch-optimiser/test/optimise.test.ts b/packages/dendriform-immer-patch-optimiser/test/optimise.test.ts index 6f37541..2967588 100644 --- a/packages/dendriform-immer-patch-optimiser/test/optimise.test.ts +++ b/packages/dendriform-immer-patch-optimiser/test/optimise.test.ts @@ -37,13 +37,21 @@ function runTest( }); } +const A = {id: 'a'}; +const B = {id: 'b'}; +const C = {id: 'c'}; +const D = {id: 'd'}; +const E = {id: 'e'}; +const F = {id: 'f'}; +const Z = {id: 'z'}; + describe(`pop`, () => { runTest( - ['a','b','c'], + [A,B,C], d => { d.pop(); }, - ['a','b'], + [A,B], { vanilla: [ {op: 'replace', path: ['length'], value: 2} @@ -57,12 +65,12 @@ describe(`pop`, () => { describe(`pop x2`, () => { runTest( - ['a','b','c'], + [A,B,C], d => { d.pop(); d.pop(); }, - ['a'], + [A], { vanilla: [ {op: 'replace', path: ['length'], value: 1} @@ -76,15 +84,15 @@ describe(`pop x2`, () => { describe(`shift`, () => { runTest( - ['a','b','c'], + [A,B,C], d => { d.shift(); }, - ['b','c'], + [B,C], { vanilla: [ - {op: 'replace', path: [0], value: 'b'}, - {op: 'replace', path: [1], value: 'c'}, + {op: 'replace', path: [0], value: B}, + {op: 'replace', path: [1], value: C}, {op: 'replace', path: ['length'], value: 2} ], optimised: [ @@ -98,15 +106,15 @@ describe(`shift`, () => { describe(`shift x2`, () => { runTest( - ['a','b','c'], + [A,B,C], d => { d.shift(); d.shift(); }, - ['c'], + [C], { vanilla: [ - {op: 'replace', path: [0], value: 'c'}, + {op: 'replace', path: [0], value: C}, {op: 'replace', path: ['length'], value: 1} ], optimised: [ @@ -119,17 +127,17 @@ describe(`shift x2`, () => { describe(`push`, () => { runTest( - ['a','b','c'], + [A,B,C], d => { - d.push('d'); + d.push(D); }, - ['a','b','c','d'], + [A,B,C,D], { vanilla: [ - {op: 'add', path: [3], value: 'd'} + {op: 'add', path: [3], value: D} ], optimised: [ - {op: 'add', path: [3], value: 'd'} + {op: 'add', path: [3], value: D} ] } ); @@ -137,20 +145,20 @@ describe(`push`, () => { describe(`push x2`, () => { runTest( - ['a','b','c'], + [A,B,C], d => { - d.push('d'); - d.push('e'); + d.push(D); + d.push(E); }, - ['a','b','c','d','e'], + [A,B,C,D,E], { vanilla: [ - {op: 'add', path: [3], value: 'd'}, - {op: 'add', path: [4], value: 'e'} + {op: 'add', path: [3], value: D}, + {op: 'add', path: [4], value: E} ], optimised: [ - {op: 'add', path: [3], value: 'd'}, - {op: 'add', path: [4], value: 'e'} + {op: 'add', path: [3], value: D}, + {op: 'add', path: [4], value: E} ] } ); @@ -159,20 +167,20 @@ describe(`push x2`, () => { describe(`unshift`, () => { runTest( - ['a','b','c'], + [A,B,C], d => { - d.unshift('d'); + d.unshift(D); }, - ['d','a','b','c'], + [D,A,B,C], { vanilla: [ - {op: 'replace', path: [0], value: 'd'}, - {op: 'replace', path: [1], value: 'a'}, - {op: 'replace', path: [2], value: 'b'}, - {op: 'add', path: [3], value: 'c'} + {op: 'replace', path: [0], value: D}, + {op: 'replace', path: [1], value: A}, + {op: 'replace', path: [2], value: B}, + {op: 'add', path: [3], value: C} ], optimised: [ - {op: 'add', path: [0], value: 'd'} + {op: 'add', path: [0], value: D} ] } ); @@ -180,23 +188,23 @@ describe(`unshift`, () => { describe(`unshift x2`, () => { runTest( - ['a','b','c'], + [A,B,C], d => { - d.unshift('d'); - d.unshift('e'); + d.unshift(D); + d.unshift(E); }, - ['e','d','a','b','c'], + [E,D,A,B,C], { vanilla: [ - {op: 'replace', path: [0], value: 'e'}, - {op: 'replace', path: [1], value: 'd'}, - {op: 'replace', path: [2], value: 'a'}, - {op: 'add', path: [3], value: 'b'}, - {op: 'add', path: [4], value: 'c'} + {op: 'replace', path: [0], value: E}, + {op: 'replace', path: [1], value: D}, + {op: 'replace', path: [2], value: A}, + {op: 'add', path: [3], value: B}, + {op: 'add', path: [4], value: C} ], optimised: [ - {op: 'add', path: [0], value: 'e'}, - {op: 'add', path: [1], value: 'd'} + {op: 'add', path: [0], value: E}, + {op: 'add', path: [1], value: D} ] } ); @@ -204,17 +212,17 @@ describe(`unshift x2`, () => { describe(`reverse`, () => { runTest( - ['a','b','c','d','e'], + [A,B,C,D,E], d => { d.reverse(); }, - ['e','d','c','b','a'], + [E,D,C,B,A], { vanilla: [ - {op: 'replace', path: [0], value: 'e'}, - {op: 'replace', path: [1], value: 'd'}, - {op: 'replace', path: [3], value: 'b'}, - {op: 'replace', path: [4], value: 'a'} + {op: 'replace', path: [0], value: E}, + {op: 'replace', path: [1], value: D}, + {op: 'replace', path: [3], value: B}, + {op: 'replace', path: [4], value: A} ], optimised: [ {op: 'move', from: [4], path: [0]}, @@ -228,36 +236,28 @@ describe(`reverse`, () => { describe(`reverse with adds and removes`, () => { runTest( - ['a','b','X','Y','c','d','e','Z'], + [A,B,C,D,E,F], d => { - d.splice(2,2); - d.pop(); - d.reverse(); - d.push('C'); - d.splice(3, 0, 'B'); - d.unshift('A'); + d.splice(2,2); // abef + d.pop(); // abe + d.reverse(); // eba + d.push(C); // ebac + d.splice(3, 0, Z); // ebazc }, - ['A','e','d','c','B','b','a','C'], + [E,B,A,Z,C], { vanilla: [ - {op: 'replace', path: [0], value: 'A'}, - {op: 'replace', path: [1], value: 'e'}, - {op: 'replace', path: [2], value: 'd'}, - {op: 'replace', path: [3], value: 'c'}, - {op: 'replace', path: [4], value: 'B'}, - {op: 'replace', path: [5], value: 'b'}, - {op: 'replace', path: [6], value: 'a'}, - {op: 'replace', path: [7], value: 'C'} + {op: 'replace', path: [0], value: E}, + {op: 'replace', path: [2], value: A}, + {op: 'replace', path: [3], value: Z}, + {op: 'replace', path: [4], value: C}, + {op: 'replace', path: ['length'], value: 5} ], optimised: [ - {op: 'move', from: [6], path: [0]}, - {op: 'move', from: [6], path: [1]}, - {op: 'move', from: [6], path: [2]}, - {op: 'move', from: [4], path: [3]}, - {op: 'replace', path: ['length'], value: 5}, - {op: 'add', path: [0], value: 'A'}, - {op: 'add', path: [4], value: 'B'}, - {op: 'add', path: [7], value: 'C'} + {op: 'move', from: [4], path: [0]}, + {op: 'move', from: [2], path: [1]}, + {op: 'replace', path: ['length'], value: 4}, + {op: 'add', path: [3], value: Z} ] } ); @@ -265,17 +265,21 @@ describe(`reverse with adds and removes`, () => { describe(`sort`, () => { runTest( - ['e','b','a','c','d'], + [E,B,A,C,D], d => { - d.sort(); + d.sort((a, b) => { + if(a.id > b.id) return 1; + if(a.id < b.id) return -1; + return 0; + }); }, - ['a','b','c','d','e'], + [A,B,C,D,E], { vanilla: [ - {op: 'replace', path: [0], value: 'a'}, - {op: 'replace', path: [2], value: 'c'}, - {op: 'replace', path: [3], value: 'd'}, - {op: 'replace', path: [4], value: 'e'} + {op: 'replace', path: [0], value: A}, + {op: 'replace', path: [2], value: C}, + {op: 'replace', path: [3], value: D}, + {op: 'replace', path: [4], value: E} ], optimised: [ {op: 'move', from: [2], path: [0]}, @@ -289,19 +293,19 @@ describe(`sort`, () => { describe(`splice`, () => { runTest( - ['a','b','c','d'], + [A,B,C,D], d => { - d.splice(2, 0, 'e'); + d.splice(2, 0, E); }, - ['a','b','e','c','d'], + [A,B,E,C,D], { vanilla: [ - {op: 'replace', path: [2], value: 'e'}, - {op: 'replace', path: [3], value: 'c'}, - {op: 'add', path: [4], value: 'd'} + {op: 'replace', path: [2], value: E}, + {op: 'replace', path: [3], value: C}, + {op: 'add', path: [4], value: D} ], optimised: [ - {op: 'add', path: [2], value: 'e'} + {op: 'add', path: [2], value: E} ] } ); @@ -309,23 +313,23 @@ describe(`splice`, () => { describe(`splice x2`, () => { runTest( - ['a','b','c','d'], + [A,B,C,D], d => { - d.splice(2, 0, 'e'); - d.splice(1, 0, 'f'); + d.splice(2, 0, E); + d.splice(1, 0, F); }, - ['a','f','b','e','c','d'], + [A,F,B,E,C,D], { vanilla: [ - {op: 'replace', path: [1], value: 'f'}, - {op: 'replace', path: [2], value: 'b'}, - {op: 'replace', path: [3], value: 'e'}, - {op: 'add', path: [4], value: 'c'}, - {op: 'add', path: [5], value: 'd'} + {op: 'replace', path: [1], value: F}, + {op: 'replace', path: [2], value: B}, + {op: 'replace', path: [3], value: E}, + {op: 'add', path: [4], value: C}, + {op: 'add', path: [5], value: D} ], optimised: [ - {op: 'add', path: [1], value: 'f'}, - {op: 'add', path: [3], value: 'e'} + {op: 'add', path: [1], value: F}, + {op: 'add', path: [3], value: E} ] } ); @@ -337,23 +341,23 @@ describe(`splice remove unshift`, () => { // - do move // - are new runTest( - ['a','b','c','d'], + [A,B,C,D], d => { d.splice(2, 1); - d.push('e','f'); + d.push(E,F); }, - ['a','b','d','e','f'], + [A,B,D,E,F], { vanilla: [ - {op: 'replace', path: [2], value: 'd'}, - {op: 'replace', path: [3], value: 'e'}, - {op: 'add', path: [4], value: 'f'} + {op: 'replace', path: [2], value: D}, + {op: 'replace', path: [3], value: E}, + {op: 'add', path: [4], value: F} ], optimised: [ {op: 'move', from: [3], path: [2]}, {op: 'replace', path: ['length'], value: 3}, - {op: 'add', path: [3], value: 'e'}, - {op: 'add', path: [4], value: 'f'} + {op: 'add', path: [3], value: E}, + {op: 'add', path: [4], value: F} ] } ); @@ -365,23 +369,23 @@ describe(`splice remove unshift with object references involved`, () => { // - do move // - are new runTest( - [{i:'a'},{i:'b'},{i:'c'},{i:'d'}], + [{i:A},{i:B},{i:C},{i:D}], d => { d.splice(2, 1); - d.push({i:'e'},{i:'f'}); + d.push({i:E},{i:F}); }, - [{i:'a'},{i:'b'},{i:'d'},{i:'e'},{i:'f'}], + [{i:A},{i:B},{i:D},{i:E},{i:F}], { vanilla: [ - {op: 'replace', path: [2], value: {i:'d'}}, - {op: 'replace', path: [3], value: {i:'e'}}, - {op: 'add', path: [4], value: {i:'f'}} + {op: 'replace', path: [2], value: {i:D}}, + {op: 'replace', path: [3], value: {i:E}}, + {op: 'add', path: [4], value: {i:F}} ], optimised: [ {op: 'move', from: [3], path: [2]}, {op: 'replace', path: ['length'], value: 3}, - {op: 'add', path: [3], value: {i:'e'}}, - {op: 'add', path: [4], value: {i:'f'}} + {op: 'add', path: [3], value: {i:E}}, + {op: 'add', path: [4], value: {i:F}} ] } ); @@ -391,7 +395,7 @@ describe(`change deep arrays`, () => { runTest( { foo: { - bar: ['a','b','c'] + bar: [A,B,C] }, baz: 'baz', qux: [] @@ -405,15 +409,15 @@ describe(`change deep arrays`, () => { }, { foo: { - bar: ['c','b','a'] + bar: [C,B,A] }, baz: 'baz?', qux: ['qux'] }, { vanilla: [ - {op: 'replace', path: ['foo','bar',0], value: 'c'}, - {op: 'replace', path: ['foo','bar',2], value: 'a'}, + {op: 'replace', path: ['foo','bar',0], value: C}, + {op: 'replace', path: ['foo','bar',2], value: A}, {op: 'add', path: ['qux', 0], value: 'qux'}, {op: 'replace', path: ['baz'], value: 'baz?'} ], @@ -427,8 +431,7 @@ describe(`change deep arrays`, () => { ); }); -// TODO - cope with mutiple identical objects -describe.skip(`sort with mutiple identical objects`, () => { +describe(`dont optimise with primitive values`, () => { runTest( ['b','a','c','a','c','b','a'], d => { @@ -441,14 +444,58 @@ describe.skip(`sort with mutiple identical objects`, () => { {op: 'replace', path: [0], value: 'a'}, {op: 'replace', path: [2], value: 'b'}, {op: 'replace', path: [3], value: 'b'}, - {op: 'replace', path: [5], value: 'b'}, + {op: 'replace', path: [5], value: 'c'}, {op: 'replace', path: ['length'], value: 6} ], optimised: [ - {op: 'replace', path: ['length'], value: 6}, // bacacb - {op: 'move', from: [1], path: [0]}, // abcacb - {op: 'move', from: [3], path: [1]}, // aabccb - {op: 'move', from: [5], path: [3]} // aabbcc + {op: 'replace', path: [0], value: 'a'}, + {op: 'replace', path: [2], value: 'b'}, + {op: 'replace', path: [3], value: 'b'}, + {op: 'replace', path: [5], value: 'c'}, + {op: 'replace', path: ['length'], value: 6} + ] + } + ); + + runTest( + ['a','b','c','a','a'], + d => { + d.shift(); + }, + ['b','c','a','a'], + { + vanilla: [ + {op: 'replace', path: [0], value: 'b'}, + {op: 'replace', path: [1], value: 'c'}, + {op: 'replace', path: [2], value: 'a'}, + {op: 'replace', path: ['length'], value: 4} + ], + optimised: [ + {op: 'replace', path: [0], value: 'b'}, + {op: 'replace', path: [1], value: 'c'}, + {op: 'replace', path: [2], value: 'a'}, + {op: 'replace', path: ['length'], value: 4} + ] + } + ); +}); + +describe(`deal with multiple identical values`, () => { + runTest( + [A,A,B,A], + d => { + d.shift(); + }, + [A,B,A], + { + vanilla: [ + {op: 'replace', path: [1], value: B}, + {op: 'replace', path: [2], value: A}, + {op: 'replace', path: ['length'], value: 3} + ], + optimised: [ + {op: 'move', from: [2], path: [1]}, + {op: 'replace', path: ['length'], value: 3} ] } ); @@ -456,14 +503,14 @@ describe.skip(`sort with mutiple identical objects`, () => { describe(`filter with returned arrays`, () => { runTest( - ['a','b','c','d','e'], + [A,B,C,D,E], d => { - return d.filter(letter => 'abe'.indexOf(letter) !== -1); + return d.filter(obj => 'abe'.indexOf(obj.id) !== -1); }, - ['a','b','e'], + [A,B,E], { vanilla: [ - {op: 'replace', path: [], value: ['a','b','e']} + {op: 'replace', path: [], value: [A,B,E]} ], optimised: [ {op: 'move', from: [4], path: [2]}, @@ -475,7 +522,7 @@ describe(`filter with returned arrays`, () => { describe(`do nothing special with replacement values of non-array types`, () => { runTest( - ['a','b','c','d','e'], + [A,B,C,D,E], () => 123, 123, { diff --git a/packages/dendriform/README.md b/packages/dendriform/README.md index 1fac113..41e7406 100644 --- a/packages/dendriform/README.md +++ b/packages/dendriform/README.md @@ -430,18 +430,24 @@ See [Array operations](#array-operations) for convenient ways to let the user ma ```js function MyComponent(props) { const form = useDendriform({ - colours: ['Red', 'Green', 'Blue'] + colours: [ + {colour: 'Red'}, + {colour: 'Green'}, + {colour: 'Blue'} + ] }); return
{form.renderAll('colours', colourForm => { - const colour = colourForm.useValue(); + const colour = colourForm.branch('colour').useValue(); return
Colour: {colour}
; })}
; } ``` +Please note that if you are rendering an array that contains any primitive values (undefined, number, string) then the automatic keying will not be able to be as accurate as if you were to render an array of objects / arrays / class instances / sets / maps. This is because object references are used to track the movement of array elements through time, and primitive elements cannot be tracked in this way. + Array element forms can also opt-in to updates regarding their indexes using the `.useIndex()` hook. If you'll be allowing users to re-order items in an array, then please note that you'll get better performance if array element components don't know about their indexes. If the `.useIndex()` hook is used, a element that has moved its position inside of its parent array will need to update, even if it is otherwise unchanged. @@ -1214,7 +1220,11 @@ const dndReorder = (result) => (draft) => { function DragAndDrop() { const form = useDendriform({ - colours: ['Red', 'Green', 'Blue'] + colours: [ + {colour: 'Red'}, + {colour: 'Green'}, + {colour: 'Blue'} + ] }); const onDragEnd = useCallback(result => { @@ -1254,7 +1264,7 @@ function DragAndDropList(props) { {...provided.draggableProps} {...provided.dragHandleProps} > - +
} ; diff --git a/packages/dendriform/test/Dendriform.test.tsx b/packages/dendriform/test/Dendriform.test.tsx index c0b8b87..d614681 100644 --- a/packages/dendriform/test/Dendriform.test.tsx +++ b/packages/dendriform/test/Dendriform.test.tsx @@ -151,7 +151,13 @@ describe(`Dendriform`, () => { describe('.index and .useIndex()', () => { test(`should provide index and produce an update`, () => { - const firstHook = renderHook(() => useDendriform(['a','b','c'])); + const A = {id: 'a'}; + const B = {id: 'b'}; + const C = {id: 'c'}; + const D = {id: 'd'}; + const E = {id: 'e'}; + + const firstHook = renderHook(() => useDendriform([A,B,C])); const form = firstHook.result.current; const elementForm = form.branch(0); @@ -160,7 +166,7 @@ describe(`Dendriform`, () => { act(() => { form.set(draft => { - draft.unshift('x'); + draft.unshift(D); }); }); @@ -170,7 +176,7 @@ describe(`Dendriform`, () => { act(() => { form.set(draft => { - draft.push('y'); + draft.push(E); }); }); @@ -552,7 +558,7 @@ describe(`Dendriform`, () => { const formA = new Dendriform(123, {history: 100}); const formB = new Dendriform(123, {history: 100}); const formC = new Dendriform(123, {history: 100}); - + historySync(formA, formB); formA.set(456); @@ -592,7 +598,7 @@ describe(`Dendriform`, () => { const formD = new Dendriform(123, {history: 100}); const formE = new Dendriform(123, {history: 100}); const formF = new Dendriform(123, {history: 100}); - + historySync(formA, formB); historySync(formC, formD); historySync(formC, formB); @@ -612,14 +618,14 @@ describe(`Dendriform`, () => { const formA = new Dendriform(123, {history: 100}); const formB = new Dendriform(123, {history: 100}); const formC = new Dendriform(123, {history: 200}); - + expect(() => historySync(formA, formB, formC)).toThrow('[Dendriform] All syncHistory() forms must each have a matching non-zero number of history items configured, e.g. {history: 10}'); }); test(`should error if passed zero history sizes`, () => { const formA = new Dendriform(123, {history: 100}); const formB = new Dendriform(123); - + expect(() => historySync(formA, formB)).toThrow('[Dendriform] All syncHistory() forms must each have a matching non-zero number of history items configured, e.g. {history: 10}'); }); @@ -628,7 +634,7 @@ describe(`Dendriform`, () => { const formB = new Dendriform(123, {history: 100}); const formC = new Dendriform(123, {history: 100}); formC.set(456); - + expect(() => historySync(formA, formB, formC)).toThrow('[Dendriform] syncHistory() can only be applied to forms that have not had any changes made yet'); }); @@ -3383,18 +3389,18 @@ describe(`Dendriform`, () => { } test(`should contain value`, () => { - + const value: PluginValue = { foo: true, bar: true }; - + const plugins = { myplugin: new MyPlugin() }; - + const form = new Dendriform(value, {plugins}); - + expect(initMock).toHaveBeenCalledTimes(1); expect(initMock.mock.calls[0][0]).toBe(form); @@ -3404,7 +3410,7 @@ describe(`Dendriform`, () => { expect(pluginResult).toBe('0'); expect(pluginResult2).toBe('1'); expect(form.plugins.myplugin.state.calledTimes).toBe(2); - + }); describe('useDendriform() with plugins', () => { @@ -3413,9 +3419,9 @@ describe(`Dendriform`, () => { const plugins = () => ({ myplugin: new MyPlugin() }); - + const firstHook = renderHook(() => useDendriform(123, {plugins})); - + const form = firstHook.result.current; expect(form.plugins.myplugin instanceof MyPlugin).toBe(true); });