Search code examples
reactjsapolloapollo-clientreact-apollo-hooksapollo-boost

Cant Set Apollo Local State with nested values


I'm testing out Apollo Graphql with React and I'm trying to update the local state with Apollo Graphql with a nested object. I'm running into an issue. The data returns a null value and does not even return the value I set as a default. The only warning I see is Missing field __typename. I'm not sure what I'm missing or if this is not how you properly set nested values with Graphql or Apollo issue. I have a code sandbox with the example I'm trying to do https://codesandbox.io/embed/throbbing-river-xwe2y

index.js

import React from "react";
import ReactDOM from "react-dom";
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "@apollo/react-hooks";
import App from "./App";

import "./styles.css";

const client = new ApolloClient({
  clientState: {
    defaults: {
      name: {
        firstName: "Michael",
        lastName: "Jordan"
      }
    },
    resolvers: {},
    typeDefs: `
      type Query {
        name: FullName
      }

      type FullName {
        firsName: String
        lastName: String
      }
    `
  }
});

client.writeData({
  data: {
    name: {
      firstName: "Kobe",
      lastName: "Bryant"
    }
  }
});

const rootElement = document.getElementById("root");
ReactDOM.render(
  <ApolloProvider client={client}>
    <App />
  </ApolloProvider>,
  rootElement
);

App.js

import React from "react";
import Name from "./Name";
import { useApolloClient } from "@apollo/react-hooks";

function App() {
  const client = useApolloClient();

  client.writeData({
    data: {
      name: {
        firstName: "Lebron",
        lastName: "James"
      }
    }
  });
  return (
    <div>
      <Name />
    </div>
  );
}

export default App;

Name.js

import React from "react";
import { NAME } from "./Queries";
import { useApolloClient } from "@apollo/react-hooks";

const Name = async props => {
  const client = useApolloClient();
  const { loading, data } = await client.query({ query: NAME });
  console.log(data);

  return <div>Hello {data.name.firstName}</div>;
};

export default Name;

QUERIES.js

import gql from "graphql-tag";

export const GET_NAME = gql`
  {
    name @client {
      firstName
      lastName
    }
  }
`;

Solution

  • Unfortunately, Apollo Client's documentation is not good in this manner and simply starts using __typename without properly explaining the reasoning behind it directly. I've seen other engineers struggling to understand its purpose before. As the warning is suggesting, you must pass a __typename property to objects you write directly to the cache, as Apollo Client will use this value by default in its data normalization process internally, to save/identify the data.

    On all your calls to client.writeData, you should include a __typename property, like:

    client.writeData({
      data: {
        name: {
          __typename: 'FullName', // this is the name of the type this data represents, as you defined in your typeDefs
          firstName: 'Lebron',
          lastName: 'James',
        },
      },
    });
    

    Also, you can't use async/await on the render method of your component -- in the case of function components, the main body itself, as Promises are not valid React elements. So you have two options:

    • switch from client.query to the useQuery hook; or
    • since you're only requesting client-side fields, you can use the client.readQuery method which is synchronous and will return the data to you without a Promise. Note that with this method you're only able to make client-side requests, i.e if you want to request client and server fields at the same time, it won't work.