我使用 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
文件中的存储变量总是未定义,并且它的值不会更新,因为值位于本地存储中
有人对这个问题有类似的经历吗?
由于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商店。