Recoil: Not possible to restore atoms created with atomFamily

Created on 7 Jun 2020  路  9Comments  路  Source: facebookexperimental/Recoil

given the atomFamily:

const testItem = atomFamily<number>({
  key: 'testItem',
  default: null
});

creating an item with set(testItem("myId"), 123) triggers a call to

 useTransactionObservation_UNSTABLE({atomValues, atomInfo, modifiedAtoms} => {
      const list = [];
      for (const modifiedAtom of modifiedAtoms) {
        list.push({name: modifiedAtom, value: JSON.stringify(atomValues.get(modifiedAtom))});
      }
    saveStore(list)
 });

with the key as testItem__"myId". When trying to restore that with

set({key: item.name}, JSON.parse(item.value));

it fails with error Missing definition for RecoilValue: "testItem__"myId""

Also atomValues.get(modifiedAtom) always return nothing as atomValues is always empty.

Also there is an error in the documentation that says we should use modifiedAtom.key. modifiedAtom is a string though and therefore does not have key property.

Most helpful comment

Not yet, we had some pandemic-related delays, but still working on the persistence library.

All 9 comments

Do not manually construct objects with a key property. All nodes must be created using the Recoil API. Try using set(testItem('myId'), JSON.parse(item.value)).

I realize the docs gave an example that does that, I'll change it. Please keep in mind this persistence API is changing shortly and I expect most of these issues to be cleaned up, it should be a lot easier to use.

OK, but it I am restoring it from a persistence source, how will I know which object to construct? Should I keep a map of atoms and then parse the key to get the atom name and the ID? Is that getAtomWithKey my own method to find that atom?

This is why we hesitated a bit to publish the temporary persistence API.. Yes, you can keep a map of atoms, though that is tricky with atomFamilies created dynamically. Internally, we actually used setUnvalidatedAtomValues(), which takes a key string. But, that's going away and requires extra metadata in the atoms. I'm really excited about the new API and helper library in the works.

Hi @drarmstr ,

Any updates about this feature / modification ? waiting for the api / documentation to be updated.

This is only the last part missing before switching to recoil. Almost there, so impatient! 馃憤
anyway thx, and keep it this way :)

Not yet, we had some pandemic-related delays, but still working on the persistence library.

Currently blocked by this. With the current API, the UI entirely locks because i have expensive recursive selectors being read on every key stroke. Need a way to cheaply know the key and value of the change. Since I know my keys ahead of time, can keep the transaction observer dumb, and move recursive code to just initializeState

for example...

export function PersistenceObserver() {
  useRecoilTransactionObserver_UNSTABLE(
    async ({ previousSnapshot, snapshot }: any) => {
      // deal with the atoms by hand
      const prevForm = await previousSnapshot.getPromise(updatedFormMapState);
      const form = await snapshot.getPromise(updatedFormMapState);
      if (!isEqual(prevForm, form)) {
         sessionStorage.setItem('@formium.form', JSON.stringify(value));
      }
    }
  );
  return null;
}

Since updatedFormMapState recurses through a bunch of atomFamily's, it's expensive. too expensive to run on each key stroke.

So, let's walk through to see the best way to solve what you're trying to do. Do you need every key stroke to update a Recoil atom? Maybe you do want then if you want other components to dynamically update as the user types.. If you always want state to ensure it persists with every state change, then that is why you are trying to persist with each key stroke? Or, is it the case that updatedFormMapState may only update sometimes, and not necessarily with each key stroke? Using the current unstable API to only persist dirty atoms may not help here, then, because you're looking at trying to persist derived state instead of atoms. I'm not sure what your mechanism of rehydrating atom state from the derived state is? Anyway, in this case, to only persist derived updatedFormMapState changes when that derived state actually changes and not when other unrelated atoms change, how about:

function FormStatePersister() {
  const form = useRecoilValue(updatedFormMapState);
  const prevForm = usePrevious(form);
  useEffect(() => {
    if (!isEqual(prevForm, form)) {
      sessionStorage.setItem('@formium.form', JSON.stringify(form));
    }
  }, [form, prevForm]);
}

with the key as testItem__"myId". When trying to restore that with

possible workaround: inside myLookupOfAtomWithKey function of state persistence part, simply get rid of "s surrounding the atomFamily parameter.

const splitKey = key.split('__');
const [lookupKey] = splitKey;

switch (key) {
  ...
  case 'testItem': {
    // splitKey[1] = "myId"
    const atomFamilyParam = splitKey[1]?.replace(/"/g, "");
    return testItemState(atomFamilyParam)
  }
  ...
}

works for me! 馃憤

Was this page helpful?
0 / 5 - 0 ratings

Related issues

tklepzig picture tklepzig  路  3Comments

ibnumusyaffa picture ibnumusyaffa  路  4Comments

adamkleingit picture adamkleingit  路  4Comments

Sawtaytoes picture Sawtaytoes  路  4Comments

julienJean99 picture julienJean99  路  3Comments