React-apollo: All child Apollo context consumers update unnecessarily every time Provider re-renders

Created on 14 Oct 2019  Â·  11Comments  Â·  Source: apollographql/react-apollo

Intended outcome:

When the component containing my ApolloProvider re-renders, the children only re-render if the ApolloClient instance has changed (by reference equality).

Actual outcome:

Every time the provider is re-rendered, the children re-render too, even if the ApolloClient instance is referentially identical.

How to reproduce the issue:

I think this is because the context value has structure {client: ApolloClient}, the provider creates a new object on every render – see https://github.com/apollographql/react-apollo/blob/4163f28531c70955ca7ad3576330e4b394d04b51/packages/common/src/context/ApolloProvider.tsx#L21

It should be a reasonably straightforward PR to construct this dictionary using useMemo instead, ensuring that the Provider can re-render without every Consumer/useApollo re-rendering unnecessarily.

Version

  System:
    OS: macOS Mojave 10.14.6
  Binaries:
    Node: 11.4.0 - ~/.nvm/versions/node/v11.4.0/bin/node
    Yarn: 1.12.3 - ~/workspace/react-native/node_modules/.bin/yarn
    npm: 6.4.1 - ~/.nvm/versions/node/v11.4.0/bin/npm
  Browsers:
    Chrome: 77.0.3865.120
    Firefox: 68.0.2
    Safari: 13.0.2
  npmPackages:
    @apollo/react-components: ^3.1.1 => 3.1.1
    @apollo/react-hooks: ^3.1.1 => 3.1.1
    apollo: ^2.1.8 => 2.1.8
    apollo-cache-inmemory: ^1.6.3 => 1.6.3
    apollo-cache-persist: ^0.1.1 => 0.1.1
    apollo-client: ^2.6.4 => 2.6.4
    apollo-link-batch-http: ^1.2.13 => 1.2.13
    apollo-link-context: ^1.0.19 => 1.0.19
    apollo-link-http: ^1.5.16 => 1.5.16

Most helpful comment

@macrozone I only use that code for client side rendering. So probably it only works for that purpose and not server side.

i did a simple workaround:

// see https://github.com/apollographql/react-apollo/issues/3595#issuecomment-577586255

import { ApolloProvider, getApolloContext } from "@apollo/react-common";
import { useMemo } from "react";

const CustomApolloProvider = ({ client, children }) => {
  const ApolloContext = getApolloContext();
  const value = useMemo(() => ({ client }), [client]);
  return <ApolloContext.Provider value={value}>{children}</ApolloContext.Provider>;
};
// use normal for ssr
export default process.browser ? CustomApolloProvider : ApolloProvider;

All 11 comments

You just saved my day... I'm having the same issue and I never thought this could be the cause

I've also encountered this issue with @apollo/[email protected]. Before this is fixed on the main branch, I wonder whether it's possible to wrap ApolloProvider into another context provider that memoizes the client?

Any updates on this? I installed 3.2.0-beta.0 but the issue seems to still be there. There is a noticeable lag in my app because of this re-render.

Can confirm, having this issue with @apollo/[email protected]. When using the useToasts() hook to toast when an error occured on a query, the component keeps rerendering in an infinite loop.

can also confirm this

  System:
    OS: macOS 10.15.2
  Binaries:
    Node: 12.13.1 - ~/.nvm/versions/node/v12.13.1/bin/node
    Yarn: 1.19.1 - /usr/local/bin/yarn
    npm: 6.12.1 - ~/.nvm/versions/node/v12.13.1/bin/npm
  Browsers:
    Chrome: 79.0.3945.117
    Firefox: 71.0
    Safari: 13.0.4
  npmPackages:
    @apollo/react-hooks: ^3.0.0 => 3.1.3
    @apollo/react-ssr: ^3.0.0 => 3.1.3
    apollo-boost: ^0.4.3 => 0.4.4
    apollo-cache-inmemory: ^1.6.2 => 1.6.3
    apollo-link-batch-http: ^1.2.13 => 1.2.13
    apollo-link-context: ^1.0.17 => 1.0.19
    apollo-link-error: ^1.1.12 => 1.1.12
    apollo-link-persisted-queries: ^0.2.2 => 0.2.2
    apollo-link-schema: ^1.2.2 => 1.2.4

did anyone found a workaround?

I've also encountered this issue with @apollo/[email protected]. Before this is fixed on the main branch, I wonder whether it's possible to wrap ApolloProvider into another context provider that memoizes the client?

i think the client is usually referential equal, so memoizing it does not make a difference. I think its more of a problem of ApolloProvider itself. e.g. when its children change or its parent rerenders. This should definitly not lead to all useQuery components to rerender

I think this is because the context value has structure {client: ApolloClient}, the provider creates a new object on every render – see

think this is because the context value has structure {client: ApolloClient}, the provider creates a new object on every render – see

https://github.com/apollographql/react-apollo/blob/4163f28531c70955ca7ad3576330e4b394d04b51/packages/common/src/context/ApolloProvider.tsx#L21

It should be a reasonably straightforward PR to construct this dictionary using useMemo instead, ensuring that the Provider can re-render without every Consumer/useApollo re-rendering unnecessarily.

i think that is not the problem, as there is a check that if the client is still the same, it won't create a new object. It might have more to do with the consumers? (just a guess)

I fixed it for my own purpose by writing my own ApolloProvider:

import React, { useMemo } from "react";
import { getApolloContext } from "@apollo/react-common";
export default function MyApolloProvider({ client, children }) {
const ApolloContext = getApolloContext();
const value = useMemo(() => ({ client }), [client]);
return (

);
}

I fixed it for my own purpose by writing my own ApolloProvider:

import React, { useMemo } from "react";
import { getApolloContext } from "@apollo/react-common";
export default function MyApolloProvider({ client, children }) {
const ApolloContext = getApolloContext();
const value = useMemo(() => ({ client }), [client]);
return (

);
}

yeah! this works!

Edit: it seems to break server side rendering. @robertsmit did you experience something similar?

@hwillson do you know if this is fixed in the upcoming version 4? I think this can be a performance killer in certain applications (e.g. in nextjs)

@macrozone I only use that code for client side rendering. So probably it only works for that purpose and not server side.

@macrozone I only use that code for client side rendering. So probably it only works for that purpose and not server side.

i did a simple workaround:

// see https://github.com/apollographql/react-apollo/issues/3595#issuecomment-577586255

import { ApolloProvider, getApolloContext } from "@apollo/react-common";
import { useMemo } from "react";

const CustomApolloProvider = ({ client, children }) => {
  const ApolloContext = getApolloContext();
  const value = useMemo(() => ({ client }), [client]);
  return <ApolloContext.Provider value={value}>{children}</ApolloContext.Provider>;
};
// use normal for ssr
export default process.browser ? CustomApolloProvider : ApolloProvider;

Any Updates?

Was this page helpful?
0 / 5 - 0 ratings