-
Notifications
You must be signed in to change notification settings - Fork 990
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
feat: Support GraphQL Fragments with Apollo Client and Fragment Registry #9140
feat: Support GraphQL Fragments with Apollo Client and Fragment Registry #9140
Conversation
groceries { | ||
... on Fruit { | ||
id | ||
name | ||
isSeedless | ||
ripenessIndicators | ||
} | ||
... on Vegetable { | ||
id | ||
name | ||
vegetableFamily | ||
isPickled | ||
} |
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 know you're in a draft so maybe too early to comment on this one but I'd expect instead of ... on Fruits {
to have a ...fruitsFragment
instead that is defined elsewhere like:
const FRUITS_FRAGMENT = gql`
fragment fruitsFragment on Fruit {
id
name
isSeedless
ripenessIndicators
}
`;
This is a valid case for union typing resolution though so super valuable nonetheless but this isn't quite the power of fragments.
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.
Yup! That's the plan. This test app was currently just to have the possibleTypes code gen working.
I have a separate app that I am actually testing fragments and then will rework this ci test project.
The hope is we can CI some e2e tests to ensure fragments return and render properly.
It will be something like (but not this exact code)...
import type { Fruit } from 'types/graphql'
import { registerFragment } from '@redwoodjs/web/apollo'
const { useRegisteredFragment } = registerFragment(
gql`
fragment Fruit_info on Fruit {
id
name
isSeedless
ripenessIndicators
}
`
)
const Fruit = ({ fruitId }: { fruitId: string }) => {
const { data: fruit, complete } = useRegisteredFragment<Fruit>(fruitId)
console.log(fruit)
return (
complete && (
<div>
<h2 className="font-bold">Fruit Name: {fruit.name}</h2>
</div>
)
)
}
export default Fruit
Thanks!
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.
That is just beautiful. 10/10
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 can't talk to the Apollo constraints, but the Relay useFragment
API tends to look like this:
const fragment = graphql`
fragment UserDisplay on User {
id
username
usernameID
avatarURL
}
`
export type UserDisplayOptions = {
noLink?: boolean
noPopover?: boolean
size?: "small" | "medium" | "large" | "block"
mode: "text-only" | "avatar-only" | "text-and-avatar" | "avatar-and-text"
style?: ViewStyle
textStyle?: TextStyle
}
export const UserDisplay = (props: { user: UserDisplay$key } & UserDisplayOptions) => {
const user = useFragment(fragment, props.user)
if (!user) {
return null
}
return <UserDisplay_RAW {...props} user={user} />
}
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 can't talk to the Apollo constraints, but the Relay
useFragment
API tends to look like this:
Apollo's useFragment
is a bit different -- it is new to 3.8.x and specifically pluck data out of the cache based on the cache key (ie reference, ie "typename:your-id-ref").
useFragment represents a lightweight live binding into the Apollo Client Cache and enables Apollo Client to broadcast very specific fragment results to individual components. This hook returns an always-up-to-date view of whatever data the cache currently contains for a given fragment. useFragment never triggers network requests of its own.
See: https://www.apollographql.com/docs/react/data/fragments/#usefragment
Since it's new, most of the time you'd use the registerFragment
to add the fragment to the client ... yes, you have to register each fragment with Apollo and yes you have to inform Apollo all the possible types unions might have.
Hence the helpers in tis PR to:
- generate possible types via code gen
- add the registry
- let you use
registerFragment
or... and then just render the data in a component - let you use
useRegisterFragment
which is this bounduseFragment
hook that will fetch from the cache and redisplay data it the cache is change on a separate query or a mutation that write to the cache. useRegisterFragment
just needs anid
... the reference to the data stored in the cache.
In short, you don't have to use useRegisterFragment
-- if you do, just pass an id and then it will grab the data from the cache. Or, After registering it and using the defined fragment in a query, you can just access the data and render it.
Note: Still looking for a nicer way to set possibleTypes
than this. But possibleTypes
lives in your project, not the fw where the Apollo client is created:
import introspection from 'src/graphql/possibleTypes'
import FatalErrorPage from 'src/pages/FatalErrorPage'
import Routes from 'src/Routes'
import './index.css'
const App = () => (
<FatalErrorBoundary page={FatalErrorPage}>
<RedwoodProvider titleTemplate="%PageTitle | %AppTitle">
<RedwoodApolloProvider
graphQLClientConfig={{
cacheConfig: {
possibleTypes: introspection.possibleTypes,
},
}}
>
<Routes />
</RedwoodApolloProvider>
</RedwoodProvider>
</FatalErrorBoundary>
)
``
Here's an update on what the Fruit fragment component can be: import type { Fruit } from 'types/graphql'
import { registerFragment } from '@redwoodjs/web/apollo'
import Card from 'src/components/Card/Card'
import Stall from 'src/components/Stall'
const { useRegisteredFragment } = registerFragment(
gql`
fragment Fruit_info on Fruit {
id
name
isSeedless
ripenessIndicators
stall {
...Stall_info
}
}
`
)
const Fruit = ({ id }: { id: string }) => {
const { data: fruit, complete } = useRegisteredFragment<Fruit>(id)
console.log(fruit)
return (
complete && (
<Card>
<h2 className="font-bold">Fruit Name: {fruit.name}</h2>
<p>Seeds? {fruit.isSeedless ? 'Yes' : 'No'}</p>
<p>Ripeness: {fruit.ripenessIndicators}</p>
<Stall id={fruit.stall.id} />
</Card>
)
)
}
export default Fruit With Fruit, Vegetable and Product and Stall Fragment components. Queried by: const GET_GROCERIES = gql`
query GetGroceries {
groceries {
...Fruit_info
...Vegetable_info
}
}
`
const GET_PRODUCE = gql`
query GetProduce {
produce {
...Produce_info
}
}
` |
As a demo of how Apollo's useFragment can pull info from cache, here's an
But because the id is passed to the fragment component, the full data renders: It only works because the cache was populated by the other useQuery. |
@Josh-Walker-GM Have updated the useCache and documentation per our review yesterday and ready for approval (pending CI). |
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 walked through creating another app which uses what DT has added here and it all appears to work smoothly. I expect that there will be revisions of based on how people find the DX but I think this is good to merge now.
…try (#9140) This PR adds support to register GraphQL Fragments. It: * adds a fragmentRegistry to the Apollo client cache * Sets up graphql config to recognize fragments in VSCode * Generates possible types for Apollo client union type and interface support * Exposes a useRegisteredFragment to render cached data * New test app with fragments for future CI * Adds possible types to create redwood app templates * Document the possible types code gen * Documents use of fragments and the fragment registry helpers This is a WIP and first phase for easier fragment support. See the text fixture for some examples and the docs for usage. Still to code are use in mutations and updating the fragment in the client cache directly.
This PR adds support to register GraphQL Fragments.
It:
This is a WIP and first phase for easier fragment support.
See the test fixture for some examples and the docs for usage.
Still to code are use in mutations and updating the fragment in the client cache directly.