React Apollo: Cannot read property of undefined when two components rendered at the same time

Viewed 2440

I ran into an issue with the react-apollo package:

  • I have 2 components that query the same graphql object
  • Each component works well independently, without error
  • When I render both, I get the Cannot read property of undefined error

I was able to recreate the error with this demo code. Please find it here as well as below: https://codesandbox.io/s/qvq0y25384

What I did try without success

  • Using query aliases resulted in the exact same behavior
  • Using fetchPolicy="no-cache" did work but is not acceptable for my real world use case where the 2 components need to use the cache for performance reasons.
  • Using shared fragments did work but is not acceptable for my real world use case where this slows down both components on every page

Error that happens only when the 2 components are rendered

PlanetPhysics.js? [sm]:27 Uncaught TypeError: Cannot read property 'planets' of undefined
    at eval (PlanetPhysics.js? [sm]:27)
    at Query.render (react-apollo.browser.umd.js:479)
    at finishClassComponent (react-dom.development.js:13194)
    at updateClassComponent (react-dom.development.js:13156)
    at beginWork (react-dom.development.js:13825)
    at performUnitOfWork (react-dom.development.js:15864)
    at workLoop (react-dom.development.js:15903)
    at HTMLUnknownElement.callCallback (react-dom.development.js:100)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:138)
    at invokeGuardedCallback (react-dom.development.js:187)
    at replayUnitOfWork (react-dom.development.js:15311)
    at renderRoot (react-dom.development.js:15963)
    at performWorkOnRoot (react-dom.development.js:16561)
    at performWork (react-dom.development.js:16483)
    at performSyncWork (react-dom.development.js:16455)
    at requestWork (react-dom.development.js:16355)
    at scheduleWork$1 (react-dom.development.js:16219)
    at Object.enqueueForceUpdate (react-dom.development.js:11337)
    at Query.Component.forceUpdate (react.development.js:288)
    at Query._this.updateCurrentData (react-apollo.browser.umd.js:371)
    at Object.next (react-apollo.browser.umd.js:342)
    at notifySubscription (Observable.js:126)
    at onNotify (Observable.js:161)
    at SubscriptionObserver.next (Observable.js:215)
    at eval (bundle.umd.js:429)
    at Array.forEach (<anonymous>)
    at Object.next (bundle.umd.js:429)
    at eval (bundle.umd.js:1123)
    at eval (bundle.umd.js:1414)
    at Array.forEach (<anonymous>)
    at eval (bundle.umd.js:1413)
    at Map.forEach (<anonymous>)
    at QueryManager.broadcastQueries (bundle.umd.js:1408)
    at Object.next (bundle.umd.js:1465)
    at notifySubscription (Observable.js:126)
    at onNotify (Observable.js:161)
    at SubscriptionObserver.next (Observable.js:215)
    at eval (bundle.umd.js:62)
    at Array.forEach (<anonymous>)
    at Object.next (bundle.umd.js:62)
    at notifySubscription (Observable.js:126)
    at onNotify (Observable.js:161)
    at SubscriptionObserver.next (Observable.js:215)
    at eval (bundle.umd.js:95)

The above error occurred in the <Query> component:
    in Query (created by PlanetPhysics)
    in PlanetPhysics (created by AppERROR1)
    in ApolloProvider (created by AppERROR1)
    in AppERROR1

Full code to recreate the error

index.js

import React from "react";
import ReactDOM from "react-dom";
import { ApolloProvider } from "react-apollo";
import { createClient } from "./client";
import { PlanetPhysics } from "./PlanetPhysics";
import { PlanetDemographics } from "./PlanetDemographics";

const client = createClient({});

/* The error only happens when both components are rendered
 * Uncaught TypeError: Cannot read property 'planets' of undefined
 */

function AppOK1() {
  return (
    <ApolloProvider client={client}>
      <PlanetPhysics />
    </ApolloProvider>
  );
}
function AppOK2() {
  return (
    <ApolloProvider client={client}>
      <PlanetDemographics />
    </ApolloProvider>
  );
}
function AppERROR1() {
  return (
    <ApolloProvider client={client}>
      <PlanetPhysics />
      <PlanetDemographics />
    </ApolloProvider>
  );
}
function AppERROR2() {
  return (
    <ApolloProvider client={client}>
      <PlanetDemographics />
      <PlanetPhysics />
    </ApolloProvider>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<AppOK1 />, rootElement);
// ReactDOM.render(<AppOK2 />, rootElement);
// ReactDOM.render(<AppERROR1 />, rootElement);
// ReactDOM.render(<AppERROR2 />, rootElement);

client.js

import { ApolloClient } from "apollo-client";
import { InMemoryCache } from "apollo-cache-inmemory";
import { HttpLink } from "apollo-link-http";

export function createClient(initialState) {
  return new ApolloClient({
    connectToDevTools: true,
    ssrMode: false,
    link: new HttpLink({
      uri: "https://prevostc-swapi-graphql.herokuapp.com"
    }),
    cache: new InMemoryCache({
      dataIdFromObject: object => {
        const identifier = object.uuid || JSON.stringify(object);
        return object.__typename + "-" + identifier || null;
      }
    }).restore(initialState || {})
  });
}

PlanetPhysics.js

import React from "react";
import gql from "graphql-tag";
import { Query } from "react-apollo";
import { DataBlock } from "./DataBlock";

const PHYSICS_QUERY = gql`
  query {
    allPlanets(first: 4) {
      planets {
        id
        name
        diameter
        rotationPeriod
      }
    }
  }
`;

export function PlanetPhysics() {
  return (
    <Query query={PHYSICS_QUERY}>
      {({ loading, error, data }) => {
        if (loading) return "Loading...";
        if (error) return `Error! ${error.message}`;
        return (
          <div>
            {data.allPlanets.planets.map(p => (
              <DataBlock data={p} title={`Physics: ${p.name}`} key={p.id} />
            ))}
          </div>
        );
      }}
    </Query>
  );
}

PlanetDemographics.js

import React from "react";
import gql from "graphql-tag";
import { Query } from "react-apollo";
import { DataBlock } from "./DataBlock";

const DEMOGRAPHICS_QUERY = gql`
  query {
    allPlanets(first: 4) {
      planets {
        id
        name
        population
      }
    }
  }
`;

export function PlanetDemographics() {
  return (
    <Query query={DEMOGRAPHICS_QUERY}>
      {({ loading, error, data }) => {
        if (loading) return "Loading...";
        if (error) return `Error! ${error.message}`;
        return (
          <div>
            {data.allPlanets.planets.map(p => (
              <DataBlock
                data={p}
                title={`Demographics: ${p.name}`}
                key={p.id}
              />
            ))}
          </div>
        );
      }}
    </Query>
  );
}

DataBlock.js

import React from "react";

export function DataBlock({ title, data }) {
  return (
    <div
      style={{
        border: "1px solid black",
        borderRadius: "5px",
        marginBottom: "10px",
        marginRight: "10px",
        padding: "10px",
        maxWidth: "220px",
        display: "inline-block"
      }}
    >
      <h3 style={{ margin: "5px 5px", textAlign: "center" }}>{title}</h3>
      <table>
        <tbody>
          {Object.keys(data).map(key => (
            <tr key={key}>
              <td style={{ textAlign: "right" }}>{key}: </td>
              <td>{data[key]}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
0 Answers
Related