-
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
useFragment fails to get data from cache for an Interface, even when possibleTypes is setup correctly #11583
Comments
Hey @Stephen2 👋 Thanks for the question! I'll do my best to explain the behavior you're seeing here.
What's neat about
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 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
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 I hope this helps and sheds some light on why this works that way! |
Wow, thank you so much for taking the time for these excellent explanations!
For the concrete example, using your examples above:
Say a component that shows the That's a bit of a blow. Let me know. Thinking out loud - If not possible to In my case, I do know that the all the Types implementing Interface must have a globally unique |
I probably should have clarified, this is only specific to the value you pass to 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? |
Yeah it helps a lot. I just realised from your explanation that I could pass in from the parent that runs the I'll have to think on this all, because if I'm prop drilling 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 |
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. |
This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. |
Issue Description
I'm sorry in advance, there's a lot of complexity and I'm kinda new to Apollo. I don't have a minimum repro for you, but hope this minimum explanation & screenshots is enough.
CandidateSubmissionData
with a typetype ApplyCandidateSubmission implements CandidateSubmissionData
I believe I've informed Apollo client cache of this correctly:
CandidateSubmissionData
issubmissionUuid: ID!
I believe I've informed Apollo client cache of this correctly:
Fragment defined like:
Fragment data accessed like:
So I thought this would be enough to get everything working, but here's where I think the bug comes in.
I can see the object made it's way to normalized cache, by using DevTools:
However, I was personally expecting it to store as the Interface name
CandidateSubmissionData
but OK, maybe I just don't understand howpossibleTypes
work.But my fragment is not fulfilled,
complete === false
.If I modify the fragment to:
Then
complete === true
and my component has it's data from the cache.The final straw that made me think this might just be a bug, and not me missing something, is playing with
cache.identify
:Workaround my colleague figured out, with my comment above it:
Thanks for reading my essay, and I look forward to your help with understanding this 🙏
Link to Reproduction
n/a
Reproduction Steps
No response
@apollo/client
version3.8.6
The text was updated successfully, but these errors were encountered: