Git Product home page Git Product logo

Comments (18)

AnatoleLucet avatar AnatoleLucet commented on August 15, 2024 16

Edit:
Since v3.1.4 a build-in middleware has been added, see this section of the readme for more info.


I made a pretty basic one, but it does the job.

import create, { GetState, SetState, StateCreator, StoreApi } from "zustand";

type Product = { id: number; amount: number };

interface Store {
  products: Product[];
  setProducts: (payload: Product) => void;
}

const isRunningInBrowser = () => typeof window !== "undefined";

const persist = <T>(name: string, config: StateCreator<T>) => (
  set: SetState<T>,
  get: GetState<T>,
  api: StoreApi<T>,
): T => {
  const state = config(
    (payload) => {
      set(payload);

      if (isRunningInBrowser) {
        localStorage.setItem(name, JSON.stringify(payload));
      }
    },
    get,
    api,
  );

  return {
    ...state,
    ...(isRunningInBrowser() && JSON.parse(localStorage.getItem(name))),
  };
};

export const [useShoppingCart] = create<Store>(
  persist<Store>("shoppingCart", (set, get) => ({
    products: [],
    setProducts: (payload) => {
      const state = get();

      set({ ...state, products: [...state.products, payload] });
    },
  })),
);

The persist middleware will automatically rehydrate your state. No need to do anything specific in your state creator function 🎉

In my example everything is in one file, but you can (and I do) put the persist and isRunningInBrowser functions in a specific file, then import it whenever you need to use a persistent store.
And it's also Typescript friendly.

from zustand.

marcoacierno avatar marcoacierno commented on August 15, 2024 4

What about this?

import create from 'zustand';

const persist = config => (set, get, api) => {
  const initialState = config(
    args => {
      set(args);
      window.localStorage.setItem('state', JSON.stringify(get()));
    },
    get,
    api,
  );

  const restoredState =
    typeof window === 'undefined'
      ? {}
      : JSON.parse(localStorage.getItem('state'));

  return {
    ...initialState,
    ...restoredState,
  };
};

const [useStore] = create(
  persist(set => ({
    theme: 'light',
    acceptedCookies: null,
    setTheme: theme => set(state => ({ theme })),
  })),
);

export default useStore;

should be SSR friendly and work?

from zustand.

maxwaiyaki avatar maxwaiyaki commented on August 15, 2024 4

@marcoacierno using your example and I still get a console error Text content did not match. Server: "1 count" Client: "3 count". Using Next.js

from zustand.

rdhox avatar rdhox commented on August 15, 2024 2

On an electron app, I recently use this as a persist process.

const MainLayout : React.FunctionComponent = () => {

  const [ initialized, setInitialized ] = useState<boolean>(false);

  useEffect(() => {

    const initializedSettings: ReducerEffect = apiSettings.getState().reducers.initialize;
    const initializedThemes: ReducerEffect = apiThemes.getState().reducers.initialize;
    
    // Initialized the app with the persists states when the app is launched
    const unsubInitialData = myIpcRenderer.on('APP_INITIAL_STATE', data => {
      initializedSettings(data.initialSettings);
      initializedThemes(data.initialThemes);
      setInitialized(true);
    });

    // Each time the state that you want to persist is changed, the subscribe api trigger your persist function (like storing in AsyncStorage)
    const unsubSaveSettings = apiSettings.subscribe(
      (state: SettingsState) => {
        if (initialized)
          myIpcRenderer.send('APP_SAVE_SETTINGS', state);
      },
      settingsState => settingsState.state
    );
    const unsubSaveThemes = apiThemes.subscribe(
      (state: ThemesState) => {
        if (initialized)
          myIpcRenderer.send('APP_SAVE_THEMES', state);
      },
      themesState => themesState.state
    );

    return () => {
      unsubInitialData();
      unsubSaveSettings();
      unsubSaveThemes();
    }
  }, [initialized]);

  return (
    ...
  );
}

I used this structure for my zustand states:

{
  state: {...}, // state values
  reducers: {...}, // pure functions to change the state 
  effects: {...} // async functions with side effects
}

You can easily think of a web/mobile way with that example. It's not a middleware, but the subscribe api is perfect for this use.

from zustand.

ahmetcetin avatar ahmetcetin commented on August 15, 2024 1

@drcmda having persistence as option would be great really. supporting it in ssr indeed would be too specific case, but persistence of a store is quite common problem really.

from zustand.

marcoacierno avatar marcoacierno commented on August 15, 2024

Mine was/is a very basic app, but that's what I implemented:

import create from 'zustand';

const restoredState = JSON.parse(localStorage.getItem('state'));

const persist = config => (set, get, api) =>
  config(
    args => {
      set(args);
      window.localStorage.setItem('state', JSON.stringify(get()));
    },
    get,
    api,
  );

const [useStore] = create(
  persist(set => ({
    theme: 'light',
    ...restoredState,
    setTheme: theme => set(state => ({ theme })),
  })),
);

export default useStore;

where I basically first set the default state of each variable and then overwrite them with what was inside the store.

It won't work well if you change type of information, or anything else really but gets the job done (at least for me)

from zustand.

alexluong avatar alexluong commented on August 15, 2024

FWIW, my initial approach is terrible for SSR. It took me 6 hours to figure it out.

Apparently, this:

const isBrowser = typeof window !== "undefined"

const persistedState =
  isBrowser
    ? JSON.parse(localStorage.getItem("sidebarState"));
    : null

doesn't work well with SSR.

The approach I need to take is to give the original state inside create function the initial state, and then use useMount to get localStorage data and update state.

Just in case anyone may run into this in the future!

from zustand.

drcmda avatar drcmda commented on August 15, 2024

is this something we can add officially to the lib? or is it too specific?

from zustand.

alexluong avatar alexluong commented on August 15, 2024

@drcmda I think this is too specific. I don't think there is a way for zustand to support it right out of the box. But maybe some documentation would be beneficial? Although this is not zustand-specific as it's a problem with other state managements too.

@marcoacierno I don't think that would work, although I haven't tested it yet. The problem is that even if the store state is updated with persisted data, the component will not rerender.

What I needed to do:

function Component() {
  const { state, setState } = useStore()

  React.useEffect(() => {
    if (typeof window !== "undefined") {
      setState(JSON.parse(localStorage.getItem("key")))
    }
  }, [setState])

  return <Stuff />
}

from zustand.

roadmanfong avatar roadmanfong commented on August 15, 2024

I'm not familiar with SSR, not sure how to do it.

But I created a package https://www.npmjs.com/package/zustand-persist for react and react native
Contributions are welcome

from zustand.

GaspardC avatar GaspardC commented on August 15, 2024

@AnatoleLucet how would you implement your solution with AsyncStorage (react-native - which is async) ?

from zustand.

AnatoleLucet avatar AnatoleLucet commented on August 15, 2024

Hey @GaspardC, sorry I forgot to respond 🙃
I can't see how this could be implemented with an async storage without making every function async (which would require you to get your store's data in a useEffect or something) or falling in a callback hell.

Though I think this might be doable with a custom hook. But I'm not sure how a wrapper hook could adapt to any kind of zustand store.
Maybe someone else will have an idea 🤷‍♂️

from zustand.

lordvcs avatar lordvcs commented on August 15, 2024

I get the error Text content did not match. Server: "1 count" Client: "3 count" when using persist along with Next.js.
@maxwaiyaki were you able to solve this?

from zustand.

MariusVB avatar MariusVB commented on August 15, 2024

This comment helped me solve it: #324 (comment)

from zustand.

alvinlys avatar alvinlys commented on August 15, 2024

This comment helped me solve it: #324 (comment)

do you mind to share what does it solved for you, because I had removed it due to same effects depsite without ssr it and just stick back to one in the documentation?

from zustand.

MariusVB avatar MariusVB commented on August 15, 2024

This comment helped me solve it: #324 (comment)

do you mind to share what does it solved for you, because I had removed it due to same effects depsite without ssr it and just stick back to one in the documentation?

I don't get the error Text content did not match. Server: "x count" Client: "y count" anymore, after wrapping in noSsr component.
What did you use from the documentation to solve it?

from zustand.

kavehsaket avatar kavehsaket commented on August 15, 2024

Same issue with next 13.4.4.

from zustand.

alvinlys avatar alvinlys commented on August 15, 2024

This comment helped me solve it: #324 (comment)

do you mind to share what does it solved for you, because I had removed it due to same effects depsite without ssr it and just stick back to one in the documentation?

I don't get the error Text content did not match. Server: "x count" Client: "y count" anymore, after wrapping in noSsr component. What did you use from the documentation to solve it?

just this exactly same https://docs.pmnd.rs/zustand/recipes/recipes#persist-middleware

from zustand.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.