- 修改 Zustand sidebar store 使用 skipHydration 避免 SSR 不匹配 - 移除 Redux auth slice 初始状态中的 localStorage 读取 - 在 providers 中使用 useEffect 初始化客户端状态 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
dc27044dab
commit
fc3efe6a27
|
|
@ -2,9 +2,21 @@
|
||||||
|
|
||||||
import { Provider as ReduxProvider } from 'react-redux';
|
import { Provider as ReduxProvider } from 'react-redux';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { store } from '@/store';
|
import { store } from '@/store';
|
||||||
import { Toaster } from '@/components/ui/toaster';
|
import { Toaster } from '@/components/ui/toaster';
|
||||||
|
import { useSidebar } from '@/store/zustand/use-sidebar';
|
||||||
|
import { initializeAuth } from '@/store/slices/auth.slice';
|
||||||
|
|
||||||
|
function HydrationHandler() {
|
||||||
|
useEffect(() => {
|
||||||
|
// 客户端初始化 zustand 持久化状态
|
||||||
|
useSidebar.persist.rehydrate();
|
||||||
|
// 客户端初始化 auth token
|
||||||
|
store.dispatch(initializeAuth());
|
||||||
|
}, []);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export function Providers({ children }: { children: React.ReactNode }) {
|
export function Providers({ children }: { children: React.ReactNode }) {
|
||||||
const [queryClient] = useState(
|
const [queryClient] = useState(
|
||||||
|
|
@ -22,6 +34,7 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<ReduxProvider store={store}>
|
<ReduxProvider store={store}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<HydrationHandler />
|
||||||
{children}
|
{children}
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ interface AuthState {
|
||||||
|
|
||||||
const initialState: AuthState = {
|
const initialState: AuthState = {
|
||||||
user: null,
|
user: null,
|
||||||
token: typeof window !== 'undefined' ? localStorage.getItem('admin_token') : null,
|
token: null,
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
|
|
@ -62,6 +62,14 @@ const authSlice = createSlice({
|
||||||
setToken: (state, action: PayloadAction<string>) => {
|
setToken: (state, action: PayloadAction<string>) => {
|
||||||
state.token = action.payload;
|
state.token = action.payload;
|
||||||
},
|
},
|
||||||
|
initializeAuth: (state) => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
const token = localStorage.getItem('admin_token');
|
||||||
|
if (token) {
|
||||||
|
state.token = token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder
|
builder
|
||||||
|
|
@ -97,5 +105,5 @@ const authSlice = createSlice({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { clearError, setToken } = authSlice.actions;
|
export const { clearError, setToken, initializeAuth } = authSlice.actions;
|
||||||
export default authSlice.reducer;
|
export default authSlice.reducer;
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,30 @@
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
import { persist, createJSONStorage } from 'zustand/middleware';
|
||||||
|
|
||||||
interface SidebarState {
|
interface SidebarState {
|
||||||
isCollapsed: boolean;
|
isCollapsed: boolean;
|
||||||
|
_hasHydrated: boolean;
|
||||||
toggle: () => void;
|
toggle: () => void;
|
||||||
setCollapsed: (collapsed: boolean) => void;
|
setCollapsed: (collapsed: boolean) => void;
|
||||||
|
setHasHydrated: (state: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSidebar = create<SidebarState>()(
|
export const useSidebar = create<SidebarState>()(
|
||||||
persist(
|
persist(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
isCollapsed: false,
|
isCollapsed: false,
|
||||||
|
_hasHydrated: false,
|
||||||
toggle: () => set((state) => ({ isCollapsed: !state.isCollapsed })),
|
toggle: () => set((state) => ({ isCollapsed: !state.isCollapsed })),
|
||||||
setCollapsed: (collapsed) => set({ isCollapsed: collapsed }),
|
setCollapsed: (collapsed) => set({ isCollapsed: collapsed }),
|
||||||
|
setHasHydrated: (state) => set({ _hasHydrated: state }),
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
name: 'sidebar-state',
|
name: 'sidebar-state',
|
||||||
|
storage: createJSONStorage(() => localStorage),
|
||||||
|
skipHydration: true,
|
||||||
|
onRehydrateStorage: () => (state) => {
|
||||||
|
state?.setHasHydrated(true);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue