QueryRenderer doesn't update after a mutation is committed

1k views Asked by At

I am very new to relay and GraphQL and looking to emulate the behaviour of after adding a mutation, my component updates with the new mutation. For example, when I add an animal, it then shows up on my animal list.

I have read a bit about manually updating the store via optimistic updates, fetch container etc, however wondering if this behaviour can occur by using just a QueryRenderer HOC and a simple mutation.

The mutation works successfully, however I can only see the update after I refresh that page.

This is the code for the root query render:

export const Customer = ({ userId }) => {
  return (
    <QueryRenderer
      environment={environment}
      query={query}
      variables={{ userId }}
      render={({ error, props }) => {
        //handle errors and loading
        const user = props.customers_connection.edges[0].node
        return (
          <div className="flex justify-between items-center">
            <h1 className="text-xl">Pets</h1>
            <AddPetForm />
            <PetList user={user} />
          </div>
        )
      }}
    />
  )
}

const query = graphql`
  query CustomerQuery($userId: bigint) {
    customers_connection(where: { userId: { _eq: $userId } }) {
      edges {
        node {
          id
          userId
          pets {
            id
            animal {
              ...Animal_animal
            }
          }
        }
      }
    }
  }
`

Here is the Animal component that lives in the PetList component. The PetList component is the one I am expect to re-render, to include a new Animal component with the animal created with the mutation.


const Animal = ({ animal }) => {

  return (
    <li>
        <Summary animal={animal} />
    </li>
  )
}

export default createFragmentContainer(Animal, {
  animal: graphql`
    fragment Animal_animal on animals {
      name
      category
      imageUrl
      weight
      type
    }
  `
})

Here is the AddPetForm component:

const AddPetForm = () => {
  const { hide } = useModal()
  return (
    <Modal>
      <QueryRenderer
        environment={environment}
        query={query}
        variables={{}}
        render={({ error, props }) => {
          //handle errors and loading 
          return (
            <Form
              hide={hide}
              //pass down props from query
            />
          )
        }}
      />
    </Modal>
  )
}

const query = graphql`
  query AddPetFormQuery {
    enum_animal_category_connection {
      edges {
        node {
          id
          value
        }
      }
    }
    //other enums to use in the form
  }
`

And the code for the mutation (this lives in the AddPetForm component):

const Form = ({ hide, ...other props }) => {
  const { handleSubmit, register, errors, } = useForm({ defaultValues, resolver })
  const [mutationIsInFlight, setMutationIsInFlight] = useState(false)
  const [mutationHasError, setMutationHasError] = useState(false)

  const onCompleted = (response, error) => {
    setMutationIsInFlight(false)
    if (error) onError(error)
    else showSuccessNotification()
  }

  const onError = error => {
    setMutationIsInFlight(false)
    setMutationHasError(true)
  }

  const onSubmit = animal => {
    setMutationIsInFlight(true)
    setMutationHasError(false)
    //assume animal has already been added
    addAnimalAsPet({ animalId: animal.animalId, userId, onCompleted, onError })
    
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="flex flex-wrap">
      // all of the inputs
      <div className="mt-3 w-full">
        <PrimaryButton type="submit" fullWidth={true} loading={mutationIsInFlight}>
          Save
        </PrimaryButton>
      </div>
      {mutationHasError && <p className="text-red-700 my-3">An error has occurred. Please try again.</p>}
    </form>
  )
}

And the mutation code:

const mutation = graphql`
  mutation addAnimalAsPet Mutation($animalId: Int, $userId: Int) {
    insert_pets_one(object: { animalId: $animalId, userId: $userId }) {
      id
    }
  }
`

export const addAnimalAsPet = ({ userId, animalId, onCompleted, onError }) => {
  const variables = { userId, animalId }
  commitMutation(environment, {
    mutation,
    variables,
    onCompleted,
    onError
  })
}


Is there something obvious I am doing wrong? As you can see, there is a nested QueryRenderer HOC, is this breaking the automatic update somehow?

I have not used a subscription as I do not need real time event listening. This data would only be updated when the user adds a new pet, and this doesn't occur very frequently. And I haven't used the optimistic updates/response as there isn't really a need to show the user the update is successful before waiting for the response.

From the docs it looks like there is a way to do this using a Refetch Container, however will have to refactor my code to use a fragment.

1

There are 1 answers

0
Charklewis On BEST ANSWER

The suggestion by xadm worked like a charm. There is a retry function that is a property of the object in the QueryRenderer render prop.

Here is a working example:

<QueryRenderer
  environment={environment}
  query={query}
  variables={{ userId }}
  render={({ error, props, retry }) => {
    //handle errors and loading
    const user = props.customers_connection.edges[0].node
    return (
      <div className="flex justify-between items-center">
        <h1 className="text-xl">Pets</h1>
        <AddPetForm refreshCustomerQuery={retry} />
        <PetList user={user} />
      </div>
    )
  }}
/>

I then simply call the refreshCustomerQuery function just after a successful mutation.

const onCompleted = (response, error) => {
  setMutationIsInFlight(false)
  if (error) onError(error)
  else {
    showSuccessNotification()
    refreshCustomerQuery()
  }
}