From f4172d275087eff06ccd8ef96a305480450ae906 Mon Sep 17 00:00:00 2001 From: Daniel Date: Tue, 29 Jun 2021 10:24:33 -0700 Subject: [PATCH 1/2] unmounting will prevent a write to cache --- package-lock.json | 116 +++++++++++++++++++++++++++++++++++----------- package.json | 2 +- server.js | 29 +++++++----- src/index.tsx | 89 +++++++++++++++++++++++++---------- 4 files changed, 173 insertions(+), 63 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2e64c52..61726ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,23 +5,55 @@ "requires": true, "dependencies": { "@apollo/client": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.2.5.tgz", - "integrity": "sha512-zpruxnFMz6K94gs2pqc3sidzFDbQpKT5D6P/J/I9s8ekHZ5eczgnRp6pqXC86Bh7+44j/btpmOT0kwiboyqTnA==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.3.20.tgz", + "integrity": "sha512-hS7UmBwJweudw/J3M0RAcusMHNiRuGqkRH6g91PM2ev8cXScIMdXr/++9jo7wD1nAITMCMF4HQQ3LFaw/Or0Bw==", "requires": { "@graphql-typed-document-node/core": "^3.0.0", "@types/zen-observable": "^0.8.0", - "@wry/context": "^0.5.2", - "@wry/equality": "^0.2.0", + "@wry/context": "^0.6.0", + "@wry/equality": "^0.5.0", "fast-json-stable-stringify": "^2.0.0", - "graphql-tag": "^2.11.0", + "graphql-tag": "^2.12.0", "hoist-non-react-statics": "^3.3.2", - "optimism": "^0.13.0", + "optimism": "^0.16.0", "prop-types": "^15.7.2", - "symbol-observable": "^2.0.0", - "ts-invariant": "^0.4.4", + "symbol-observable": "^4.0.0", + "ts-invariant": "^0.7.0", "tslib": "^1.10.0", "zen-observable": "^0.8.14" + }, + "dependencies": { + "graphql-tag": { + "version": "2.12.5", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.5.tgz", + "integrity": "sha512-5xNhP4063d16Pz3HBtKprutsPrmHZi5IdUGOWRxA2B6VF7BIRGOHZ5WQvDmJXZuPcBg7rYwaFxvQYjqkSdR3TQ==", + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + } + } + }, + "ts-invariant": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.7.5.tgz", + "integrity": "sha512-qfVyqTYWEqADMtncLqwpUdMjMSXnsqOeqGtj1LeJNFDjz8oqZ1YxLEp29YCOq65z0LgEiERqQ8ThVjnfibJNpg==", + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + } + } + } } }, "@apollo/protobufjs": { @@ -3316,9 +3348,9 @@ "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==" }, "@types/zen-observable": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.1.tgz", - "integrity": "sha512-wmk0xQI6Yy7Fs/il4EpOcflG4uonUpYGqvZARESLc2oy4u69fkatFLbJOeW4Q6awO15P4rduAe6xkwHevpXcUQ==" + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.2.tgz", + "integrity": "sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg==" }, "@typescript-eslint/eslint-plugin": { "version": "4.7.0", @@ -3568,19 +3600,48 @@ } }, "@wry/context": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.5.2.tgz", - "integrity": "sha512-B/JLuRZ/vbEKHRUiGj6xiMojST1kHhu4WcreLfNN7q9DqQFrb97cWgf/kiYsPSUCAMVN0HzfFc8XjJdzgZzfjw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@wry/context/-/context-0.6.0.tgz", + "integrity": "sha512-sAgendOXR8dM7stJw3FusRxFHF/ZinU0lffsA2YTyyIOfic86JX02qlPqPVqJNZJPAxFt+2EE8bvq6ZlS0Kf+Q==", "requires": { - "tslib": "^1.9.3" + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + } } }, "@wry/equality": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.2.0.tgz", - "integrity": "sha512-Y4d+WH6hs+KZJUC8YKLYGarjGekBrhslDbf/R20oV+AakHPINSitHfDRQz3EGcEWc1luXYNUvMhawWtZVWNGvQ==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.1.tgz", + "integrity": "sha512-FZKbdpbcVcbDxQrKcaBClNsQaMg9nof1RKM7mReJe5DKUzM5u8S7T+PqwNqvib5O2j2xxF1R4p5O3+b6baTrbw==", "requires": { - "tslib": "^1.9.3" + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + } + } + }, + "@wry/trie": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.3.0.tgz", + "integrity": "sha512-Yw1akIogPhAT6XPYsRHlZZIS0tIGmAl9EYXHi2scf7LPKKqdqmow/Hu4kEqP2cJR3EjaU/9L0ZlAjFf3hFxmug==", + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" + } } }, "@xtuc/ieee754": { @@ -12378,11 +12439,12 @@ } }, "optimism": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.13.0.tgz", - "integrity": "sha512-6JAh3dH+YUE4QUdsgUw8nUQyrNeBKfAEKOHMlLkQ168KhIYFIxzPsHakWrRXDnTO+x61RJrS3/2uEt6W0xlocA==", + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.16.1.tgz", + "integrity": "sha512-64i+Uw3otrndfq5kaoGNoY7pvOhSsjFEN4bdEFh80MWVk/dbgJfMv7VFDeCT8LxNAlEVhQmdVEbfE7X2nWNIIg==", "requires": { - "@wry/context": "^0.5.2" + "@wry/context": "^0.6.0", + "@wry/trie": "^0.3.0" } }, "optimize-css-assets-webpack-plugin": { @@ -16529,9 +16591,9 @@ } }, "symbol-observable": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-2.0.3.tgz", - "integrity": "sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==" }, "symbol-tree": { "version": "3.2.4", diff --git a/package.json b/package.json index dbb0113..85e3ff3 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { - "@apollo/client": "^3.2.5", + "@apollo/client": "^3.3.20", "@testing-library/jest-dom": "^5.11.5", "@testing-library/react": "^11.1.2", "@testing-library/user-event": "^12.2.2", diff --git a/server.js b/server.js index c23ad70..9c9b819 100644 --- a/server.js +++ b/server.js @@ -1,22 +1,20 @@ const { ApolloServer, gql } = require('apollo-server-express'); const express = require('express'); -const { v4 } = require('uuid'); const simpleSchema = gql` type Query { - movies(movieIds: [Int!]): [Movie!]! - requestDetails: RequestDetails! + movies: [Movie!]! + } + + type Mutation { + deleteMovie(movieId: Int!): Boolean! } type Movie { movieId: Int! internalTitle: String! } - - type RequestDetails { - id: String! - } `; const fakeMovies = { @@ -37,8 +35,8 @@ const fakeMovies = { internalTitle: 'some movie 80229867', }, 80025678: { - movieId: 80229867, - internalTitle: 'some movie 80229867', + movieId: 80025678, + internalTitle: 'some movie 80025678', }, 80229865:{ movieId: 80229865, @@ -71,14 +69,23 @@ const server = new ApolloServer({ resolvers: { Query: { movies: (root, args, context, info) => { - const movieIds = args.movieIds; return new Promise(resolve => { setTimeout(() => { - resolve(movieIds.map(movieId => fakeMovies[movieId])); + resolve(Object.values(fakeMovies)); }, 2000); }); }, }, + Mutation: { + deleteMovie: (root, args, context, info) => { + delete fakeMovies[args.movieId]; + return new Promise(resolve => { + setTimeout(() => { + resolve(true); + }, 100); + }); + } + } } }); const app = express(); diff --git a/src/index.tsx b/src/index.tsx index e2c4270..d6796dd 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,55 +1,96 @@ import React from "react"; import ReactDOM from "react-dom"; import gql from "graphql-tag"; -import { BrowserRouter, Link, Route, Switch } from 'react-router-dom'; +import { BrowserRouter, Route, Switch, useHistory } from 'react-router-dom'; -import { ApolloClient, HttpLink, InMemoryCache, ApolloProvider, useQuery} from "@apollo/client"; +import { ApolloClient, HttpLink, InMemoryCache, ApolloProvider, useQuery, useMutation} from "@apollo/client"; +import { useCallback } from "react"; +import { useEffect } from "react"; +import { useState } from "react"; const client = new ApolloClient({ cache: new InMemoryCache(), link: new HttpLink({ uri: "/graphql" }), - defaultOptions: { - watchQuery: { - fetchPolicy: 'cache-and-network', - nextFetchPolicy: 'cache-first', - } - } }); -// Removing "requestDetails" from here will make the React warnings go away. const GET_MOVIES = gql(` -query GetMovies($movieIds: [Int!]!) { - movies(movieIds: $movieIds) { +query GetMovies { + movies { movieId internalTitle } } `); -function FooBarPage() { +const DELETE_MOVIE = gql(` +mutation DeleteMovie($movieId: Int!) { + deleteMovie(movieId: $movieId) +} +`); + +function AnotherPageWithMovies() { + const { data } = useQuery(GET_MOVIES, { + fetchPolicy: 'cache-only', + nextFetchPolicy: 'cache-only' + }); + + /** + * Force re-rendering so we continue to read from cache after the refetchQueries completes, to demonstrate + * that the cache is not updated. + */ + const [tick, setTick] = useState(0); + useEffect(() => { + const interval = window.setInterval(() => { + setTick((prevTick) => prevTick + 1); + }, 1000); + return () => { + window.clearInterval(interval); + } + }, []); return
-

Foobar Route

- Click me to go back to home and see the query re-fire +

We switched routes and the movies list did not update, tick {tick}

+ {(() => { + if (!data) { + return
loading...
+ } + return + })()}
} -function HomePage() { - const { data, loading } = useQuery(GET_MOVIES, { - variables: { movieIds: [80117456, 80025678] } +function HomePageWithMovies() { + const { data, loading } = useQuery(GET_MOVIES); + const [deleteMovie] = useMutation(DELETE_MOVIE, { + refetchQueries: ['GetMovies'] }); + const history = useHistory(); + const onDeleteMovie = useCallback(async (movieId: number) => { + await deleteMovie({ variables: { movieId }}); + history.push('/foobar'); + }, [deleteMovie, history]) + return (

Home Page Route

- Click me to unmount to another route +
when you delete a movie, we will change routes right after the mutation finishes, but before refetch queries does. the mounted page will have the same list of movies because the cache did not update properly
{(() => { if (loading) { - return
loading (we only want to see this on first mount)...
+ return
loading...
} -