Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unexpected behavior in writing to InMemoryCache during SSR #6747

Closed
SiddharthMantri opened this issue Jul 30, 2020 · 6 comments
Closed

Unexpected behavior in writing to InMemoryCache during SSR #6747

SiddharthMantri opened this issue Jul 30, 2020 · 6 comments

Comments

@SiddharthMantri
Copy link
Contributor

SiddharthMantri commented Jul 30, 2020

Issue submitted in collaboration with: @chrisbmar

Using Apollo v3. These are the steps that i have been able to identify in reproducing this bug:

  • Write an item to cache during SSR that already has typeDefs on the server
  • If this type exists on the server and the component that is being SSRed queries this, it'll overwrite the cache with data from the server EVEN if the id is different.

Minimum working example that runs in express:

On express

import {
  gql,
  ApolloProvider,
  InMemoryCache,
  ApolloClient,
  useQuery,
  createHttpLink,
} from "@apollo/client";
import { renderToStringWithData } from "@apollo/client/react/ssr";
import React from "react";
import express from "express";
import { ApolloServer } from "apollo-server-express";
import fetch from "cross-fetch";

const BOOKS = [
  {
    __typename: "Book",
    id: "1",
    book: "Harry Potter",
    author: {
     __typename: "Author",
      name: "JK Rowling",
    },
  },
];

const typeDefs = gql`
  type Book {
    book: String!
    author: Author
    id: ID!
  }
  type Author {
    name: String
  }
  type Query {
    books: [Book]
  }
`;

const resolvers = {
  Query: {
    books: (parent, args, context) => {
      return BOOKS;
    },
  },
};

const GET_BOOKS = gql`
  query GetBooks {
    books {
      id
      book
      author
    }
  }
`;

const TestComponent = () => {
  const { data: bookData, loading, error } = useQuery(GET_BOOKS);

  /**
   * What is the expected render here?
   * Is it just Harry Potter or is it an array of Harry Potter and Lord of the Rings
   */
  return <div>{JSON.stringify(bookData)}</div>;
};

const expressServer = () => {
  const app = express();
  const apolloServer = new ApolloServer({ typeDefs, resolvers });

  apolloServer.applyMiddleware({ app });

  // SSR Middleware
  app.use((req, res, next) => {
    const link = createHttpLink({
      uri: "http://localhost:3000/graphql",
      fetch, //cross-fetch
    });
    const cache = new InMemoryCache();

    const book = {
      __typename: "Book",
      id: "1000",
      book: "Lord of the Rings",
      author: {
        __typename: "Author",
        name: "Tolkien",
      },
    };

    cache.writeQuery({
      query: gql`
        {
          books
        }
      `,
      data: {
        books: [book],
      },
    });

    const client = new ApolloClient({
      ssrMode: true,
      link,
      cache,
    });

    const appTree = (
      <ApolloProvider client={client}>
        <TestComponent />
      </ApolloProvider>
    );

    console.log(client.cache.extract()); // The root query object contains the books with __ref pointing to the ID of Lord of the Rings

    renderToStringWithData(appTree).then((content) => {
      const initialState = client.cache.extract();
      console.log(client.cache.extract()); // The root query object has been overwritten by the __ref pointing to Harry Potter
      const html = `
              <!doctype html>
              <html>
                  <head />
                  <body>
                  <div id="root">${content}</div>
                      <script>window.__APOLLO_STATE__=${JSON.stringify(
                        initialState
                      ).replace(/</g, "\\u003c")}</script>
                      <script src="/main.js"></script>
                  </body>
              </html>`;

      res.status(200);
      res.send(html);
      res.end();
    });
  });
  app.listen(9000, (...cb) => {
    if (cb && cb.length) {
      // eslint-disable-next-line no-console
      console.error(cb);
    }
    // eslint-disable-next-line no-console
    console.log(`listening on port: ${9000}`);
  });
};

expressServer();
@benjamn
Copy link
Member

benjamn commented Aug 5, 2020

What version of @apollo/client are you using? Since you're priming the cache before rendering, and the default fetch policy is cache-first, I would expect the initial data (LoTR) to be rendered without a network request, but @apollo/client@3.1.0 fixed a regression (#6710) that may be affecting this behavior.

@SiddharthMantri
Copy link
Contributor Author

Apologies for getting back late on this. If it is supposed to be LoTR, is there ever a way to honor cache data AND have data from the network? Basically have both LoTR and Harry Potter in the data that is rendered?

@benjamn
Copy link
Member

benjamn commented Aug 18, 2020

@SiddharthMantri In theory, using fetchPolicy: "cache-and-network" should give you that, though I suspect the SSR result will end up looking like you just did a network fetch (because the cache data will be superseded by the network data):

const { data: bookData, loading, error } = useQuery(GET_BOOKS, {
  fetchPolicy: "cache-and-network",
});

@SiddharthMantri
Copy link
Contributor Author

You are right about the superseded data - since that is exactly what we get back in the results. The cached data is overwritten. I assume there is no way to prevent the overwrite during SSR?

We have (what we think is) a valid use case where the same data type needs to come from different sources, once before SSR by writing to cache using an internal API, and once on SSR where the query is resolved against another graphql service. The final values displayed should appear as if they were from a single source where the end user doesn't care about the underlying source.

@benjamn
Copy link
Member

benjamn commented Apr 13, 2021

@SiddharthMantri Sorry for the very late follow-up, but this PR may be relevant to this issue: #7983

@hwillson
Copy link
Member

hwillson commented May 4, 2021

Let us know if this is still a concern with @apollo/client@latest - thanks!

@hwillson hwillson closed this as completed May 4, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 15, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants