From 193e993095c7210f4020e0d357d5a0260de2c7e8 Mon Sep 17 00:00:00 2001 From: Matchu Date: Thu, 15 Sep 2022 04:45:44 -0700 Subject: [PATCH] A very hacky hack for SSR cache merges Ok! I think I got it! It's very very nastly tho lmao! But this will merge in the new SSR-provided data before the new page can render, instead of having it sometimes make redundant network requests & show loading spinners in the meantime for data that Next.js already fulfilled for it. Nasty nasty lil trick. But it seems to be working! Let's see how it does lmao --- pages/_app.tsx | 61 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/pages/_app.tsx b/pages/_app.tsx index 5f0211b..9a3761b 100644 --- a/pages/_app.tsx +++ b/pages/_app.tsx @@ -41,15 +41,6 @@ export default function DTIApp({ Component, pageProps }: AppPropsWithLayout) { const renderWithLayout = Component.renderWithLayout ?? renderWithDefaultLayout; - // Store the *first* value of initialCacheState we get into our cache, - // because we don't want to rebuild our client and flush out everything else - // when the page changes. We set it in state here then never touch it again! - // TODO: Is there a clever way to *add* to our cache each time? The Apollo - // API suggests not really, but maybe there's a clever option... - const [initialCacheState, unusedSetInitialCacheState] = React.useState( - pageProps.graphqlState ?? {} - ); - React.useEffect(() => setupLogging(), []); return ( @@ -70,12 +61,12 @@ export default function DTIApp({ Component, pageProps }: AppPropsWithLayout) { audience="https://impress-2020.openneo.net/api" scope="" > - + {renderWithLayout()} - + ); @@ -85,12 +76,12 @@ function renderWithDefaultLayout(children: JSX.Element) { return {children}; } -function ApolloProviderWithAuth0({ +function DTIApolloProvider({ children, - initialCacheState, + additionalCacheState, }: { children: React.ReactNode; - initialCacheState: NormalizedCacheObject; + additionalCacheState: NormalizedCacheObject; }) { const auth0 = useAuth0(); const auth0Ref = React.useRef(auth0); @@ -99,6 +90,12 @@ function ApolloProviderWithAuth0({ auth0Ref.current = auth0; }, [auth0]); + // Save the first `additionalCacheState` we get as our `initialCacheState`, + // which we'll use to initialize the client without having to wait a tick. + const [initialCacheState, unusedSetInitialCacheState] = React.useState( + additionalCacheState + ); + const client = React.useMemo( () => buildApolloClient({ @@ -107,6 +104,42 @@ function ApolloProviderWithAuth0({ }), [initialCacheState] ); + + // When we get a new `additionalCacheState` object, merge it into the cache: + // copy the previous cache state, merge the new cache state's entries in, + // and "restore" the new merged cache state. + // + // HACK: Using `useMemo` for this is a dastardly trick!! What we want is the + // semantics of `useEffect` kinda, but we need to ensure it happens + // *before* all the children below get rendered, so they don't fire off + // unnecessary network requests. Using `useMemo` but throwing away the + // result kinda does that. It's evil! It's nasty! It's... perfect? + // (This operation is safe to run multiple times too, in case memo + // re-runs it. It's just, y'know, a performance loss. Maybe it's + // actually kinda perfect lol) + // + // I feel like there's probably a better way to do this... like, I want + // the semantic of replacing this client with an updated client - but I + // don't want to actually replace the client, because that'll break + // other kinds of state, like requests loading in the shared layout. + // Idk! I'll see how it goes! + React.useMemo(() => { + const previousCacheState = client.cache.extract(); + const mergedCacheState = { ...previousCacheState }; + for (const key of Object.keys(additionalCacheState)) { + mergedCacheState[key] = { + ...mergedCacheState[key], + ...additionalCacheState[key], + }; + } + console.debug( + "Merging Apollo cache:", + additionalCacheState, + mergedCacheState + ); + client.cache.restore(mergedCacheState); + }, [client, additionalCacheState]); + return {children}; }