Skip to content
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

Merged
merged 33 commits into from
Jul 4, 2024

Conversation

phryneas
Copy link
Member

@phryneas phryneas commented May 27, 2024

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.

Copy link

changeset-bot bot commented May 27, 2024

🦋 Changeset detected

Latest commit: 33c0fef

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@apollo/client Minor

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

Copy link
Contributor

github-actions bot commented May 27, 2024

size-limit report 📦

Path Size
dist/apollo-client.min.cjs 38.89 KB (+0.52% 🔺)
import { ApolloClient, InMemoryCache, HttpLink } from "dist/main.cjs" 47.63 KB (+0.31% 🔺)
import { ApolloClient, InMemoryCache, HttpLink } from "dist/main.cjs" (production) 45.17 KB (+0.21% 🔺)
import { ApolloClient, InMemoryCache, HttpLink } from "dist/index.js" 34.22 KB (-0.01% 🔽)
import { ApolloClient, InMemoryCache, HttpLink } from "dist/index.js" (production) 32.08 KB (-0.01% 🔽)
import { ApolloProvider } from "dist/react/index.js" 1.26 KB (0%)
import { ApolloProvider } from "dist/react/index.js" (production) 1.24 KB (0%)
import { useQuery } from "dist/react/index.js" 5.2 KB (-2.19% 🔽)
import { useQuery } from "dist/react/index.js" (production) 4.28 KB (-2.67% 🔽)
import { useLazyQuery } from "dist/react/index.js" 5.68 KB (+1.42% 🔺)
import { useLazyQuery } from "dist/react/index.js" (production) 4.75 KB (+1.83% 🔺)
import { useMutation } from "dist/react/index.js" 3.59 KB (+0.03% 🔺)
import { useMutation } from "dist/react/index.js" (production) 2.81 KB (+0.04% 🔺)
import { useSubscription } from "dist/react/index.js" 3.63 KB (+0.03% 🔺)
import { useSubscription } from "dist/react/index.js" (production) 2.78 KB (+0.04% 🔺)
import { useSuspenseQuery } from "dist/react/index.js" 5.47 KB (0%)
import { useSuspenseQuery } from "dist/react/index.js" (production) 4.12 KB (0%)
import { useBackgroundQuery } from "dist/react/index.js" 4.98 KB (-0.02% 🔽)
import { useBackgroundQuery } from "dist/react/index.js" (production) 3.63 KB (0%)
import { useLoadableQuery } from "dist/react/index.js" 5.04 KB (0%)
import { useLoadableQuery } from "dist/react/index.js" (production) 3.69 KB (0%)
import { useReadQuery } from "dist/react/index.js" 3.35 KB (+0.03% 🔺)
import { useReadQuery } from "dist/react/index.js" (production) 3.3 KB (0%)
import { useFragment } from "dist/react/index.js" 2.32 KB (0%)
import { useFragment } from "dist/react/index.js" (production) 2.27 KB (0%)

@phryneas phryneas changed the base branch from pr/lint-hooks to main June 21, 2024 10:41
Copy link

netlify bot commented Jun 21, 2024

Deploy Preview for apollo-client-docs ready!

Name Link
🔨 Latest commit 33c0fef
🔍 Latest deploy log https://app.netlify.com/sites/apollo-client-docs/deploys/6686b3fab7f2da00083f95be
😎 Deploy Preview https://deploy-preview-11869--apollo-client-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@phryneas phryneas added this to the 3.11.0 milestone Jun 25, 2024
@phryneas phryneas changed the title [WIP] refactor useQuery to not use an internal class refactor useQuery to not use an internal class Jun 25, 2024
@phryneas phryneas marked this pull request as ready for review June 25, 2024 09:05
@jerelmiller jerelmiller changed the base branch from main to release-3.11 July 2, 2024 17:29
@jerelmiller
Copy link
Member

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!

@phryneas phryneas force-pushed the pr/rewrite-useQuery branch 2 times, most recently from ec7621f to a8f9e70 Compare July 3, 2024 09:52
@phryneas
Copy link
Member Author

phryneas commented Jul 3, 2024

Did a bunch of cherry-picking and history rewriting - it should be good now!

Copy link
Member

@jerelmiller jerelmiller left a 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);
Copy link
Member

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
Copy link
Member

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.

Copy link
Member Author

@phryneas phryneas Jul 4, 2024

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 Show resolved Hide resolved
src/react/hooks/useQuery.ts Outdated Show resolved Hide resolved
Comment on lines 194 to 196
renderPromises.getSSRObservable(makeWatchQueryOptions())) ||
client.watchQuery(
getObsQueryOptions(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Copy link
Member Author

@phryneas phryneas Jul 4, 2024

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.

options: QueryHookOptions<NoInfer<TData>, NoInfer<TVariables>>,
watchQueryOptions: Readonly<WatchQueryOptions<TVariables, TData>>
) {
{
Copy link
Member

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?

Copy link
Member Author

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 Show resolved Hide resolved
);
export function toQueryResult<TData, TVariables extends OperationVariables>(
result: ApolloQueryResult<TData>,
resultData: InternalResult<TData, TVariables>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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,
Copy link
Member

@jerelmiller jerelmiller Jul 3, 2024

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.

Copy link
Member Author

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;
Copy link
Member

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 😆)

Copy link
Member Author

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.

@phryneas phryneas force-pushed the pr/rewrite-useQuery branch 3 times, most recently from c903b43 to dfda94e Compare July 4, 2024 14:18
@phryneas phryneas merged commit 5ae4876 into release-3.11 Jul 4, 2024
41 of 42 checks passed
@phryneas phryneas deleted the pr/rewrite-useQuery branch July 4, 2024 14:44
@github-actions github-actions bot mentioned this pull request Jul 9, 2024
@github-actions github-actions bot mentioned this pull request Jul 22, 2024
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 4, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants