pmndrs/suspend-react

Re-rendering on clear

Opened this issue · 2 comments

Love the simplicity of the API, I'm in the process of converting all my hand rolled suspense helpers over to this!

One piece of functionality I'd love to have in the library or at least your opinion on how to implement it separately best is forcing a re-render when the cache is cleared/updated.

I just did something similar yesterday, I am implementing a refresh feature and want to trigger a re-render when the refresh is done. Here is the solution I came up with:

// things.hooks.ts
const useRefreshing = () => useApp(R.prop('refreshing'))

export const useMyThings = (): ThinThing[] => {
  const fetchThings = useFetchThings()
  useRefreshing() // This causes this hook to execute and thereby rerender any time refreshing changes
  return suspend(fetchThings, fetchThingsKey)
}

export const useRefreshThings = (): AppState['refreshThings'] =>
  useApp(R.prop('refreshThings'))



// things.store.ts
// This is part of a Zustand store, another wonderful pmndrs project :)
export const createThingsSlice: StateSlice<ThingsSlice> = (set, get) => {
  ...

  const clearThings = () => clear(fetchThingsKey)

  const refreshThings = async () =>
    new Promise<void>(async (res) => {
      set({ refreshing: true })
      await get().refreshUser()
      clearThings()
      preload(
        () =>
          fetchThings().then((things) => {
            res()
            set({ refreshing: false })
            return things
          }),
        fetchThingsKey
      )
    })

  return {
    refreshing: false,
    fetchThings,
    refreshThings,
    clearThings,
  }
}

This approach doesn't quite accomplish what I want for refreshing (Suspense briefly renders the fallback), but it works great for re-rendering on clear. You could easily replace the store with useState, I have other reasons for using stores. The refreshThings function is a little ugly because I want to be able to await a call to refreshThings and have it resolve when the new data are available.

A quick update... You can avoid the Suspend fallbacks flashing by changing set({ refreshing: false }) to setTimeout(() => {set({ refreshing: false })}, 0)

The flashing of the Suspend fallbacks occurs because calling set({refreshing: false}) before the promise returned by fn is resolved never gives suspend-react a chance to cache what the promise resolves to before set({refreshing: false}) queues a re-render with stale data. Then-ing a promise puts a job in the job queue (which is executed immediately) whereas setTimeoutputs a task in the task queue, which is not evaluated until the job queue is empty. So because query caches the result of the promise returned by fn in a then call, if the promise returned by fn is resolved before suspend is called again, the fresh data will be immediately available, and suspend will never throw a promise and causing a suspension. Using setTimeout to make the call to set({refreshing: false}) a task instead of a job will result in query finishing its execution before set({refreshing: false}) is called, so when a re-render is triggered, the fresh data have already been cached. At least, that's my best guess as to what's going on!