Recoil: Pagination Pattern & Suspense fallback

Created on 14 Nov 2020  路  9Comments  路  Source: facebookexperimental/Recoil

Please take a look at the sandbox: https://codesandbox.io/s/hardcore-thunder-vgbvt?file=/src/Screen.tsx

Purpose:

  • When you click the Load more button, the list will be appended with new items.
  • When you click the Reset button, the list will be re-initialized.

Explanation:

  • Setting the "last" local state will trigger the "chunk" recoil state to fetch data starting from it.
  • Setting the "last" local state value to "init" will clean up "list" state and fetch data from 0.
  • The Chunk selectorFamily fetches 3 items, starting from the "last" params. I use the selectorFamily here to cache data.
  • The "list" state stores chunks to render a list component. If there is a new chunk, it is appended to the list state.

Questions:

  1. I'm not sure this pattern is the right one. But it works for me, although it's quite weird and cumbersome. Do you have any better ways?
  2. The Problem is the Suspense mechanism itself shows the "loading" fallback every time there is a new chunk. Maybe the React Concurrent mode will help. Do you know how to avoid the fallback flashing?
question

Most helpful comment

See also https://github.com/facebookexperimental/Recoil/issues/290 for a previous discussion on the subject.

All 9 comments

I think I'd keep all the data in an atom, and then have the loading of new data not be Recoil but instead an async function, and then set the new state when the data is fetched. That way you always have some state in your atom, and the page won't flash white when loading.

I think I'd keep all the data in an atom, and then have the loading of new data not be Recoil but instead an async function, and then set the new state when the data is fetched. That way you always have some state in your atom, and the page won't flash white when loading.

Yes, I know. It's the traditional way. I'm trying to implement things in the Recoil way and Suspense.

I have adjusted a bit. I made the local "last" state as an atom and register it as a dependency in the "Chunk". Chunk becomes a selector instead of selectorFamily. "list" stay local state, it can be an atom of course. But I prefer keeping it local.

In that case, I'd probably have two atoms, one for the actual state, and one for the previous state. You can register an atom effect in the main atom, that sets the old value in the other atom each time it's chabged.

Then in the React component using the data, you can use useRecoilValueLoadable to get a loadable for the main atom. Then if the loadable state is 'loading' you can use the old data, and then when the new arrives use that instead. You could even extract that part into a hook.

Sorry for formatting/spelling mistakes, typing this on mobile.

See also https://github.com/facebookexperimental/Recoil/issues/290 for a previous discussion on the subject.

Depending on the behaviour you want, there are different approaches to take. If you want Suspense to be pending while incrementally loading then you can use a selector as your example to represent the loading. You may want to use a selector to actually combine the chunks as it seems like the example effect only appends regardless of the index, though it's probably just a simple example. If you don't want Suspense to suspend the <DataList> component, then, as @BenjaBobs suggests, you could use an atom to represent the current list state and asynchronously adjust it as chunks are loaded. If you want to show some indication that the appending to the list is pending, then you could add some additional spinner based on the async state of the additional fetch. Another alternative @BenjaBobs refers to in #290 would be to use a selector to merge the chunks, but then implement an abstraction or experiment with the new useTransition() React Concurrent Mode hook to render the previous state while the new state is pending.

In that case, I'd probably have two atoms, one for the actual state, and one for the previous state. You can register an atom effect in the main atom, that sets the old value in the other atom each time it's chabged.

Then in the React component using the data, you can use useRecoilValueLoadable to get a loadable for the main atom. Then if the loadable state is 'loading' you can use the old data, and then when the new arrives use that instead. You could even extract that part into a hook.

Sorry for formatting/spelling mistakes, typing this on mobile.

Nice solution. Thank you!

Depending on the behaviour you want, there are different approaches to take. If you want Suspense to be pending while incrementally loading then you can use a selector as your example to represent the loading. You may want to use a selector to actually combine the chunks as it seems like the example effect only appends regardless of the index, though it's probably just a simple example. If you don't want Suspense to suspend the <DataList> component, then, as @BenjaBobs suggests, you could use an atom to represent the current list state and asynchronously adjust it as chunks are loaded. If you want to show some indication that the appending to the list is pending, then you could add some additional spinner based on the async state of the additional fetch. Another alternative @BenjaBobs refers to in #290 would be to use a selector to merge the chunks, but then implement an abstraction or experiment with the new useTransition() React Concurrent Mode hook to render the previous state while the new state is pending.

Actually, I'm trying to implement an endless scroll. I want to get more data and append to the list when it reaches the end, seamlessly, without flashing or loading. @BenjaBobs suggestion is nice. I'm looking into #290. But I think React Concurrent Mode is interesting too.

Instead of an atom synced for previous state, also consider a usePrevious() type of hook (there are a few implementations out there) for rendering based on previous state.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Sawtaytoes picture Sawtaytoes  路  4Comments

adamkleingit picture adamkleingit  路  4Comments

adrianbw picture adrianbw  路  3Comments

ymolists picture ymolists  路  3Comments

thegauravthakur picture thegauravthakur  路  3Comments