From 6b1a3782ca4acdd3346bfba05008a8e5c856e063 Mon Sep 17 00:00:00 2001 From: joepuzzo Date: Mon, 29 Apr 2024 05:47:26 -0700 Subject: [PATCH] removed react-dom as peer dep --- ARRAY_FIELD_THOUGHTS.md | 146 ++++++++++++++++++++++++++++++++++++++++ CHANGELOG.md | 6 ++ package.json | 4 +- src/ObjectMap.js | 26 ++++++- 4 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 ARRAY_FIELD_THOUGHTS.md diff --git a/ARRAY_FIELD_THOUGHTS.md b/ARRAY_FIELD_THOUGHTS.md new file mode 100644 index 00000000..9dc866cf --- /dev/null +++ b/ARRAY_FIELD_THOUGHTS.md @@ -0,0 +1,146 @@ +# How remove works + +``` + 0, 1, 2, 3, 4, 5, 6, 7 + a, b, c, d, e, f, g, h + +Goal: remove d + +EndState: + + 0, 1, 2, 3, 4, 5, 6, 7 + a, b, c, e, f, g, h + +------------------------------------------------ + +a -> stays +b -> stays +c -> stays +d -> gets removed +e -> moves from 4 to 3 +f -> moves from 5 to 4 +g -> moves from 6 to 5 +h -> moves from 7 to 6 +``` + +Next we need to consider how `informed` works. + +`useField(“fieldName”)` + +Which in the case of array fields is this + +`useField(“arrays[3].fieldName”)` + +**By Design** informed’s `useField` will trigger a `useEffect` anytime a fields name changes + +**why?** because that is how its state is tracked and it must be re-registered under that new name + +```js +// Register +useEffect( + () => { + if (isRelevant) { + // We already initialized before the render so the input exists in the form state, we need to redo after the render + logger('Register', name, metaRef.current); + formController.register(name, metaRef); + logger('Second Initialize', name); + formController.initialize(name, metaRef, false); + } + return () => { + logger('De-Register', name, metaRef.current); + formController.deregister(name); // <<<<<<<<<< THIS IS KEY !!!!!!!!!!!! + }; + }, + [name] +); +``` + +Inside of `FormController` this code is called ( basically tells it to no longer track this field under that name ) + +```js + deregister(name) { + debug('De-Register', name); + if (this.fieldsMap.get(name)) { + this.fieldsMap.delete(name); + this.emit('field', name); + } + } +``` + +In addition to the useEffect that triggers based on the fields `name` we also have a normal `useEffect` with **NO** dependencies + +**What does this mean? When does this trigger?** It means we can have a `cleanup` function that we return from this `useEffect` that will clean up anytime this field gets unmounted + +For normal use case of `un-rendering` a from field the above useEffect is GREAT! It does exactly what we want… + +```js +// Cleanup on un-mount +useEffect(() => { + return () => { + // … some other code + formController.remove(metaInfo.name, metaInfo.keep, metaInfo); // <<<< IMPORTANT LINE ( tells form to remove this field! ) + }; +}, []); +``` + +**HOWEVER!** This is bad when we are performing an array field removal! + +**WHY?** Because informed will try to perform a state removal of `“arrays[3].fieldName”` ( in our example field d ) but we DONT want to remove it from the form state… we want to `PULL IT` out of the array. + +**The Solution** To solve this, we can tell the `FormController` to `lock` any removals that attempt to occur while the re-registration of all fields happens + +In other words, the second the user clicks on the `Remove` for field d ( the field at index 3 ) we can tell the form to block any removals until all the fields have finished `re-registering` + +**How do we know when that is done??** Simple, on user click of remove `d` ( at index 3 ) we simply take note on the length of our array ( in this case a, b, c, d, e, f, g, h is 8 ) + +So we know that e, f, g, and h all need to re-register under new names and that because our array is shrinking by a size of 1 we know that the last field to deregister will be at index 7 + +Therefore, we have code written to detect this located in `useArrayField` and that allows our form to unlock itself to allow future removals` + +``` +e -> moves from 4 to 3 +f -> moves from 5 to 4 +g -> moves from 6 to 5 +h -> moves from 7 to 6 << Detect deregister on 7 and unlock +``` + +## Move + +Now lets consider move(i, j) + +Where i is the index of the field we want to move + +And j is the location we want to move it to + +``` + 0, 1, 2, 3, 4, 5, 6, 7 + a, b, c, d, e, f, g, h + +Goal: move h between d and e + +EndState: + + 0, 1, 2, 3, 4, 5, 6, 7 + a, b, c, d, h, e, f, g + +------------------------------------------------ + +a -> stays +b -> stays +c -> stays +d -> stays +h -> moves from 7 to 4 +e -> moves from 4 to 5 +f -> moves from 5 to 6 +g -> moves from 6 to 7 +``` + + Notice how we have a similar scenario where e, f, g, and h will all be de registering + +... now i need time to think ... + +My initial thinking is we dont need lock because we are not removing anything + +.. its possible that we may only need to create the move at the data structure level + +i.e ObjectMap diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d203c45..37ad8361 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 4.58.1 ( April 29th, 2024) + +### Removed + +- `react-dom` from peer deps as native app will yell at you :) + ## 4.58.2 ( April 10th, 2024) ### Added diff --git a/package.json b/package.json index 1871d222..4ca49ace 100644 --- a/package.json +++ b/package.json @@ -138,7 +138,9 @@ "yup": "^0.28.1" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "optionalDependencies": { "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" }, "nyc": { diff --git a/src/ObjectMap.js b/src/ObjectMap.js index aa341fbd..db9204bc 100644 --- a/src/ObjectMap.js +++ b/src/ObjectMap.js @@ -45,6 +45,19 @@ const ldSwap = (arr, a, b) => { } }; +/* --------------------- move --------------------- */ +const ldmove = (arr, fromIndex, toIndex) => { + if (Array.isArray(arr) && fromIndex < arr.length && toIndex < arr.length) { + const item = arr.splice(fromIndex, 1)[0]; + arr.splice(toIndex, 0, item); + } else { + // Handle invalid operation more gracefully in real scenarios + console.warn( + `Attempted to move from ${fromIndex} to ${toIndex} but the operation is not valid.` + ); + } +}; + /* --------------------- has --------------------- */ // foo --> @@ -285,7 +298,6 @@ export class ObjectMap { return newObj; } - // Very important ;) static swap(object, path, i, j) { // Get the path to the array console.log('Swaping out out:', path, i, j); @@ -297,6 +309,18 @@ export class ObjectMap { ldSwap(arr, i, j); } } + + static move(object, path, fromIndex, toIndex) { + console.log('Moving:', path, fromIndex, 'to', toIndex); + // Get the array at the specified path + const arr = ldget(object, path); + console.log('Array before move', JSON.stringify(arr)); + + // Use ldmove to perform the operation + ldmove(arr, fromIndex, toIndex); + + console.log('Array after move', JSON.stringify(arr)); + } } function cleanup(obj, path) {