question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Problems with optimisticResponse and reducer updating a list query

See original GitHub issue

I’ve tried to reduce my example to the minimum. Basically, i have this simple schema set up on the server side:

const typeDefs = [`
  type Query {
    items: [Item]
  }

  type Mutation {
    addItem(name: String!): Item
  }

  type Item {
    id: ID!
    name: String!
  }

  schema {
    mutation: Mutation
    query: Query
  }
`];

Item ids are auto-incremented integers. All items are displayed in a list, using the item query defined in the schema above to connect data. When the loading field is set, a loader is displayed instead of the list. Also, a reducer is used to update the query result whenever a result from addItem mutation is incoming:

import React from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
import update from 'immutability-helper';

function Item({ item: { id, name } }) {
  return <li><div>{name}</div><div>ID {id}</div></li>;
}

function ItemList({ data: { items, loading } }) {
  if (loading) return <div>loading...</div>;
  return <ul>{items.map(item => <Item key={item.id} item={item} />)}</ul>;
}

const itemQuery = gql`
  query items {
    items { id name }
  }
`;

export default graphql(itemQuery, {
  options() {
    return {
      reducer: (prev, { operationName, type, result: { data } }) => {
        if (type === 'APOLLO_MUTATION_RESULT' && operationName === 'addItem' && data) {
          return update(prev, { items: { $push: [data.addItem] } });
        }
        return prev;
      },
    };
  },
})(ItemList);

Right below the item list is a simple form used to add new items to the list. This addItem mutation invoked by this form is set up with an optimisticReponse that generates a temporary uuid as the items id (instead of the server generated auto-incrementing integers):

import React from 'react';
import uuid from 'uuid/v4';
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';

class ItemForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = { name: '' };

    this.handleNameChange = this.handleNameChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleNameChange(event) {
    this.setState({ name: event.target.value });
  }

  handleSubmit(event) {
    this.props.add(this.state.name);
    this.setState({ name: '' });
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input type="text" name="name" value={this.state.name} onChange={this.handleNameChange} />
        <input type="submit" value="Add Item" />
      </form>
    );
  }
}

const addItem = gql`
  mutation addItem($name: String!) {
    addItem(name: $name) {
      id name
    }
  }
`;

export default graphql(addItem, {
  props: ({ mutate }) => ({
    add: name => mutate({
      variables: { name },
      optimisticResponse: {
        __typename: 'Mutation',
        addItem: {
          name,
          __typename: 'Item',
          id: uuid(),
        },
      },
    }),
  }),
})(ItemForm);

To be able to test things out, i added an artificial delay of 5 seconds to the addItem mutation resolver on the server. When i add a single item, the optimisticResponse is immediately reflected in the item list and 5 seconds later, the server id replaces the generated temporary uuid.

When i add another item while the first mutation is not resolved, things start going south. The list already has the (temporary optimisticResponse) first added item. Once the first mutation resolves, the list goes to a waiting state - and i don’t know why.

I’ve added a short video of this with Apollo Dev Tools opened so you can follow my writings. Whatever happens triggers the loading field. In the store, the temporary item is replaced with the server-approved item once the mutation is resolved, but the ROOT_QUERY, which is inspected in the dev tools, somehow retains the old temporary item until all stacked/parallel optimisticResponse mutations are resolved. This is reproducible with more than 2 mutations in parallel aswell.

apollo-optimisticresponse-problem

Why? What i would’ve expected is each item being added to the list immediately in its temporary optimisticResponse version and being replaced after 5 seconds when the server resolved the mutation - not being affected by any other mutations.

Issue Analytics

  • State:closed
  • Created 7 years ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
codepunktcommented, Dec 30, 2016

@helfer Thanks for looking into this - very appreciated!

It does not stay in loading after all mutations are resolved - it displays the mutation results. Something weird seems to happen in the store. Try to look at the attached gif a few times and you will notice that once the first of two mutations resolves, the resulting item is duplicated in the store because the optimisticResponse item still persists even though the item from the server resolve was received.

Using $unshift instead of $push has the same problem.

You can find a repo with my reproduction at https://github.com/codepunkt/apollo-optimisticresult-problem-reproduction/

1reaction
helfercommented, Dec 30, 2016

@codepunkt Thanks for the really thorough problem description! In short, what I read is that the following happens:

  1. You make a first mutation with optimistic update, the first optimistic item is shown
  2. You make a second mutation with optimistic update, both optimistic items are shown
  3. The first mutation resolves with the actual result, but your UI now shows the loading state
  4. The second mutation resolves with the actual result, but the UI still shows the loading state.

I’m not 100% sure about number 4. Does it stay in loading after all mutations are resolved, or does the UI actually display the real mutation results?

What you found sounds like it could be related to #1039, but in your case I’m 99% sure that the problem is due to some bug in Apollo Client. But just out of curiosity, could you try running your example with $unshift instead of $push. I don’t think it should make a difference, but if it did, it would be a useful piece of information.

Is there any chance that you could make a small github repo with your reproduction? That would be super helpful, because I could then run it with a debugger and see what’s really happening inside Apollo Client.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Optimistic response not working when adding items to list
Adding a new list with optimistic response works perfectly: const [addListMutation] = useAddListMutation({ update: (cache, ...
Read more >
Manual Cache Updates - Redux Toolkit
RTK Query > Usage > Manual Cache Updates: Updating cached data ... After a mutation, updating a single item in a large list...
Read more >
Working Up to an Optimistic Response in Apollo Client with ...
Iteration #1 - Todo list updated on refresh · App - parent component · TodoAdd - component that can add a Todo (using...
Read more >
Tutorial: Speeding up GraphQL Mutations with optimistic UI
value }, update: (store, { data: { addChannel } }) => { // Read the data from the cache for this query. const...
Read more >
Building a GraphQL-powered mobile application With React
To display the list of tweets, I must therefore create: ... The query argument must be a GraphQL document, ... Problem solved?
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found