Recoil: Missing selector async example in the API docs

Created on 15 May 2020  路  14Comments  路  Source: facebookexperimental/Recoil

I like where this library is going. :) I'm really interested to see the preferred method for dealing with async calls (fetch, etc.). Is there anywhere I can see an example while waiting for the docs to be updated?

Most helpful comment

Ah, if you're just looking for a boolean "is this query complete or not" then you don't need a separate atom for that. Recoil is designed to work with "Suspense" in React for blending asynchronous dependencies with synchronous React rendering. So, all you need to do is this:

const userDataQuery = selectorFamily({
  key: 'UserDataQuery',
  get: userID => async ({get}) => {
    const response = await asyncQueryForUserData(userID);
    const data = we can transform the response here
    return data;
  },
});

function UserName({userID}) {
  const userData = useRecoilValue(userDataQuery(userID));
  return <div>{userData.name}</div>;
}

function MyApp() {
  return (
    <React.Suspense fallback={<div>Loading...</div>}>
      <UserName userID={123} />
    </React.Suspense>
  );
}

You don't need to use Suspense, though. You can also get the loaded/not-loaded status of an asynchronous dependency yourself:

const userDataQuery = selectorFamily({
  key: 'UserDataQuery',
  get: userID => async ({get}) => {
    const response = await asyncQueryForUserData(userID);
    const data = we can transform the response here
    return data;
  },
});

function UserName({userID}) {
  const userDataLoadable = useRecoilValueLoadable(userDataQuery(userID));
  return userDataLoadable.status === 'hasValue'
    ? <div>{userDataLoadable.contents.name}</div>
    : 'Loading...';
}

All 14 comments

Basically, the get property of a selector can either return the value for the selector or a Promise of the value.

Here's a quick example for using a selector as an async query in the Recoil data flow graph:

const userDataQuery = selector({
  key: 'UserDataQuery',
  get: async ({get}) => {
    const userID = get(userIDAtom);  // We might store current user as Atom state
    const response = await asyncQueryForUserData(userID);
    const data = we can transform the response here if needed
    return data;
  },
});

function UserName() {
  const userData = useRecoilValue(userDataQuery);
  return <div>{userData.name}</div>;
}

And here's an example using the selectorFamily helper to model an async query with parameters:

const userDataQuery = selectorFamily({
  key: 'UserDataQuery',
  get: userID => async ({get}) => {
    const response = await asyncQueryForUserData(userID);
    const data = we can transform the response here
    return data;
  },
});

function UserName({userID}) {
  const userData = useRecoilValue(userDataQuery(userID));
  return <div>{userData.name}</div>;
}

Any abstraction for fetching status? Is it may be implemented via selector set?

set is for writing to the selector, not really about out-of-band communication. For example, if you want to change upstream selector/atom values. Modeling query status would really depend on how your query exposes it. But, one thing to keep in mind is that selector functions are intended to be "pure". That is, for a given set of inputs and dependency values it should always produce the same output. Internally the implementation caches the value. So, don't use selectors around a query like this if you expect the response to change for a given set of inputs.

I think one way is parameterize userDataQuery getter with asyncQueryForUserData function that setting separate status atom with appropriate value.

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

const userDataQuery = selectorFamily({
    key: 'UserDataQuery',
    get: (userID, asyncQueryForUserData) => async ({ get }) => {
        const response = await asyncQueryForUserData(userID);
        const data = response
        return data;
    },
});

function UserName({ userID }) {
    const [fetching, setFetching] = React.useRecoilState(statusAtom);
    const fetchData = React.useCallback(async (userID) => {
        try {
            const data = await getData(userID);
            setFetching(true);
            return data;
        } finally {
            setFetching(false);
        }
    }, []);
    const userData = useRecoilValue(userDataQuery(userID, fetchData));

    if (fetching) return "...fetching";

    return <div>{userData.name}</div>;
}

Ah, if you're just looking for a boolean "is this query complete or not" then you don't need a separate atom for that. Recoil is designed to work with "Suspense" in React for blending asynchronous dependencies with synchronous React rendering. So, all you need to do is this:

const userDataQuery = selectorFamily({
  key: 'UserDataQuery',
  get: userID => async ({get}) => {
    const response = await asyncQueryForUserData(userID);
    const data = we can transform the response here
    return data;
  },
});

function UserName({userID}) {
  const userData = useRecoilValue(userDataQuery(userID));
  return <div>{userData.name}</div>;
}

function MyApp() {
  return (
    <React.Suspense fallback={<div>Loading...</div>}>
      <UserName userID={123} />
    </React.Suspense>
  );
}

You don't need to use Suspense, though. You can also get the loaded/not-loaded status of an asynchronous dependency yourself:

const userDataQuery = selectorFamily({
  key: 'UserDataQuery',
  get: userID => async ({get}) => {
    const response = await asyncQueryForUserData(userID);
    const data = we can transform the response here
    return data;
  },
});

function UserName({userID}) {
  const userDataLoadable = useRecoilValueLoadable(userDataQuery(userID));
  return userDataLoadable.status === 'hasValue'
    ? <div>{userDataLoadable.contents.name}</div>
    : 'Loading...';
}

@drarmstr looks like it鈥檚 a common scenario, would you mind putting it in the docs somewhere?

@drarmstr looks like it鈥檚 a common scenario, would you mind putting it in the docs somewhere?

Yup, that's the plan!

Cool. And what about complex side effect handling like redux-saga does?

Can you provide an example side-effect you're interested in?

Basically, the get property of a selector can either return the value for the selector or a Promise of the value.

Here's a quick example for using a selector as an async query in the Recoil data flow graph:

const userDataQuery = selector({
  key: 'UserDataQuery',
  get: async ({get}) => {
    const userID = get(userIDAtom);  // We might store current user as Atom state
    const response = await asyncQueryForUserData(userID);
    const data = we can transform the response here if needed
    return data;
  },
});

function UserName() {
  const userData = useRecoilValue(userDataQuery);
  return <div>{userData.name}</div>;
}

How about updating the data? What could be our strategy?
Awesome job, by the way, thank you for this great library.

@drarmstr

Can you provide an example side-effect you're interested in?

Latest only task handling like with https://redux-saga.js.org/docs/api/#takelatestpattern-saga-args for example.

Hey,

I'm also interested in an example for a POST request. Like I have an input and a button and on the button click the app sends a post query with input's text in body to /my_api/new_post. As response the api returns total posts count that I'd like to store. Should I use selector.set for this case?

@mbelsky - I think it's interesting to explore using writeable selectors to abstract a RESTful API. Though, keep in mind that there is a convention that selector evaluation functions should be pure. The results may be cached based on a unique set of input and dependency values. Though, we are exploring how to adjust that policy.

Another approach to consider if you want to use a REST API as a backing-store for state is to use atoms to represent the state locally. You could query their initial value when setting up the initial Recoil state snapshot and then use the Recoil state transaction observer hook to monitor for changes and update the server accordingly. You could also setup another mechanism to get updates to the values from the server which could change the local atom state. I don't think we've documented the observer API yet as we're still planning to polish it, though we currently use it for atom persistence.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pesterhazy picture pesterhazy  路  4Comments

julienJean99 picture julienJean99  路  3Comments

karevn picture karevn  路  3Comments

ymolists picture ymolists  路  3Comments

thegauravthakur picture thegauravthakur  路  3Comments