apollo-cache-persist: apollo-cache-persist missing example with support for reactive variables

I want to automatically persist reactive variable value so the data will still be there after refreshing the page.

Given a reactive variable

import { ReactiveVar } from '@apollo/client'
import Disclaimer from 'dskcore/@interfaces/Disclaimer'

export default (disclaimerVar: ReactiveVar<Disclaimer>) => {
  return (value: Disclaimer) => {
    disclaimerVar(value)
  }
}

export const disclaimerVar: ReactiveVar<Disclaimer> = makeVar<Disclaimer>(
  initialDisclaimer
)


export const cache: InMemoryCache = new InMemoryCache({
  typePolicies: {
    Query: {
      fields: {
        disclaimer: {
          read () {
            return disclaimerVar()
          }
        }
      }
    }
  }
})

How can I make the reactive variable persist?

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 17
  • Comments: 30 (7 by maintainers)

Most upvoted comments

Hi,

Inspired by other answers here’s my solution to the problem, implemented in Typescript, and using localStorage. I’m sure someone here can adapt it to use async-type storage if they need it. Note that this is a ‘clean’ implementation that doesn’t involve monkey-patching makeVar:

import { makeVar, ReactiveVar } from '@apollo/client';
import { isString } from 'lodash';

const getCleanValueForStorage = (value: unknown) => {
  return isString(value) ? value : JSON.stringify(value);
};

const makeVarPersisted = <T>(initialValue: T, storageName: string): ReactiveVar<T> => {
  let value = initialValue;

  // Try to fetch the value from local storage
  const previousValue = localStorage.getItem(storageName);
  if (previousValue !== null) {
    try {
      const parsed = JSON.parse(previousValue);
      value = parsed;
    } catch {
      // It wasn't JSON, assume a valid value
      value = (previousValue as unknown) as T;
    }
  }

  // Create a reactive var with stored/initial value
  const rv = makeVar<T>(value);

  const onNextChange = (newValue: T | undefined) => {
    try {
      // Try to add the value to local storage
      if (newValue === undefined) {
        localStorage.removeItem(storageName);
      } else {
        localStorage.setItem(storageName, getCleanValueForStorage(newValue));
      }
    } catch {
      // ignore
    }

    // Re-register for the next change
    rv.onNextChange(onNextChange);
  };

  // Register for the first change
  rv.onNextChange(onNextChange);

  return rv;
};

export default makeVarPersisted;

Using it is as simple as:

export const loginToken = makeVarPersisted<string | undefined>(undefined, 'myVariable');

The value will try to initialise from localStorage, and fall back on the default. Updating will automatically update local storage too.

@wtrocki thanks for the heads up, I assumed (and I think @fullhdpixel too) this could be used to make reactiveVars to persist, because it would be a game changer to make apollo the definitive state manager solution for every case. So could you advice how we could make this work? Im interested on trying investigating for creating a PR to make it happen.

A temporary solution could be to create a wrapper in between the setter getter of the reactive vars and localStorage?

'Cmon someone makes this a PR! 🏆

Can’t count how many hours I lost trying to figure out a lib named cache-persist wasn’t persisting the basic apollo local state example

I have improved the retention of the reactive variable in the example above

import { makeVar, ReactiveVar } from "@apollo/client";
import { isString } from "lodash";
import AsyncStorage from "@react-native-async-storage/async-storage";

const getCleanValueForStorage = (value: unknown) => {
  return isString(value) ? value : JSON.stringify(value);
};

export const getVarPersisted = async <T>(
  rv: ReactiveVar<T>,
  storageName: string
) => {
  let value;

  // Try to fetch the value from local storage
  const previousValue = await AsyncStorage.getItem(storageName);

  if (previousValue !== null) {
    try {
      const parsed = await JSON.parse(previousValue);
      value = parsed;
    } catch {
      value = previousValue as unknown as T;
    }
  }

  value && rv(value);

  const onNextChange = (newValue: T | undefined) => {
    try {
      if (newValue === undefined) {
        AsyncStorage.removeItem(storageName);
      } else {
        AsyncStorage.setItem(storageName, getCleanValueForStorage(newValue));
      }
    } catch (err) {
      console.log("🚀 - err", err);
    }

    rv.onNextChange(onNextChange);
  };

  rv.onNextChange(onNextChange);
};

export const countVar = makeVar(0);

Use case image

@fullhdpixel Reactive variables will not work with persistence. That is true. PR’s welcome although this is not problem with library. It is more how apollo client works.

@rebz @pillowsoft the example indeed “doesn’t work” in terms of persistence. The example got updated for the apollo team to have a minimum viable demo app at hand to find out a way to make reactive vars persistent

@timothyarmes Why use isString instead of typeof value === 'string'?

@ivnnv nice to hear that! You don’t need to wait, just copy the source code from the PR and import the reactive variables from there, it’s just one file! 😉

@PedroBern thats fantastic news! Thank you very much for the apollo-client PR, ill be one of the first testers once that is merged for sure

Superb work 😃

Am I missing something? The example does not appear to work. How does it persist?

Superb work 😃

Added minimal example of using a a reactive var to store the selected values for the currencies list!