nextjs 服务器端 zustand 中的初始状态错误

问题描述 投票:0回答:1

我使用 zustand 作为全局状态管理器,我想在 nextjs 页面的服务器端使用持久状态。但是当我记录当前状态值时,它将打印默认值(默认值为空)并且它不会记录更新的状态值。

代码在这里:

商店.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;

仪表板/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: {},
  };
}

我发现

store.ts
文件中的存储变量总是未定义,并且它的值不会更新,因为值位于本地存储中

有人对这个问题有类似的经历吗?

reactjs typescript next.js server-side-rendering zustand
1个回答
0
投票

由于NextJS在服务端进行了静态优化,如果你持久化修改后的Zustand store并尝试在渲染前加载它,NextJS会检测到服务端得到的结果与实际结果不同。 这种情况下的解决方案不是直接使用 zustand store 来影响 UI 元素,而是使用 React useState 并在 useEffect 中设置持久状态,这样这将在渲染后发生,NextJS 不会抱怨它。

在代码方面,我使用这个自定义钩子来包装 Zustand 商店:

export const useSSRStore = (store) => {
    const [_store, setStore] = useState({});
    useEffect(() => {
        setStore(store);
    }, [store]);
    return _store;
};

像这样使用它:

const {
        isDarkMode,
        isRightSectionVisible,
    } = useSSRStore(useUIStore());

在哪里

useUIStore()
返回原来的Zustand商店。

© www.soinside.com 2019 - 2024. All rights reserved.