Search code examples
reactjsgraphqlreact-apolloapollo-clientgraphene-django

updateMutation doesn't update data


My goal is to update and display new data for {data.contact.contactInformation} automatically, once I click the button for the updateMutation. However, it only works once I refresh the page. From my understanding, the IDs must be returned and then Apollo should handle the rest, according to the Apollo data mutation docs.

But I seem to have some mistake in my code. Do you see what's wrong with it?

const FEED_QUERY = gql`
  query Contact($id: Int!) {
    contact(id: $id) {
      id
      contactInformation
      belongsTo {
        id
        username
        email
      }
    }
  }
`;

const UPDATE_CONTACT_DETAILS = gql`
  mutation updateContactDetails($contactInformation: String!) {
    updateContactDetails(contactInformation: $contactInformation) {
      id
      contactInformation
      belongsTo {
        id
        username
        email
      }
    }
  }
`;

function EmergencyContact() {
  const params = useParams();
  const { loading, error, data } = useQuery(FEED_QUERY, {
    variables: { id: params.contactId }
  });
  const [updateContactDetails] = useMutation(UPDATE_CONTACT_DETAILS);
  const [value, setValue] = useState("");

  if (loading) return "Loading...";
  if (error) return `Error! ${error.message}`;

  return (
    <>
      <div key={data.contact.id}>
        {data.contact.contactInformation}
        <form
          onSubmit={e => {
            e.preventDefault();
            updateContactDetails({
              variables: {
                contactInformation: value
              }
            });
            setValue("");
          }}
        >
          <input value={value} onChange={e => setValue(e.target.value)} />
          <button type="submit">Update Contact Information</button>
        </form>
      </div>
    </>
  );
}

export default EmergencyContact;

schema.py

class ContactInformationType(DjangoObjectType):
    class Meta:
        model = ContactInformation


class Query(graphene.ObjectType):
    contact = graphene.Field(ContactInformationType, id=graphene.Int())
    contact_access_key = graphene.Field(
        ContactInformationType, access_key=graphene.String()
    )
    contact_informations = graphene.List(ContactInformationType)

    @staticmethod
    def resolve_contact_informations(parent, info, **kwargs):
        return ContactInformation.objects.all()

    @staticmethod
    def resolve_contact_access_key(parent, info, **kwargs):
        access_key = kwargs.get("access_key")

        if not access_key:
            return

        contact_info = ContactInformation.objects.filter(access_key=access_key).first()
        if info.context.user != contact_info.belongs_to:
            raise PermissionDenied()

        return contact_info

    @staticmethod
    @login_required
    def resolve_contact(parent, info, **kwargs):
        id = kwargs.get("id")
        if id is not None:
            contact_info = ContactInformation.objects.filter(belongs_to__pk=id).first()
            if info.context.user != contact_info.belongs_to:
                raise PermissionDenied()

            return contact_info


class UpdateContactDetails(graphene.Mutation):
    id = graphene.ID()
    contact_information = graphene.String()
    belongs_to = graphene.Field(UserType)

    class Arguments:
        contact_information = graphene.String()

    @staticmethod
    def mutate(self, info, contact_information):
        contact_detail = ContactInformation.objects.get(belongs_to=info.context.user)
        contact_detail.contact_information = contact_information
        contact_detail.save()
        return UpdateContactDetails(
            id=contact_detail.pk,
            contact_information=contact_detail.contact_information,
            belongs_to=contact_detail.belongs_to,
        )


class Mutation(graphene.ObjectType):
    update_contact_details = UpdateContactDetails.Field()

Solution

  • Apollo uses both the id and the __typename to generate the cache key. So a User with an id of 1 and a Post also with an id of 1 will not share the same key -- their keys will be User:1 and Post:1 respectively. If your user field and your updateContactDetails return different types (even if those types have the exact same fields), they will end up with different cache keys and so Apollo will not know it needs to update the user query. This should be fixed on the backend (i.e. make sure both fields have the same type) -- if it can't be fixed, you'll need to manually update the cache by providing an update function to your useMutation hook.