Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

Why doesn't writeFragment update queries that use the fragment? #4001

Open
roballsopp opened this issue Jun 2, 2020 · 1 comment
Open

Why doesn't writeFragment update queries that use the fragment? #4001

roballsopp opened this issue Jun 2, 2020 · 1 comment

Comments

@roballsopp
Copy link

roballsopp commented Jun 2, 2020

Here's a mutation config I have that uses writeFragment in the updater:

export const createSurveyResponseMutation = gql`
	mutation createSurveyResponse($input: CreateSurveyResponseInput!) {
		createSurveyResponse(input: $input) {
			id
			surveyTemplateId
			...SurveyQueryContainer_surveyResponse
		}
	}
	${SurveyQueryContainerGql.surveyResponseFragment}
`;

export const getCreateSurveyResponseMutationConfig = ({ variables }) => ({
		mutation: createSurveyResponseMutation,
		variables,
		optimisticResponse: {
			createSurveyResponse: {
				...variables.input,
				__typename: 'SurveyResponse',
				questionResponses: { __typename: 'ModelQuestionResponseConnection', items: [] },
			},
		},
		update: (store, { data: { createSurveyResponse } }) => {
			store.writeFragment({
				id: `SurveyResponse:${createSurveyResponse.id}`,
				fragment: SurveyQueryContainerGql.surveyResponseFragment,
				fragmentName: 'SurveyQueryContainer_surveyResponse',
				data: createSurveyResponse,
			});
		},
	});

Here's the component using the query I want to update:

import React from 'react';
import PropTypes from 'prop-types';
import { Query } from '@apollo/react-components';
import SurveyNavigator from './SurveyNavigator';
import { SurveyQueryContainerQuery } from './graphql/SurveyQueryContainer';

export default function SurveyQueryContainer({ route, navigation }) {
	const { surveyTemplateId, surveyResponseId } = route.params;

	return (
		<Query
			variables={{ surveyTemplateId, surveyResponseId }}
			query={SurveyQueryContainerQuery}>
			{({ loading, error, data, refetch }) => {
				return <SurveyNavigator surveyTemplate={data.getSurveyTemplate} surveyResponse={data.getSurveyResponse} />;
			}}
		</Query>
	);
}

And here's the query:

import gql from 'graphql-tag';
import { SurveyNavigatorFragments } from '../SurveyNavigator';

export const surveyTemplateFragment = gql`
	fragment SurveyQueryContainer_surveyTemplate on SurveyTemplate {
		id
		name
		...SurveyNavigator_surveyTemplate
	}
	${SurveyNavigatorFragments.surveyTemplate}
`;

export const surveyResponseFragment = gql`
	fragment SurveyQueryContainer_surveyResponse on SurveyResponse {
		id
		...SurveyNavigator_surveyResponse
	}
	${SurveyNavigatorFragments.surveyResponse}
`;

export const SurveyQueryContainerQuery = gql`
	query SurveyQueryContainerQuery($surveyTemplateId: ID!, $surveyResponseId: ID!) {
		getSurveyTemplate(id: $surveyTemplateId) {
			...SurveyQueryContainer_surveyTemplate
		}
		getSurveyResponse(id: $surveyResponseId) {
			...SurveyQueryContainer_surveyResponse
		}
	}
	${surveyTemplateFragment}
	${surveyResponseFragment}
`;

I know the getSurveyTemplate field is in the cache since I've visited this screen before and the data for that field is always the same (the surveyTemplateId variable is always the same). The only thing in the SurveyQueryContainerQuery that changes each time is the surveyResponseId variable and therefore the SurveyResponse. Given this setup, if I fire my createSurveyResponse mutation before visiting the screen with SurveyQueryContainer in it, I would expect the query component to render immediately because I have optimistically updated the cache with the correct SurveyQueryContainer_surveyResponse fragment. I should have all the data needed to satisfy SurveyQueryContainerQuery present in the cache, so it shouldn't need to do a network fetch, yet it is still doing this. Am I doing something wrong here? Why can't it find the fragment I've written?

This seems to work when I change my updater to:

...
update: (store, { data: { createSurveyResponse } }) => {
	let getSurveyTemplate;
	try {
		getSurveyTemplate = store.readFragment({
			id: `SurveyTemplate:${createSurveyResponse.surveyTemplateId}`,
			fragment: SurveyQueryContainerGql.surveyTemplateFragment,
			fragmentName: 'SurveyQueryContainer_surveyTemplate',
		});
	} catch (e) {
		console.warn("Could not read survey template from store", e);
	}

	store.writeQuery({
		query: SurveyQueryContainerGql.SurveyQueryContainerQuery,
		variables: {
			surveyTemplateId: createSurveyResponse.surveyTemplateId,
			surveyResponseId: createSurveyResponse.id,
		},
		data: {
			getSurveyTemplate,
			getSurveyResponse: createSurveyResponse,
		},
	});
},
...

But this is quite a bit more verbose, and seems like it shouldn't be necessary.

@roballsopp
Copy link
Author

My writeFragment example works as expected with a cache redirect:

new ApolloClient({
	...
	cache: new InMemoryCache({
		cacheRedirects: {
			Query: {
				getSurveyResponse: (_, args, { getCacheKey }) => getCacheKey({ __typename: 'SurveyResponse', id: args.id }),
			},
		},
	}),
	...
})

Still very curious why this doesn't seem to be the default behavior, and what I'm breaking by doing this.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant