Recoil: Atom effect able to get another atom's value

Created on 3 Nov 2020  路  7Comments  路  Source: facebookexperimental/Recoil

I've been looking into atom effects and they look really helpful. In the example, it shows a state synchronization example effect like this:

const syncStorageEffect = userID => ({setSelf, trigger}) => {
  // Subscribe to remote storage changes and update the atom value
  myRemoteStorage.onChange(userID, userInfo => {
    setSelf(userInfo); // Call asynchronously to change value
  });

  // Cleanup remote storage subscription
  return () => {
    myRemoteStorage.onChange(userID, null);
  };
};

That looks perfect, if myRemoteStorage is a static, but how would you structure that same effect when myRemoteStorage is coming from a Recoil value? If the datastore is also tracked in a Recoil atom, then I can't figure out a good pattern for using it in an effect. Was there an approach in mind to handle that pattern?

enhancement

Most helpful comment

@mdlavin - We are exploring the ability for atom effects to look at the state of other atoms, perhaps via providing a mechanism like getSnapshot() as a callback for the atom effect.

All 7 comments

I'm also looking for a way to subscribe to a recoil value in an atom's effect now. Will let you know if I find a way but also pls share your solution if you'll find any

Since an atom doesn't expose a get function, I guess the only way would be to catch a get function elsewhere and store it in a variable outside of Recoil.
Then you'd be able to use it, but it is a bit hacky, and I don't think it's the intended usage.
I'm also not sure the implications it would have on concurrent rendering.

So I found the solution, in my case its not bi-directional but I imagine this could be achieved with the set without a problem. Interesting thing here is you'd expect that using this selector in multiple places would cause multiple fetches but it doesn't, which is perfectly what I wanted :) gotta love recoil
Also the result is memoed and so changing activeUsernameState in recoil to already previously used username also doesn't cause refetch, which is again what I wanted.
Here's the example:

export const suggestedFiltersSelector = selector<SuggestedFilters>({
  key: 'suggestedFiltersSelector',
  get: async ({ get }) => {
    try {
      const { data } = await apolloClient.query<
        IGetSuggestedFiltersQuery,
        IGetSuggestedFiltersQueryVariables
      >({
        query: GetSuggestedFiltersDocument,
        variables: { username: get(activeUsernameState) },
      });

      return data?.getSuggestedFilters.suggestedFilters;
    } catch (error) {
      Logger.error(error);
    }
    return {};
  },
});

Hope that also helps you :)

@mdlavin - We are exploring the ability for atom effects to look at the state of other atoms, perhaps via providing a mechanism like getSnapshot() as a callback for the atom effect.

What about looking at the state of the current atom? Is there a way to find the old/current value in onSet before the new value is committed? Or the initial value in the case that trigger === 'set'? Speaking of which, what is the purpose of the node self reference in the effects? It doesn't seem like I can use it for anything.

The onSet() callback is provided two parameters, the new value and the old value. Though, the API is still in flux and may need to be tweaked to fully support async/error states.

The node allows you to do things like build up a list or map of atoms which can be used by other async callbacks.

I'm coming from Jotai competitor. Looked at how RecoilJS can solve my problems.

It looks like atom affects is what Jotai is. Jotai doesn't have selector entity, only atoms.
Moreover atoms can get and set another atoms and can even get it's own value.

My use case - use atoms as source of truth and update itself as fast as possible, while updating database is considered as side-effect.

export const tagsAtom = atom(
  (_) => {
    const tagRepository = getTagRepository();
    return tagRepository.find();
  },
  (
    get,
    set,
    { id, ...newTag }: QueryDeepPartialEntity<Tag> & { id: string }
  ) => {
    const oldTags = get(tagsAtom);
    const newTags = oldTags.map((oldTag) => {
      if (oldTag.id === id) {
        return {
          ...oldTag,
          ...newTag
        };
      } else {
        return oldTag;
      }
    });

    // @ts-expect-error
    set(tagsAtom, newTags);

    const tagRepository = getTagRepository();
    tagRepository.update({ id }, newTag);
  }
);

how this can be achieved using RecoilJS? I've read all documentation and still can't figure that out.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

aappddeevv picture aappddeevv  路  3Comments

julienJean99 picture julienJean99  路  3Comments

ibnumusyaffa picture ibnumusyaffa  路  4Comments

eLeontev picture eLeontev  路  3Comments

pesterhazy picture pesterhazy  路  4Comments