reduxjs / redux-toolkit

The official, opinionated, batteries-included toolset for efficient Redux development
https://redux-toolkit.js.org
MIT License
10.55k stars 1.13k forks source link

Support for Suspense in RTK Query #1574

Open sazzer opened 2 years ago

sazzer commented 2 years ago

Redux Toolkit and RTK Query are awesome, and have really made it easier to get things working quickly and well.

What would be really good though is to have support for the React Suspense API, so that we can render a fallback component whilst data is loading without needing to handle it in-component with the isLoading flag.

It's not a huge deal, but it just makes things that little bit neater to have a wrapper around the component that handles this instead.

Cheers

phryneas commented 2 years ago

RTK 1.7 will have the necessary internal changes for that. (see #1277) After that we'll probably release an external package marked as "unstable" with a module for suspense hooks.

sazzer commented 2 years ago

Awesome :)

Is there anything that documents upcoming changes? I did a search for "Suspense" in the issues but nothing that looked relevant came up.

phryneas commented 2 years ago

Not really, but you will need the new api.getRunningQueryPromise function as a base foundation.

AndrewCraswell commented 2 years ago

Looks like 1.7 released last month, and the suspense is killing me ;)

phryneas commented 2 years ago

All the building blocks for it are there now, so you could build your own hooks or RTK Query plugin for it at this time.

But I don't have the capacity to build anything at the moment. If you want this functionality in the near future, it probably has to come from the community.

laurencefass commented 2 years ago

Moving loading states out of components into Suspense looks like the way forward and there seems to be a lot of work going in other frameworks to accomodate. Will there be any first-class Suspense support in RTK Query in the future?

phryneas commented 2 years ago

@laurencefass yes, for example in the PR #2245 directly above your answer. The point is though, that Suspense has not officially been declared as "ready for use with client-side libraries" by the React team yet, but only as "ready for use if you use an opinionated framework like next if that supports it".

luke10x commented 2 years ago

I ended up writing my own quick-and-dirty hook using api.getRunningQueryPromise as @phryneas mentioned in previous comments:

// Simplified version of QueryActionCreatorResult
interface WrappedData<T extends unknown> {
  data: T
}

export const useRtkQueryResource = <T extends unknown>(api: unknown, endpointName: string): never | (() => T) => {
  // @ts-ignore
  const apiEndpointQuery = api.endpoints[endpointName]
  // @ts-ignore
  const useEndpointQuery = apiEndpointQuery?.useQuery

  const { data } = useEndpointQuery(null) as WrappedData<T>

  // @ts-ignore
  let promise = api
    // @ts-ignore
    .util
    .getRunningOperationPromise(endpointName, null) as PromiseLike<WrappedData<T>>|undefined

  // Promise is undefined when data is there cached locally already,
  // in this case let's have a promise resolved with the locally cached data
  if (promise === undefined) {
    promise = new Promise(
      (resolve, reject)=> {
        if (data !== undefined) {
          resolve({ data })
        } else {
          reject("Cannot get RTK Query promise and there is no data loaded yet")
        }
      }
    )
  }

  let status = 'pending'
  let response: T

  promise.then(
    (res: WrappedData<T>) => {
      status = 'success'
      response = res.data
    },
    (err) => {
      status = 'error'
      response = err
    },
  )

  return () => {
    switch (status) {
      case 'pending':
        throw promise
      case 'error':
        throw response
      default:
        return response
    }
  }
}
markerikson commented 2 years ago

For the record, I just built a proof of concept "Suspense Cache" using RTK Query. Not final yet, but you can see the PR here:

https://github.com/replayio/devtools/pull/7205

This lets you do:

function MyComponent() {
  const data = getDataFromSuspenseCache()

  // render here
}

while encapsulating the whole "throw a promise or return the data" thing.

(fixed the wrong PR link)

markerikson commented 1 year ago

Relevant thoughts over in the React Query repo:

https://github.com/TanStack/query/discussions/4623

Also probably outdated discussion on Twitter:

https://twitter.com/arnbzn/status/1435529763893006336