Comments (5)
Hey @Stephen2 👋
Thanks for the question! I'll do my best to explain the behavior you're seeing here.
I just don't understand how
possibleTypes
work.
possibleTypes
is a way for you to explain the subtype/supertype relationship between types in your GraphQL schema. This is necessary for queries that return in-line fragments to work.
What's neat about possibleTypes
is that it allows you to share type policies among several subtypes by defining a type on your supertype, which you've done here with keyFields
on CandidateSubmissionData
. This saves you from needing to copy that keyFields
configuration over to every subtype that would implement that interface.
I was personally expecting it to store as the Interface name
CandidateSubmissionData
You actually wouldn't want us to do this! Interfaces do not guarantee uniqueness among their subtypes, so if we were to store data using the interface type, there is a possibility we might clobber data together in ways that don't make sense. Take the following schema:
interface Character {
id: ID!
name: String!
}
type Jedi implements Character {
id: ID!
name: String!
lightsaberColor: String
}
type Droid implements Character {
id: ID!
name: String!
primaryFunction: String
}
And this query:
query {
characters {
id
name
... on Jedi {
lightsaberColor
}
...on Droid {
primaryFunction
}
}
}
Since that interface doesn't guarantee uniqueness, its possible we get 2 different characters with the same id, which is perfectly valid:
{
data: {
characters: [
{
__typename: 'Jedi',
id: 1,
name: 'Luke Skywalker',
lightsaberColor: 'green'
},
{
__typename: 'Droid',
id: 1,
name: 'R2-D2',
primaryFunction: 'Astromech'
}
]
}
}
If we were to write this to the same cache entry, you'll get a mix of data that wasn't meant to be combined:
{
'Character:1': {
__typename: 'Character',
id: 1,
// Oh no, we've lost Luke!
name: 'R2-D2',
// Uh-oh, we have both Jedi and Droid data here!
lightsaberColor: 'green',
primaryFunction: 'Astromech',
}
}
Instead, we write these using their subtypes to ensure the data is isolated from each other.
This also goes for reading data out of the cache. It's impossible to determine which is the "correct" entity to read out of the cache if you provide the interface type as the __typename
:
useFragment({
// Should we return Jedi:1 or Droid:1 here?
from: { __typename: 'Character', id: 1 }
})
For this reason, we require you to pass the subtype to useFragment
so that we can provide you with the right entity.
The final straw that made me think this might just be a bug, and not me missing something, is playing with
cache.identify
:
cache.identify
is just a helper method that takes an object with __typename
and key fields, looks up the associated type policy for that type, and gives you back a string cache ID. It's not smart enough though to detect if that __typename
is itself a supertype, so it just returns that name (though it is smart enough to know if that __typename
is a subtype). We probably could warn here since you'd never want to use the interface cache id, but this is not something it does today.
Workaround my colleague figured out
I'd avoid doing this because as the above example demonstrates, storing the data under the interface type is dangerous and you might end up mixing data that should be isolated from each other and overwriting common field names between subtypes. Instead just be sure to use the subtypes in useFragment
to ensure you're getting the data you expect.
I hope this helps and sheds some light on why this works that way!
from apollo-client.
Wow, thank you so much for taking the time for these excellent explanations!
we require you to pass the subtype to useFragment
Sounds like it might not be possible for me to have a component call useFragment
when the component is agnostic about the implementing Type, and just wants to operate on the Interface?
For the concrete example, using your examples above:
interface Character {
id: ID!
name: String!
}
Say a component that shows the name
for either Jedi or Droid wouldn't be possible with just 1 component and 1 useFragment
call.
That's a bit of a blow. Let me know.
Thinking out loud - If not possible to useFragment
, I suppose I can use prop drilling to get the fragment into the component.
In my case, I do know that the all the Types implementing Interface must have a globally unique submissionUuid
so our hack is "safe", but I do take your lesson loud & clear that this shouldn't be how to do things in Apollo
from apollo-client.
Sounds like it might not be possible for me to have a component call
useFragment
when the component is agnostic about the implementing Type, and just wants to operate on the Interface?
I probably should have clarified, this is only specific to the value you pass to from
. You are absolutely able to use a GraphQL fragment that works on the interface type! You just can't pass a from
that contains a __typename
that is the interface type itself.
This is perfectly valid however:
const CHARACTER_FRAGMENT = gql`
# We can use interface types in our fragment document just fine!
fragment CharacterFragment on Character {
id
name
}
`
interface CharacterProps {
id: string;
type: 'Droid' | 'Jedi';
}
function Character({ id, type }: CharacterTypes) {
const { data } = useFragment({
fragment: CHARACTER_FRAGMENT,
// This just needs to contain a `__typename` that is either "Droid" or "Jedi",
// but not "Character"
from: { __typename: type, id }
});
return <div>{data.name}</div>
}
I don't fully understand how you've got your app setup, but the only thing you'd need to make sure to pass to this component is the subtype name, but it will be able to handle both interface types correctly.
Does that help?
from apollo-client.
Yeah it helps a lot.
I just realised from your explanation that I could pass in from the parent that runs the query
, request and pass down the __typename
into the component, and use that for the from
just like you did there.
I'll have to think on this all, because if I'm prop drilling __typename
down anyway, maybe it just makes sense to prop drill the fragmentData
directly.
I think I'm going to close this now, I think I understand all your lessons 🙏 what a lucky day, for me to have answers from someone so quick to respond & knowledgeable!! cheers mate
from apollo-client.
Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better.
from apollo-client.
Related Issues (20)
- Tracking issue: `gql.tada` HOT 2
- @defer not working in React Native HOT 20
- Apollo client initiated requests since apollo client v3.9.2 in Nextjs sometimes render as undefined data HOT 9
- Better types MockedResponse.newData HOT 3
- graphql union type extraction fragment doesn't get mapping field values HOT 4
- Question about using useTransition with useSuspenseQuery's refetch and fetchMore after SSR HOT 2
- Error while build the angular app with SSR HOT 1
- `ObservableQuery` `setVariables`/`reobserve` with new variables reports partial data unexpectedly
- Invariant Violation: An error occurred! For more details, see the full error text at https://go.apollo.dev/c/err#%7B%22version%22%3A%223.9.4%22%2C%22message%22%3A49%2C%22args%22%3A%5B%5D%7D HOT 8
- offsetLimitPagination helper is not using cache in some cases HOT 2
- Better documentation on `queryDeduplication` HOT 1
- useFragment invalid return value in the case of null or undefined id in the from argument HOT 1
- Refetch with New Variables Overrides Other Existing Refetches HOT 3
- Increase the timeout for a request. HOT 7
- Apollo Client `MockedProvider` unable to mock alias fields HOT 6
- `client.query`, `fetchMore`, and others suffer from unhandled promise rejections
- useQuery loading state always true HOT 8
- when apollo client query failed, it exit all the process and cannot catch the error HOT 2
- Field Policies: 'undefined' return value for `read` affects other field policy `read` return values. HOT 2
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from apollo-client.