urql-graphql / urql

The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
https://urql.dev/goto/docs
MIT License
8.54k stars 444 forks source link

feat(react): useFragment hook (BETA) #3570

Closed JoviDeCroock closed 2 months ago

JoviDeCroock commented 2 months ago

Resolves #1408

Summary

This introduces a new hook to the React package that has two main purposes. On one hand it will mask over the data it gets from its parent component given a fragment, so in the following scenario:

query GetTodo { todo { ...TodoFields completed } }
fragment TodoFields on Todo { id text }

When we'd pass the resulting data from GetTodo.todo into a component and leverage useFragment we wouldn't be able to get to the completed parameter.

The second goal is to establish a loading paradigm for deferred queries, this means that given the following document

query {
  todo {
    id
    text
    author { ...AuthorFields @defer }
  }
}

fragment AuthorFields on Author {
  id
  name
}

When the initial payload comes in we can display the id and text, during that we display this query as stale however we can make this hook in deeper with React paradigms by leveraging suspense when the deferred boundary isn't complete and suspense is enabled we can suspend. Even without suspense this brings the benefit of not having to check stale and being able to render independently of the result.stale flag.

Notes

The heuristic used to assess whether we are using is the presence of undefined basically data can only be undefined when it has not been queried or while it's being streamed in.

I opted for a slightly different approach to useQuery, in useQuery we are subscribed to the request and when the resolved query comes in we call resolve on the promise we threw to React. In this one I opted to purely base it on props, I tested this in this demo.

I opted to create the request with our code we use for operations but replaced variables with data to ensure every entity gets its own cache entry.

A lacking piece at the moment would be directives, if a user leverages @include() with a variable then we'd need access to the parent variables to uphold our heuristic of when to set this to loading. To counter-act this we check whether the field is undefined and when it had one of these directives we accept the absence of the data.

In the future we'll need to find a way to resolve() the promise, this is currently impossible as we have no way of establishing a stable identifier across concurrent invocations of a fragment given unique sets of data.

Todo

changeset-bot[bot] commented 2 months ago

🦋 Changeset detected

Latest commit: 11727b66afed571414cffddb1ec7db19e9876304

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

This PR includes changesets to release 1 package | Name | Type | | ---- | ----- | | urql | 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

JoviDeCroock commented 2 months ago

This proposal has run into kind of a dead-end after trying it out with streamed rendering, the issue resides in the fact that during SSR React does not do any re-rendering which means useFragment will never be re-evaluated.

For this to work we'd have to establish a subscription in our urql-client where this fragment is used, this subscription would need to bring the data to the useFragment, this adds additional complexity to this API proposal because we'd need to be aware on which entity we are listening for changes and hook into the correct query. to evaluate this fragment against.