Recoil: is there a difference between atomFamily default and selectorFamily get?

Created on 1 Jun 2020  路  11Comments  路  Source: facebookexperimental/Recoil

Was a bit confused by this two approaches.

const userInfoQuery = atomFamily({
    key: 'UserInfoQuery',
    default: async userID => {
        const response = await myDBQuery({userID});
        return response;
  });

and

const userInfoQuery = selectorFamily({
    key: 'UserInfoQuery',
    get: userID => async () => {
        const response = await myDBQuery({userID});
        return response;
    },
});

They both will be recomputed if we pass new userID.
So if we don't need get or set then we can use atomFamily?

question

Most helpful comment

Both expose the same interface and both may be read/writeable (if the selector provides a set).

Atoms represent and maintain state while selectors represent functions. Use atoms when you need state and a selector when it is derived from other state or async requests.

In reality, atoms with asynchronous defaults are actually implemented by wrapping with a selector. It's provided as a convenience. So, to your original question, the atomFamily would actually construct selectors to get the async data as well as atoms with state that then go unused if you never set the atom.

All 11 comments

Assuming they are used like this

const Comp = () => {
  const val = useRecoilValue(userInfoQuery(userId))
  return val
}

Hi @alexandrzavalii

One difference between Atom::default and Selector::get is that the 'default' will only run once, but the Selector's 'get' will run again if it accesses other RecoilValues. For example:

const userInfoQuery = selectorFamily({
    key: 'UserInfoQuery',
    get: userID => async ({get}) => {
        const pageToLoad = get(pageIndex); // changes to pageIndex will trigger a re-run
        const response = await myDBQuery({userID, page: pageToLoad});
        return response;
    },
});

@acutmore thanks for your reply!
I wonder if there is any difference in the code that I wrote in the question. In my example selector does not use get.
In atomFamily, looks like default will be called again if the userID changes.

@acutmore thanks for your reply!
I wonder if there is any difference in the code that I wrote in the question. In my example selector does not use 'get'.
In atomFamily, looks like default will be called again if the userID changes.

In your example your selector didn't have a get inside your get function so no subscriptions were made that would trigger the re-runs.

@acutmore wouldn't replacing get key with subscribe be more accurate?

const userInfoQuery = selectorFamily({
    key: 'UserInfoQuery',
    subscribe: userID => async ({get}) => {
        const pageToLoad = get(pageIndex); // changes to pageIndex will trigger a re-run
        const response = await myDBQuery({userID, page: pageToLoad});
        return response;
    },
});

I wonder if there is any difference in the code that I wrote in the question. In my example selector does not use 'get'.

Understood. I misread your original post, I thought you were asking if there was a difference between AtomFamily and SelectorFamily.

A difference in the behaviour for your code will be that atomFamily returns a ReadWrite RecoilValue, whereas the selectorFamily will return a read-only RecoilValue.

const af = atomFamily({ ... });
const sf = selectorFamily({ ... });

useRecoilValue(af(param)); // safe
useRecoilValue(sf(param)); // safe
useRecoilState(af(param)); // safe
useRecoilState(sf(param)); // throws exception

@acutmore Okay so if I understand you correctly, if I use useRecoilValue, then there is no difference if I use selectorFamily or atomFamily

const Comp = () => {
  const val = useRecoilValue(userInfoQuery(userId))
  return val
}

Seems a bit misleading to me that you can achieve exactly the same thing, with quiet different api's

Hi @alexandrzavalii

Another difference is that the current useTransactionObservation_UNSTABLE hook only provides access to atoms - this may change in the future but it is an example of how Recoil could treat Atoms and Selectors differently. Over time more differences may appear between the two. For example a Recoil DevTools may display Atoms and Selectors differently.

Hope that helps.

yes, it does! Thanks for the info!
haven't looked at useTransactionObservation yet

Great! As the docs say "[useTransactionObserver] API is currently under development and will change." So not something to use right now, but just an email of another difference :).

Both expose the same interface and both may be read/writeable (if the selector provides a set).

Atoms represent and maintain state while selectors represent functions. Use atoms when you need state and a selector when it is derived from other state or async requests.

In reality, atoms with asynchronous defaults are actually implemented by wrapping with a selector. It's provided as a convenience. So, to your original question, the atomFamily would actually construct selectors to get the async data as well as atoms with state that then go unused if you never set the atom.

@drarmstr hmm interesting.
In the docs we have this selector

const userNameQuery = selectorFamily({
  key: 'UserName',
  get: (userID) => async ({get}) => {
    const response = await myDBQuery({userID});
    if (response.error) {
      throw response.error;
    }
    return response.name;
  },
});

It was missleading to see the get there even though it is not used.
Would it be okay to remove it?
Have this pr here: https://github.com/facebookexperimental/Recoil/pull/234

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ibnumusyaffa picture ibnumusyaffa  路  4Comments

karevn picture karevn  路  3Comments

Sawtaytoes picture Sawtaytoes  路  4Comments

ymolists picture ymolists  路  3Comments

robsoncezario picture robsoncezario  路  3Comments