facebookexperimental / Recoil

Recoil is an experimental state management library for React apps. It provides several capabilities that are difficult to achieve with React alone, while being compatible with the newest features of React.
https://recoiljs.org/
MIT License
19.61k stars 1.18k forks source link

Setting other atoms in an Atom Effect? #1993

Open JavierM42 opened 2 years ago

JavierM42 commented 2 years ago

Is there a reason for the Atom Effect parameters not including set?

My use case is I'd like to set another atom to update UI with the state of local updates.

Here's a very simple example:

const theEffect = ({ onSet }) => {
  onSet((newValue) => {

    // Here I'd like to set another atom that keeps track that this value was updated locally.
    // set(someOtherAtom, someValue);

    SomeExternalStorage.update(newValue);
  })
}
tpatalas commented 2 years ago

https://github.com/facebookexperimental/Recoil/discussions/1785

I personally ended up using React Effect or useRecoilCallback with atomEffect. Not so intuitive way though.

olefrank commented 1 year ago

1785

I personally ended up using React Effect or useRecoilCallback with atomEffect. Not so intuitive way though.

Hi tpAtalas

Could you please elaborate on your answer? I mean, how did you update state B as an effect of updating state A? You mention

using React Effect or useRecoilCallback with atomEffect

I'm updating the state inside a useRecoilCallback but I don't see the possibility to attach an effect to it...

Would you be so kind to share a code example?

tpatalas commented 1 year ago

Hm maybe I would do something like this:

const atomOffline = atom({
  key: 'atomWithAtomEffect',
  default: false,
  effects: [offlineEffect],
});

const atomAnother = atom({
  key: 'atomAntoher',
  default: false,
});

// Object: set atomAnother 'true' whenever atomOffline is set to true (becoming offline), triggered by offlineEffect.

// 1. Create effect component (or hook)
export const SyncAtomsEffects = () => {
  // const offlineAtom = useRecoilValue(atomOffline)
  // You need to use RecoilValue if you have not rendered this atom in any component.
  // However, getting another atom within useRecoilcallback is always better since it will not trigger any re-rendering while you get the value.
  const syncAtoms = useRecoilCallback(({ snapshot, set, reset }) => () => {
    const get = <T,>(p: RecoilValue<T>) => snapshot.getLoadable(p).getValue();

    if (get(atomOffline)) return set(atomAnother, true);
    return reset(atomAnother);
  });

  useEffect(() => {
    syncAtoms();
  }, [syncAtoms]);

  return null;
};

// 2. Insert SyncAtomEffect into another component, where you wanna trigger the sync
export const IamAComponent = () => {
  return (
    <>
      <SyncAtomEffects />
      <Layout>
        <ComponentExampleA>
          <ComponentExampleB />
        </ComponentExampleA>
      </Layout>
    </>
  );
};

I think the best part is this effect component will not trigger any re-rendering of the component

kungfoolabs commented 11 months ago

@tpatalas Thanks for this. I tried this out and while it works, if we are syncing 1 to many atoms - it breaks. For example, pull an atom/selector by list and reindex by id, then this will break in production. During test mode it works fine, but because it sets many atoms, this will cause a ton a refreshes/rerendering.

There has to be a better way to handle this... unfortunately this is a huge barrier since either i have to rewrite entire atom structure for list vs by id or have to switch to another library such as jotai