wrong Initial state in zustand in nextjs server side

Viewed 27

I use zustand as a global state manager and I want use of the persisted states in server side of nextjs pages. but when I log the currently state values it will print default values (which the default values are null) and it does not log the updated states values.

The codes are here:

store.ts

/**
 *? For more information about config zustand store in nextjs visit here:
 *? https://github.com/vercel/next.js/blob/canary/examples/with-zustand/lib/store.js
 */

import { useLayoutEffect } from 'react';

import create, { StoreApi } from 'zustand';
import createContext from 'zustand/context';
import { devtools, persist, PersistOptions } from 'zustand/middleware';

import { createClinicSlice } from './slices/clinic';
import { createUserSlice } from './slices/user';

import type { IClinicSlice, IResetState, IUserSlice } from '$types';

const persistProperties: PersistOptions<any> = {
  name: 'globalStorage',
  getStorage: () => localStorage,
  // ? should update version when app version updated
  version: 0,
};

let store: any;

interface IInitialState {
  clinics: null;
  activeClinic: null;
  user: null;
}

const getDefaultInitialState = (): IInitialState => ({
  clinics: null,
  activeClinic: null,
  user: null,
});

const zustandContext = createContext<StoreApi<IClinicSlice & IUserSlice & IResetState>>();

export const Provider = zustandContext.Provider;
export const useStore = zustandContext.useStore;

export const initializeStore = (preloadedState: Record<string, any> = {}) => {
  return create<IClinicSlice & IUserSlice & IResetState>()(
    devtools(
      persist(
        (set) => ({
          ...getDefaultInitialState(),
          ...preloadedState,
          // @ts-ignore
          ...createClinicSlice(set),
          // @ts-ignore
          ...createUserSlice(set),
          resetStore: () => set(getDefaultInitialState()),
        }),
        persistProperties
      )
    )
  );
};

export function useCreateStore(
  serverInitialState: Record<string, any>
): typeof initializeStore {
  if (typeof window === 'undefined') {
    return () => initializeStore(serverInitialState);
  }

  const isReusingStore = Boolean(store);
  store = store ?? initializeStore(serverInitialState);

  useLayoutEffect(() => {
    // serverInitialState is undefined for CSR pages. It is up to you if you want to reset
    // states on CSR page navigation or not. I have chosen not to, but if you choose to,
    // then add `serverInitialState = getDefaultInitialState()` here.
    if (serverInitialState && isReusingStore) {
      store.setState(
        {
          // re-use functions from existing store
          ...store.getState(),
          // but reset all other properties.
          ...(serverInitialState || getDefaultInitialState()),
        },
        true // replace states, rather than shallow merging
      );
    }
  });

  return () => store;
}

_app.tsx

import { useEffect } from 'react';

import CssBaseline from '@material-ui/core/CssBaseline';
import { ThemeProvider, jssPreset, StylesProvider } from '@material-ui/core/styles';
import i18n from 'i18next';
import { create } from 'jss';
import rtl from 'jss-rtl';
import type { AppProps } from 'next/app';
import Head from 'next/head';
import { I18nextProvider } from 'react-i18next';
import { ThemeProvider as SCThemeProvider } from 'styled-components';

import muiTheme from '$/assets/style/theme';
import { AuthProvider } from '$/components/contexts/auth';
import { ReactToastify } from '$/components/ReactToastify/ReactToastify';

import { Provider, useCreateStore } from '$store';

import '../../public/assets/style/index.css';
import 'leaflet/dist/leaflet.css';
import 'leaflet-geosearch/assets/css/leaflet.css';
import 'leaflet-geosearch/dist/geosearch.css';
import 'react-toastify/dist/ReactToastify.css';
import '$/utils/i18n.config';

const jss = create({
  plugins: [...jssPreset().plugins, rtl()],
});

function MyApp({ Component, pageProps }: AppProps) {
  useEffect(() => {
    const jssStyles = document.querySelector('#jss-server-side');
    if (jssStyles) {
      jssStyles.parentElement!.removeChild(jssStyles);
    }
  }, []);

  const createStore = useCreateStore(pageProps.initialZustandState);

  return (
    <>
      <Head>
        <meta
          name="viewport"
          content="minimum-scale=1, initial-scale=1, width=device-width, user-scalable=no"
        />
      </Head>
      <Provider createStore={createStore}>
        <I18nextProvider i18n={i18n}>
          <ReactToastify />
        </I18nextProvider>

        <StylesProvider jss={jss}>
          <ThemeProvider theme={muiTheme}>
            <SCThemeProvider theme={muiTheme}>
              <CssBaseline />
              <AuthProvider>
                <I18nextProvider i18n={i18n}>
                  <Component {...pageProps} />
                </I18nextProvider>
              </AuthProvider>
            </SCThemeProvider>
          </ThemeProvider>
        </StylesProvider>
      </Provider>
    </>
  );
}

export default MyApp;

dashboard/index.tsx

import { useEffect } from 'react';

import type { NextPage } from 'next';
import { useRouter } from 'next/router';

import { PageScrollableArea } from '$/components/index';
import ClinicDashboardLayout from '$/components/layout/clinicDashboardLayout/ClinicDashboardLayout';
import { isNil } from '$/utils/stringUtils';
import ClinicDashboard from '$/view/clinic/Dashboard';
import withPrivateRoute from '$/view/hocs/withPrivateRoute';

import { CLINICS_PAGE_URL } from '$constant';
import { initializeStore, useStore } from '$store';

const Dashboard: NextPage = (props) => {
  return (
    <ClinicDashboardLayout>
      <PageScrollableArea>
        <ClinicDashboard />
      </PageScrollableArea>
    </ClinicDashboardLayout>
  );
};

export default withPrivateRoute(Dashboard);

export function getServerSideProps() {
  const zustandStore = initializeStore();
  console.log(`===> zustandStore.getState() ===>`, zustandStore.getState());
  // if (isNil(zustandStore.getState().activeClinic)) {
  //   return {
  //     redirect: {
  //       destination: CLINICS_PAGE_URL,
  //       permanent: true,
  //     },
  //   };
  // }
  return {
    props: {},
  };
}

I find out the store variable in store.ts file always is undefined and its value will not update as values are located in local storage

Does anybody have a similar experience with this issue?

0 Answers
Related