Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Missing an equivalent of React Query's useQueries hook #1041

Closed
gfox1984 opened this issue Mar 16, 2021 · 14 comments
Closed

Missing an equivalent of React Query's useQueries hook #1041

gfox1984 opened this issue Mar 16, 2021 · 14 comments
Labels
feature request New feature or request

Comments

@gfox1984
Copy link

There does not seem to be an equivalent of the React Query's useQueries hook in SWR.

Being able to run a variable number of queries in parallel using SWR and yet benefit from caching per individual items would be a big enabler. I know that I can use a custom fetcher with SWR which returns the Promise.all of multiple requests, but that doesn't cache results individually.

A good use case is given on the link above. Imagine having to fetch multiple users at once, and then in other parts of the code, only request for one of these users. You would definitely like to avoid making a new request for the same user requested earlier.

Note: I tried to ask the community for a way to do that with SWR, without success.

@gfox1984 gfox1984 changed the title Add an equivalent of React Query's useQueries hook Missing an equivalent of React Query's useQueries hook Mar 16, 2021
@shuding
Copy link
Member

shuding commented Mar 17, 2021

Thanks for the detailed description! So the solution would be something similar to the impl. of useSWRInfinite, but requests are fired together instead of paginated.

Happy to add it if someone comes up with a PR, I think useSWRList would be a nice name. 👍

@promer94
Copy link
Collaborator

promer94 commented Mar 19, 2021

I think we could use the global mutate function to cache the each item based on its key.

import { mutate } from 'swr'
function useSWRList(queryList) {
  const mutations = queryList.map(({ queryKey, queryFn }) => {
    return mutate(queryKey, () => queryFn(...queryKey))
  })

  return useSWR(
    queryList.map((v) => v.queryKey),
    () => Promise.all(mutations)
  )
}

FYI: https://codesandbox.io/s/swr-list-xw58t

@nandorojo
Copy link

We'd also want to catch errors for each key individually, so that some calls can fire even if others aren't ready. If the first request's key throws an error, the others should still fire.

@promer94
Copy link
Collaborator

@nandorojo Hi, would you mind check the codesandbox again. I have updated the example.

const mutations = queryList.map(({ queryKey, queryFn }) => () =>
    mutate(queryKey, () => queryFn(...queryKey))
      // The data has been saved in cache based on key
      .then((v) => v)
      // The error has been save in cache base on key
      // so the error was handled individually
      .catch((e) => ({
        error: e,
        key: queryKey
      }))
  )

I feel like this pattern might be batter then useQueries hook. It only rerenders onces when all data resolves.
useQueries hook will rerender much more times than useSWRList which might cause unexpected layout shift or bad performance

@eliascotto
Copy link

@promer94 Hi, thanks for the solution. It seems resolving the case.

Just to keep in consideration for the PR, in my case I didn't need to mutate the entire list but just fetch every time the array changes and a new request should be performed.

So in my case I modified the function like this

import useSWR, { mutate, cache } from 'swr'

export function useSWRList(queryList, queryFn) {
  const mutations = queryList.map((queryKey) => () => {
    const cachedResult = cache.get(queryKey)

    if (cachedResult) {
      return cachedResult
    }

    return (
      mutate(queryKey, () => queryFn(queryKey))
      // The error has been save in cache base on key
      // so the error was handled individually
      .catch((e) => ({
        error: e,
        key: queryKey,
      }))
    )
  })

  return useSWR(
    queryList,
    () => Promise.all(mutations.map((v) => v()))
  )
}

@ianstormtaylor
Copy link

ianstormtaylor commented Sep 21, 2021

Would love to see this!

This is useful for any situation where you have lists that contain elements that may be shared across parents. For example imagine looking up portfolios of stocks (eg. an array of tickers). There's a chance for overlap between two portfolios (eg. they both contain AAPL), so you want to cache the individual stock queries.

This type of use case can't be replicated with sub-components and the normal useSWR hook. You could do that if all you wanted to do was show a profile for each stock. But then you wouldn't be able to calculate aggregate statistics like "net worth" using the entire portfolio of holdings.

@shuding shuding added feature request New feature or request and removed question labels Sep 21, 2021
@mAAdhaTTah
Copy link

Is this currently being worked on? We could use this & would be interested in picking this up.

@rodbs
Copy link

rodbs commented Nov 5, 2021

I'm looking for this feature too!!
@elias94 Is your solution updated to useSWR 1.0.0 ( I think the cache is imported from useSWRConfig)

@wei
Copy link

wei commented Nov 11, 2021

Hi @promer94 thanks for the response and the examples above. However, like many above we would love to have the ability to load data from multiple requests as data comes back, in order words, rerender anytime we receive a response from one of the requests.

To respond to your reply above:

I feel like this pattern might be batter then useQueries hook. It only rerenders onces when all data resolves. useQueries hook will rerender much more times than useSWRList which might cause unexpected layout shift or bad performance

We have optimized UX to handle layout and component caching for performance. Showing some data as soon as they're available has been proven to help with attracting users' attention and retention. I feel like dealing with layout and rerender performance should be something developers can take into consideration themselves.

We would love to have useQueries equivalent in addition to the Promise.all pattern you've shared above to provide best of both worlds~ What do you think?

@Totalbug92
Copy link

Any status update on this?

@mAAdhaTTah
Copy link

I started working on it initially before getting pulled away to other things. I also realize, based on convos here, that the desired semantics of this hook aren't necessarily clear. I'll probably just pick an approach to get it into a PR for discussion, but I don't really have so much code written that, should someone else have the time, they wouldn't/shouldn't pick this up.

@shuding
Copy link
Member

shuding commented Dec 1, 2021

desired semantics of this hook aren't necessarily clear

Agreed here @mAAdhaTTah, I think the most important thing to do first is getting a RFC/proposal instead of jumping into the implementation directly.

@kripod
Copy link

kripod commented Jan 13, 2022

I think the project should aim to mimic the interface that useQueries provides, so that each of the results are loaded independently. It’s possible to create a user-land implementation for the variant which works in an all-or-nothing fashion, but independent loading of each query doesn’t seem possible without mimicking huge parts of SWR, due to the fact that a { data, error, isValidating } object should be managed for each individual query.

Having the dependency collection optimization in mind, I would suggest the useSWRList hook to return with an object of array values:

const { data, error, isValidating } = useSWRList(["a", "b", "c"], config);
// data[i]
// error[i]
// isValidating[i]

rather than an array of objects:

const results = useSWRList(["a", "b"], config);
// results[i].data
// results[i].error
// results[i].isValidating

because when fetching each item on the list, chances are that all of the given items will be used – the access of data feels easier to track than result[i].data.

However, error and isValidating may not be used for any elements at all, so when an error happens for an item, no re-render may be necessary, depending on the renderer code.

Also, I think the useSWRList hook should accept a single optional config (and fetcher) parameter, as:

  • items loaded with useSWRList should have identical types
  • the request for multiple items should possibly (but not necessarily) be sent to the same endpoint, but with different parameters

I would recommend suggesting developers to use the useSWRList hook only to load a variable amount of items of the same kind – for use-cases other than that, use individual useSWR hooks or multiple useSWRList calls grouped by the type of items requested.

@mAAdhaTTah
Copy link

@shuding and anyone else in this issue: I've opened an RFC for this hook. I'm also willing to implement it once we agree on the intended semantics. I was doing some refactoring of our code and need this hook to fix some things so I finished up the RFC I started a while back. Also happy to implement this once we align on semantics.

Let me know what y'all think!

@promer94 promer94 mentioned this issue Jun 1, 2022
@vercel vercel locked and limited conversation to collaborators Dec 20, 2022
@koba04 koba04 converted this issue into discussion #2325 Dec 20, 2022

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
feature request New feature or request
Projects
None yet
Development

No branches or pull requests