-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
refactor useQuery to not use an internal class #11869
Conversation
🦋 Changeset detectedLatest commit: 33c0fef The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
size-limit report 📦
|
✅ Deploy Preview for apollo-client-docs ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
I've done a commit-by-commit look through this PR, but I'd like to wait to fully comment on this until you've had a chance to merge the base branches into this one. I think this contains changes from a couple other PRs, so its difficult to tell whats from that and whats from the other branches. Once that is done, I should be able to get a quick review submitted. Thanks! |
ec7621f
to
a8f9e70
Compare
Did a bunch of cherry-picking and history rewriting - it should be good now! |
a8f9e70
to
a69327c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still find a good portion of this fairly awkward but I think that's less a fault of this refactor and more that this refactor really illuminated some of the already tight coupling in a lot of the areas of the hook that were there as a result of the class usage. As much as I'd love to continue refactoring into oblivion, we gotta ship something and I think this is a huge improvement over what we've had.
I've added quite a few suggestions, but many of them are non-blocking so I'll let you use your best judgement on whether it makes sense to change them or not.
|
||
*/ | ||
expect(obsQueries.size).toBe(IS_REACT_19 ? 1 : 2); | ||
expect(obsQueries.size).toBe(2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎉 🎉 🎉
@@ -1,19 +1,38 @@ | |||
/** | |||
* Function parameters in this file try to follow a common order for the sake of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm curious, why this approach vs using an object to pass in values to each of the functions? There are cases such as this one that I would find easier to reason about with the object syntax vs positional args.
getObsQueryOptions(
undefined,
client,
options,
makeWatchQueryOptions()
)
// =>
getObsQueryOptions({
client,
options,
watchQueryOptions: makeWatchQueryOptions()
})
I fear these rules will be too easy to break in the future and if we need to add more arguments it will be difficult to what the "right" order would be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Positional function arguments can be minimized, while option object property names cannot.
In the end, you end up with
getObsQueryOptions(
void 0,
c,
o,
makeWatchQueryOptions()
)
vs
getObsQueryOptions({
client: c,
options: o,
watchQueryOptions: makeWatchQueryOptions()
})
and the same difference on the declaration side, too:
function getObsQueryOptions(a,b,c,d){}
vs
function getObsQueryOptions({
observable: a,
client: b,
options: c,
watchQueryOptions: d
}){}
or probably transpiled to
function getObsQueryOptions(o){
var a=o.observable, b=o.client, c=o.options, d=o.watchQueryOptions
}
This adds up, a lot.
With a file like this, which contains a lot of helper hooks, we could probably in the long run even use something like the Clojure compiler that would inline a bunch of the hooks - but only with positional arguments, not with config objects.
I fear these rules will be too easy to break in the future and if we need to add more arguments it will be difficult to what the "right" order would be.
Tbh., I'm fine with that. I wanted to get this in as orderly as possible for now (because during refactoring, it was a mess), but if things evolve, that's okay too :)
src/react/hooks/useQuery.ts
Outdated
renderPromises.getSSRObservable(makeWatchQueryOptions())) || | ||
client.watchQuery( | ||
getObsQueryOptions( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renderPromises.getSSRObservable(makeWatchQueryOptions())) || | |
client.watchQuery( | |
getObsQueryOptions( | |
renderPromises.getSSRObservable(makeWatchQueryOptions())) || | |
client.watchQuery( | |
getObsQueryOptions( |
I know this is a result of keeping much of the code the same, but something I'm noticing here is that both makeWatchQueryOptions
and getObsQueryOptions
both return WatchQueryOptions
. I find it very interesting that we sorta use them both together.
I'm not sure if there is any room for any further refactoring here, but it would be lovely if we could have a single place where we handle the creation of watch query options. I find it a bit difficult to understand when we'd want to use one vs the other.
Feel free to push back on this request, but I figured I'd note it as something that I found a bit confusing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the name getObsQueryOptions
is just poorly chosen.
It's more getWatchQueryOptionsPotentiallyMergingSomeOptionsFromThePreviousObservable
.
In the getSSRObservable
case, there is just a guarantee of "observable will never change", so the call is not necessary there.
I'd say let's leave it for now, but it is something I hope we can get rid of in the long term altogether.
src/react/hooks/useQuery.ts
Outdated
options: QueryHookOptions<NoInfer<TData>, NoInfer<TVariables>>, | ||
watchQueryOptions: Readonly<WatchQueryOptions<TVariables, TData>> | ||
) { | ||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these braces needed? Any chance we could remove the { }
surrounding the implementation here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uhh... yah no idea how I got these there..
src/react/hooks/useQuery.ts
Outdated
); | ||
export function toQueryResult<TData, TVariables extends OperationVariables>( | ||
result: ApolloQueryResult<TData>, | ||
resultData: InternalResult<TData, TVariables>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
resultData: InternalResult<TData, TVariables>, | |
previousData: TData | undefined, |
Could we pass in previousData
here instead? The resultData.current = toQueryResult(resultData)
feels a tad odd because I'd expect to mutate resultData
or something else. Changing this to previousData
makes it more obvious what the dependency is here.
const queryResult: InternalQueryResult<TData, TVariables> = { | ||
data, // Ensure always defined, even if result.data is missing. | ||
...resultWithoutPartial, | ||
client: client, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if there is an opportunity to do a touch more with the value returned from this function. I see that client
is set here, which appears to be used by _useQuery
(via result
), but I also see that useQueryInternals
returns a client
as well, which should be the same object. Seems we have some overlap between result
and the return value of useQueryInternals
. Any chance we could reduce the overlap between the two a bit further? Perhaps client
and observable
can be read from result
instead of the top-level object in useQueryInternals
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just gave that a try - it adds another 15 bytes (this touches a lot of code), and I'm not 100% sure without a very thorough review if this would always hold the right client for the right request.
As this whole "result handling" logic is something I kinda want to revamp anyways, I'd also say let's shelve this one.
(handleStoreChange) => { | ||
// reference `disableNetworkFetches` here to ensure that the rules of hooks | ||
// keep it as a dependency of this effect, even though it's not used | ||
disableNetworkFetches; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Side note:
At some point, it would be great to write a test for this. I've commented this out here and from the dependency array and everything continues to pass in both the main and ssr useQuery
tests. Are we sure this reference is actually needed? Might be good to evaluate if this is actually covered by a test somewhere (sorry I'm not doing that deep dive myself right now 😆)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm pretty sure it's not covered by a test 😭
This is a very weird workaround for SSR that we should also get rid of at some point.
c903b43
to
dfda94e
Compare
Co-authored-by: Jerel Miller <jerelmiller@gmail.com>
dfda94e
to
987aaad
Compare
8be4f63
to
33c0fef
Compare
After #11890, I'm fairly confident in this now.
This needs to be reviewed commit-by commit, although a bunch of merges added additional commits at the start and end - I'm really sorry for those! (I believe some of them will disappear here if #11890 gets merged first!)
There might be more PRs to further streamline the result handling, but I think this PR is big enough as it is right now.