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

Persisting InMemoryCache when using SSR #5475

Closed
fukuru opened this issue Oct 18, 2019 · 9 comments
Closed

Persisting InMemoryCache when using SSR #5475

fukuru opened this issue Oct 18, 2019 · 9 comments

Comments

@fukuru
Copy link

fukuru commented Oct 18, 2019

Hi there,

What is the recommended way to persist InMemoryCache when using server side rendering?

If my app was not server side rendered I could just persist to localStorage.

await persistCache({
  cache,
  storage: window.localStorage,
});

Having trouble trying to figure out how to persist InMemoryCache when using NextJS.

@ajhool
Copy link

ajhool commented Nov 1, 2019

In my app, I'm using:

import { persistCache } from 'apollo-cache-persist';
import { InMemoryCache } from 'apollo-cache-inmemory';

const initCache = (initialState?: any) => {
  const cache = new InMemoryCache().restore(initialState || {});
  
  /**
   * Cache uses localStorage to save data.
   * 
   * This cache is used by Apollo (graphql client).
   */
  if(typeof window !== 'undefined') {
    persistCache({
      cache,
      storage: window.localStorage
    });
  }
  
  return cache;
}

export default initCache;

I believe this approach means that SSR won't be able to access the cache but the client will access the cache rather than making network calls once the initial SSR has completed. All calls made from the client will have access to the cache.

@sakhmedbayev
Copy link

@ajhool, thanks for sharing your setup. Can you please show how you are initializing the apollo client?

@sanderkooger
Copy link

@ajhool, thanks for sharing your setup. Can you please show how you are initializing the apollo client?

Have you figured you how to implement this? I am running in the same issue?

@sanderkooger
Copy link

@pescoboza
Copy link

pescoboza commented Apr 1, 2021

I know this might be a bit state... but did you come up with a solution? I'm a bit puzzled about getting this to work using reactive variables for local state management.

@RishikeshDarandale
Copy link

@pescoboza , same here. Anyone found solution?

@pescoboza
Copy link

pescoboza commented Apr 28, 2021

Hello @RishikeshDarandale!
Resorted to creating a class wrapper that holds a reactive variable to write it and hydrate it as needed from the local or session storage. I'm using react and made a HOC that fires up each one of these at mount.

It worked for me, but use this at your own risk. I have not tested performance or security.

import { makeVar } from '@apollo/client';
import type { ReactiveVar } from '@apollo/client';
/**
 * Holds a singular reactive variable and provides persistence methods.
 */
export default class ReactiveStoreEntry<T> {
    private readonly persist: boolean;
    private readonly initialState: T;

    public readonly loadOnMount: boolean;
    public readonly preferInitialState: boolean;
    public readonly localStorageKey: string;
    public readonly reactiveVar: ReactiveVar<T>;

    constructor({
        initialState,
        persist,
        loadOnMount,
        localStorageKey,
        preferInitialState,
    }: {
        localStorageKey?: string;
        initialState: T;
        loadOnMount?: boolean;
        persist?: boolean;
        preferInitialState?: boolean;
    }) {
        if (persist && localStorageKey == null)
            throw TypeError('Must specify localStorageKey when persist is set to true');

        this.localStorageKey = localStorageKey || '';
        this.persist = persist || false;
        this.loadOnMount = loadOnMount || true;
        this.initialState = initialState;
        this.preferInitialState = preferInitialState || false;

        this.reactiveVar = makeVar<T>(initialState);
    }

    get() {
        return this.reactiveVar();
    }

    set(newValue: T, save = false) {
        this.reactiveVar(newValue);
        if (save) this.save();
        return this;
    }

    reset() {
        this.reactiveVar(this.initialState);
        return this;
    }

    // Load the reactive variable from local storage
    load() {
        // Nothing is loaded is the data is not set to persist
        if (!this.persist) return;

        // If on serve or if the initial state is prefered over cached state, load the initial state
        if (typeof window === 'undefined' || this.preferInitialState) {
            this.set(this.initialState);
            return;
        }

        try {
            // Get data from local storage using the key
            const rawData = localStorage.getItem(this.localStorageKey);

            // Nothing found: load initial state
            if (rawData == null) {
                this.set(this.initialState);
                return;
            }

            // Parse the string value
            const parsedValue = JSON.parse(rawData);

            // If nothing was parsed, or if the value is an empty object, load initial state and return
            if (parsedValue == null || (typeof parsedValue === 'object' && Object.keys(parsedValue).length === 0)) {
                this.set(this.initialState);
                return;
            }

            // Set the value
            this.set(parsedValue);
        } catch (err) {
            // On error, fall back to initial state
            console.warn(err);
            this.set(this.initialState);
            return;
        }
        return this;
    }

    // Save to local storage for persitence
    save() {
        if (!this.persist || typeof window === 'undefined') return;
        localStorage.setItem(this.localStorageKey, JSON.stringify(this.get()));
        return this;
    }

    clearFromStorage() {
        localStorage.removeItem(this.localStorageKey);
        return this;
    }
}

@adrianos10
Copy link

I think I've managed to add apollo cache persist based on existing example in Next.js - vercel/next.js#29718

@bignimbus
Copy link
Contributor

Thanks all for the great discussion! I think this question touches upon a larger question of how Apollo Client should be used in SSR contexts. This is of particular interest now that React Server Components have been adopted by Next.js.

I'm doing some housekeeping and in the interest of focusing on actionable issues I'll close this one for now. Please feel free to use our community forum to share some ideas as well! If you feel strongly this should remain open please let me know, happy to accommodate 🙏🏻

@bignimbus bignimbus closed this as not planned Won't fix, can't repro, duplicate, stale Nov 3, 2022
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 1, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

8 participants