-
-
Notifications
You must be signed in to change notification settings - Fork 3k
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
new useQueries API #2923
Comments
if a query has
|
omit it from the options on type level and ignore it at runtime is the plan. |
If you know any other options that should be hoisted, please let me know :) |
I'm not a huge fan of "silent" anomalies at runtime (from the developer point of view who set the option at the query level and could expect a certain behavior). You may have this case with the current |
suspense doesn't work at all for useQueries at the moment, and having one suspend and the other not is exactly the issue that sparked this re-design. There is an old PR (#2109) that tries do to it, and the best thing we could come up with is suspending as soon as any of the queries suspends. That's pretty much the same as this proposal, except that this is more explicit.
hmm, imho, the options just doesn't exist anymore on that level in v4. TypeScript will enforce that, and we'll also highlight it in the migration guide. It is equivalent to passing |
It's fine with me! |
Some thoughts about mixing
|
I guess my feelings about // assuming `suspense: true` for brevity -- i don't feel like typing out `isLoading` stuff :)
const [
{ data: criticalData },
{ data: niceToHaveData }
] = useQueries([
{ ...criticalQuery, useErrorBoundary: true }, // if this doesn't work, just crash, nothing we can do
{ ...niceToHaveQuery, useErrorBoundary: false }, // we can manage without this one though
]);
return (
<div>
<CriticalUI data={criticalData} />
<NotThatImportantUI data={niceToHaveData ?? "it's okay, we can show a fallback or something"} />
</div> EDIT: of course, with a "hoisted" niceToHaveQuery.queryFn
.then(data => ({ ok: true, data }))
.catch(error => ({ ok: false, error })) but then |
@lubieowoce thank you for your input. I really like the As for suspense, I have very little experience with it. From my last talk with @tannerlinsley, I took away that what we want to achieve is to use the same suspense boundary for all queries that do suspend and fetch them in parallel. The loading should stop when all queries are done. if we mix suspense true / false within one useQueries, how would that behave? As you said:
Yes, you get basically the same with separate useQuery calls / separate useQueries calls, but at least it's explicit. Changing the api now to an object will give us more room for extensibility in the future to have settings that need to be set for all queries in the list. I thought that
I don't fully understand that. If users really want to mix suspense / non-suspense, couldn't they just do two separate
Why not? I think this would be the expected behaviour for me. How else should it be? FYI, with React 18, we will likely move over to |
unfortunately they can't. this won't start fetching [A,B,C,D] in parallel (which is what i personally would expect): useQueries([A, B], { suspense: false }); // #1
useQueries([C, D], { suspense: true }); // #2 because when a component suspends (because of (obviously if we swapped the calls and did the suspense one first, i wouldn't expect them to start in parallel, because control flow wouldn't reach the non-suspense one during the initial render. but it's way less obvious that the order in the example won't be parallel) please let me know if I can explain anything better! i tried my best to be explicit about the assumptions/expectations i have for the behavior, but maybe I'm missing something :) |
I think it would be fine to suspend two and then start the other two, but yeah, it's not ideal. It's also a question of "who does this?", and "is this really a use-case"? Why would you suspend one query but not the other?
the current suspense implementation in not sure how
you're doing a very good job explaining me all this suspense stuff, so thank you for that 🙏 |
idk, i wasn't imagining anything fancy, just something in the vein of if (defaultedOptions.some((q) => q.suspense) {
const willSuspend = ...
if (willSuspend) {
// kick off the ones that don't use suspense
startFetching(defaultedOptions.filter((q) => !q.suspense))
}
// suspend on the ones that *do* use suspense
throw fetchAll(defaultedOptions.filter((q) => q.suspense));
}
well... i would like to do it, sometimes 😁 Here's an example of when you might want this, a variation on the const Page = () => (
<div>
<h1>FooHub</h1>
<Suspense fallback={<UserWidgetSkeleton />}
<UserWidget />
</Suspense>
</div>
);
const UserWidget = () => {
const [
{ data: profile },
repos,
] = useQueries([
{ ...profileQuery, suspense: true },
{ ...reposQuery, suspense: false },
]);
return (
<div>
<img src={profile.avatarUrl} />
<h2>@{profile.username}</h1>
<div>Joined {profile.joinedDate}</div>
<div>
{repos.isLoading
? <Spinner />
: `this user has ${repos.data.length} repositories`
}
</div>
</div>
);
} The gist is, we can't really show anything until Of course, you could get around this by preloading stuff on |
that use-case also makes sense, and if you can get that done, that would be indeed cool. Here is a quick example with a suspense query and a non-suspense query: https://codesandbox.io/s/react-query-starting-point-forked-j8qml?file=/src/App.tsx You can see that the suspense query loads first, and only then the non-suspense query starts to fetch. They don't run in parallel. I think this is in line with what you outlined previously. We currently subscribe in a useEffect, which is why the subscription doesn't trigger because suspense suspends before that. With React 18, this will change to If I get it right, your suggestion is to call all in all, even if we decide to go that route and keep I just think that the api would be more extensible and future proof if it were an object |
yeah, that's more or less what I'm thinking!
I hope i can make it work :) and AFAIU, it's fine to do side-effecty stuff like this as long as you suspend afterwards and take care to not do it again if it's already done. reminds me of the (officially supported!) one-time-setstate init pattern: const [thingy, setThingy] = useState();
if (!thingy) {
setThingy(new Thingy(...));
}
yeah, |
the API itself (putting aside all the suspense stuff)
what if we did the same thing we do with query options, and allow two forms: useQueries([A, B, C])
useQueries({ queries: [A, B, C], someFutureSetting: ... }) or maybe a mix, where there's an extra param - trivially backwards compatible! useQueries([A, B, C], { someFutureSetting: ... }) tbh where i'm coming from is i just really don't want to type but it's true, an object lets us do more stuff! some very handwavy examples i came up with: // #1. Named queries
// (technically could be done without the outer object,
// but then we'd be dealing with name conflicts between
// "hoisted" settings and queries.
// and we'd need "reserved names" to avoid conflicts,
// which always sucks and becomes a back-compat pain)
const { a, b } = useQueries({
queries: { a: A, b: B, c: C },
});
// #2. Multiple arrays
// (idk what `lazyQueries` would do, just an example)
useQueries({
queries: [A, B, C],
lazyQueries: [D, E],
});
// #3. a Promise.all sort of thing
const aPlusB = useQueries({
queries: [A, B],
combine: ([a, b]) => a + b,
})
// #4. multiple queries helper?
// equivalent to
// useQueries([idA, idB].map((id) =>
// ({ key: ['thing', id], queryFn: ... })
// ))
// but maybe there's something smart we can do with
// the knowledge that they're all "instances of the same query"?
const aPlusB = useQueries({
values: [idA, idB],
keyFn: (id) => ['thing', id],
queryFn: (id) => fetch(`/api/get-thing/${id}`),
}) (these are just quick examples of the possibilities and not necessarily APIs i'd want -- some of them stray firmly into DSL-land, which i'm not a fan of because those tend to grow until they're a bad little scripting language. if we wanted "query combinators" like that |
in v4, we'll also remove all overloads and provide only one streamlined api for everything - an object. so yeah, with this in mind alone, I think it makes sense to change the api to accept an object, where the current array then lives under
yes, these are all good examples of things we can do in the future once we have the new api. I'll add the array -> object change to #2918 because we'll very likely also add a codemod that does all this. We can keep this issue open to track if / which options should be moved to the top level. If we can't make mixed suspense work, this would be one candidate. Otherwise, if we can support all options on each query, we don't need to do anything further and just have the option of adding more options (pun intended) in the future :) |
Ohhh not sure how I missed that... in that case an object will be consistent with the rest, so it makes sense! 👍 |
@lubieowoce any updates on making useQueries work with suspense? |
@TkDodo work has stalled a bit, i've been busy 😅 def not PR ready yet + there's probably a bunch of junk i left lying around, but the WIP is here: https://github.com/lubieowoce/react-query/tree/feature/use-queries-suspense-2 TBH i also think i got a bit overwhelmed - the codebase has a lot of moving pieces. i could use some guidance on how it's all meant to fit together... maybe a chat somewhere w/ a shorter feedback loop than GH comments if you've got time? also this reminds me of a v4-adjacent thing to consider. the name " |
@lubieowoce we can definitely have a chat if you want - feel free to reach out to me on the TanStack Discord or a Twitter DM.
I think the component mounts, in which case we fetch, and then it suspends. It will then re-mount, which is why we actually set a small |
alright, will do! thanks :)
hmm. i can't remember now, but IIRC (really hope i'm not confusing sth about how |
no, it does not suspend on refetches. That could happen on every window focus and would show loading spinners all over the place, which is something react-query is good at avoiding 😅 . Only the hard |
just taking an Array of useQuery objects doesn't cover all the cases, because some options have to be the same for all queries, for example
suspense
oruseErrorBoundary
: We can't really have one query in the list suspend and some other query not suspend. Conceptually,useQueries
is also more than a bunch ofuseQuery
calls chained together.Proposed solution
make
useQueries
take an object as input, where we can define top level options that are true for all queries and a list ofqueries
, something like:The text was updated successfully, but these errors were encountered: