useFragment requires concrete __typename for interface types

I’m working on a large project that is currently running into some friction when using useFragment with types that implement interfaces.

Context:

Our app preloads data in a custom package and components then read that data via useFragment. For non-interface types, this is straightforward since the __typename is predictable. But when the fragment is on an interface, the component doesn’t always know which concrete __typename to pass in. We can’t reliably prop-drill the __typename from a parent component, because the data preloading happens in a separate package and is decoupled from the component tree that renders the fragments.

We’ve seen a couple issues in the past related to useFragment and interfaces:

We created a minimal repro that emulates our app’s data loading and some of the options we’re considering below: GitHub - jcostello93/apollo-client-fragment-interface

Option 1: Use cache-only useQuery instead of useFragment in leaf nodes. The useQuery hook correctly returns data without requiring the concrete __typename. However, the performance concerns of using useQuery instead of useFragment make this a non-starter.

Option 2: Store cache refs in React context. After the preload, we’d have single cache-only useQuery to read the ids and __typenames from the cache and then store them in context. Components could then get the cache ref from context and pass it to useFragment.

Option 3: Create a useInterfaceFragment helper that checks the cache for the concrete __typename and passes that into useFragment.

Option 4: Define a keyFields function on the interface that hardcodes the interface __typename. For example:

BaseItem: {
  keyFields: () => `BaseItem:${id}`,
}

This way, the policy is inherited by implementation types via possibleTypes and components can just pass in from: { id, __typename: ‘BaseItem’}. This workaround was also used in Cache redirects don't work with interfaces specified by possibleTypes · Issue #382 · apollographql/apollo-feature-requests · GitHub.

We’re currently leaning towards Option 4 (custom keyFields on the interface type) given its simplicity and minimal impact on our codebase. But since this breaks from default keyFields behavior, we wanted to validate this pattern with the team and see if we missed any alternatives. We also noticed at least example of strange behavior where keyFields was invoked with an undefined id, leading to a InterfaceType:undefined cache ref.

We also wanted to ask if there’s a plan to support possibleTypes relations in useFragment in the future so the concrete typename is not required.

Thank you!

1 Like

Hi there!

Generally, only an id or only the name of the implementing interface is just not enough information to go on for useFragment.

Look at a scenario, where both A and B implement the fragment interface F, and you have both A:1 and B:1 in the cache.
This is a very valid scenario that might cause a conflict here.
It would even get worse, if you had A:1 in the cache, and B:1 would exist on the server, but was not in the cache. By just looking at the cache and parent type, useFragment would deliver you A:1, but in reality it should throw an error due to the conflict - your code might require B:1, but get completely wrong data.

So, there is nothing on the Apollo Client side we could change to “fix” this. Your schema might guarantee the id to be unique not only on a typename level, but also on a “all shared interface implementations” level, but that isn’t the case for every schema.

As a result, the four options you are giving here are probably everything that’s available to you. I don’t think there’s a clear winner from our side - it’s best to make that choice on your end.

As for your undefined case - if you can create a reproduction and believe this is a bug, please open a bug report in our GitHub issues, I’d be happy to take a look!