Should a tightly coupled mutation hook spread a component-owned fragment?

Apollo recommends colocating fragments with the components that consume their fields. The documentation also recommends that a parent component include only fragments belonging to its directly rendered children, creating a fragment hierarchy that mirrors the component hierarchy. This makes sense as it keeps each component responsible for defining its own data requirements and allows upstream fragments and queries to remain agnostic about downstream implementation.

How should that guidance apply to a custom hook that is tightly coupled to a component, particularly when the hook performs a mutation whose response includes the component’s fragment?

For example, assume TodoCard_Fragment is registered with Apollo’s fragment registry and can therefore be referenced by name without importing or interpolation :

// TodoCard.tsx
const TodoCard_Fragment = gql`
  fragment TodoCard_Fragment on Todo {
    id
    title
    completed
  }
`;

export function TodoCard({ todo }: Props) {
    const [updateTodo] = useUpdateTodo();

    return (
        <button
            onClick={() =>
                updateTodo({
                    variables: {
                        id: todo.id,
                        completed: !todo.completed,
                    },
                })
            }
        >
            {todo.title}
        </button>
    );
}

// useUpdateTodo.ts

const UpdateTodo_Mutation = gql`
  mutation UpdateTodo_Mutation($id: ID!, $completed: Boolean!) {
    updateTodo(id: $id, completed: $completed) {
      ...TodoCard_Fragment
    }
  }
`;

export function useUpdateTodo() {
    return useMutation(UpdateTodo_Mutation);
}

Since the hook would clearly not be a parent of TodoCard, spreading TodoCard_Fragment into the hook’s mutation falls outside the same direct-parent lineage as component fragment composition. At the same time, the hook and component are intentionally coupled tightly. The hook updates the entity rendered by the component, and returning the component fragment ensures that all of the returned fields written to the normalized cache.

Is Apollo’s direct-child recommendation intended only for composing fragments through the rendered component hierarchy, or does it also apply when a mutation (or other operation) encapsulated by a component-specific hook references a component’s fragment?

In this example, is it appropriate for UpdateTodo_Mutation to spread TodoCard_Fragment so that its response contains the fields consumed by TodoCard, even though the mutation is defined in a hook rather than in TodoCard or one of its parent components?

If not, what pattern does Apollo recommend for ensuring that a mutation returns the fields required by the component it updates without duplicating the component’s field selection?

The following related questions/issues/topics I found were close, but were either quite old, or not specifically mentioning coupled hooks:

Thank you in advance!
– Craig Wright

1 Like