Apollo-client: New object wrapper on every query prevents object comparison

Created on 2 Nov 2017  路  3Comments  路  Source: apollographql/apollo-client

Intended outcome:
For a query like:

query userData {
  user(username: "example") {
    user {
      id
      posts {
        id
        ...
      }
    }
  }
}

When the query is run multiple times (hitting the cache) I should be able to do object comparison, e.g. this.props.posts[0] === nextProps.posts[0]. Note that each post is a normalized object in Apollo's in memory cache.

Actual outcome:
Each time the query is run new objects are created making object comparison useless.

In v1 this worked great, but in v2 it is broken.

This is useful for perf in PureComponents (and shouldComponentUpdate), as well as any time you want check if the data has changed. When upgrading to v2 this broke my app and I ended up having to do a bunch of id comparisons.

At the very least please update the docs and list this as a breaking change when upgrading. My upgrade went smoothly other than this, which was a major time sink.

Version

performance 馃悶 bug

Most helpful comment

@jbaxleyiii I create a test app for reproducing the other issue but this seems to be reproduceble with it also.
Reproduction app is based on create-react-app and apollo-server-express
(can be cloned from https://github.com/SkReD/apollo-demo.git)
To run it you need to start express server by npm run start-server command and then run
webpack-dev-server by command npm start.

For reproduction simply hit Rerender button multiple times - there would be a log message in console which indicates, that new data is differs from current one.

Code, that check new data is differs from current is contained in componentDidUpdate method:

class Todos extends Component {
    componentDidUpdate(prevProps) {
        if (this.props.data.todos !== prevProps.data.todos) {
            console.log('new data!');
        }
    }
    render() {
        if (this.props.data.loading) {
            return <img src={logo} className="App-logo" alt="logo"/>;
        } else if (this.props.data.error) {
            return JSON.stringify(this.props.data.error, null, 4);
        }
        else {
            return this.props.data.todos.map(todo =>
                <div key={todo.id}>
                    {todo.name}
                    <button onClick={() => this.props.onEdit(todo)}>edit</button>
                </div>
            );
        }
    }
}

const TodosWithData = graphql(todosQuery)(Todos);

Looks like the line https://github.com/apollographql/apollo-client/blob/aaf9916999f43c851ca12d854ee2fe9c43abfc3c/packages/apollo-client/src/core/ObservableQuery.ts#L179 in Observable.ts is responsible for this.

All 3 comments

@rafrex this was definitely not intentional! Would you be able to create a small reproduction so I can fix this for you!

@jbaxleyiii I create a test app for reproducing the other issue but this seems to be reproduceble with it also.
Reproduction app is based on create-react-app and apollo-server-express
(can be cloned from https://github.com/SkReD/apollo-demo.git)
To run it you need to start express server by npm run start-server command and then run
webpack-dev-server by command npm start.

For reproduction simply hit Rerender button multiple times - there would be a log message in console which indicates, that new data is differs from current one.

Code, that check new data is differs from current is contained in componentDidUpdate method:

class Todos extends Component {
    componentDidUpdate(prevProps) {
        if (this.props.data.todos !== prevProps.data.todos) {
            console.log('new data!');
        }
    }
    render() {
        if (this.props.data.loading) {
            return <img src={logo} className="App-logo" alt="logo"/>;
        } else if (this.props.data.error) {
            return JSON.stringify(this.props.data.error, null, 4);
        }
        else {
            return this.props.data.todos.map(todo =>
                <div key={todo.id}>
                    {todo.name}
                    <button onClick={() => this.props.onEdit(todo)}>edit</button>
                </div>
            );
        }
    }
}

const TodosWithData = graphql(todosQuery)(Todos);

Looks like the line https://github.com/apollographql/apollo-client/blob/aaf9916999f43c851ca12d854ee2fe9c43abfc3c/packages/apollo-client/src/core/ObservableQuery.ts#L179 in Observable.ts is responsible for this.

@SkReD thanks for creating this. I've had this on my todo list but haven't gotten to it.

Was this page helpful?
0 / 5 - 0 ratings