swr icon indicating copy to clipboard operation
swr copied to clipboard

Avoiding waterfalls with Suspense

Open pupudu opened this issue 4 years ago • 5 comments

Side note: I am writing this with my jaw on the floor. What a clean API. Nice work guys. 😍

This issue refers to the idea of waterfalls as mentioned in the react docs. https://reactjs.org/docs/concurrent-mode-suspense.html#for-library-authors

From a glance, it appears that the suspense based API does not take the above optimization into consideration. If I am not mistaken, to avoid waterfalls, we need to declare the API calls first(which will init the fetch calls), and then read the data from another call (read method in relay), which will throw the promise.

Is this already done in the library? If not, do you guys plan to do it in the future? How would the API look like?


PS: In my head, the API should look something like below

function MyComponent(props) {
    // First we declare the list of fetch calls. Here we use an object. Could be an array instead.
    // Doing this will NOT trigger suspense. i.e. A promise shall not be thrown.
    // But the API calls will be triggered in parallel  
    const fetcher = useSuspenseSwr({
         key1: {/*fetch config for key 1*/},
         key2: {/*fetch config for key 2*/},
    });

    // and then call fetcher.fetch('key1') somewhere in the code
    // This is when the promise will be thrown
}

pupudu avatar Oct 28 '19 21:10 pupudu

I was wondering about this as well.

samcx avatar Nov 12 '19 07:11 samcx

You could use mutate to fill the SWR cache with data before rendering if you now what you will need.

const data = await fetcher("/api/resource/1");
mutate("/api/resource/1", data, false); // the false is to avoid a revalidation in SWR

You could do this multiple times before rendering your component, now when a component using SWR with suspense enabled is rendered it will read from the pre-filled cache, if it's already there the component will render immediately without showing the Suspense fallback.

sergiodxa avatar Nov 12 '19 15:11 sergiodxa

Hi all! Happy to hear about your feedback on #168 🙏

shuding avatar Dec 02 '19 18:12 shuding

I'm working on my own project to simply avoid the waterfalls in this way. If it helps.

// Component.tsx
import { useSWR } from './WrappedSwr'

export const Component = () => {
  const resource1 = useSWR<YourResponseType1>('/path1', fetcher) // fetch1
  const resource2 = useSWR<YourResponseType2>('/path2', fetcher) // fetch2
  const data1 = resource1.read() // throw proimse
  const data2 = resource2.read() // throw proimse

  // or you can pass the resource to a child component

  // return (
  //   <Suspense>
  //     <Child resource={resource1} />  // props.resource.read() in Child Component
  //   </Suspense>
  // )

  // ...
// WrappedSwr.ts
import useRawSWR, { mutate, responseInterface } from 'swr'

const useSWR = <Data>(
  key: string,
  fetcher: (args: any) => Data | Promise<Data>,
): Resource<Data> => {
  try {
    const res = useRawSWR<Data>(key, fetcher)
    // If the data is in swr's cache
    return { read: () => res.data as Data }
  } catch (promiseOrError) {
    if (typeof promiseOrError.then === 'function') {
      return wrapPromise<responseInterface<Data, Error>>(promiseOrError)
    }
    throw promiseOrError
  }
}

// from: https://codesandbox.io/s/frosty-hermann-bztrp?file=/src/fakeApi.js
function wrapPromise<T extends { data?: any }>(promise: Promise<T>) {
  let status: 'success' | 'pending' | 'error' = 'pending'
  let result: T | Error
  const suspender = promise.then(
    (r) => {
      status = 'success'
      result = r
    },
    (e) => {
      status = 'error'
      result = e
    },
  )
  return {
    read() {
      if (status === 'pending') {
        throw suspender
      } else if (status === 'error') {
        throw result
      } else if (status === 'success') {
        return (result as T).data
      }
      throw new Error('unknown status in wrapPromise')
    },
  }
}

export { useSWR, mutate }

yoshiko-pg avatar Jul 02 '20 06:07 yoshiko-pg

Here's my solve: https://medium.com/@tamis.mike/useswr-parallel-suspense-b41929a01098

miketamis avatar Jun 23 '22 21:06 miketamis