You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Formik值存入Redux Persist切片后跳转刷新丢失问题

问题:Redux Persist 存储表单草稿在跨页面刷新后丢失

我尝试把Formik表单的值保存到Redux Persist切片里,在表单页面刷新时一切正常,但跳转到其他页面刷新后,返回表单页面发现切片数据为空。

问题重现步骤

  • 在表单中修改值,切片被正常填充
  • 跳转到其他页面
  • 刷新该页面
  • 返回表单页面,切片数据为空

相关代码

Store 配置

import { configureStore } from "@reduxjs/toolkit";

//SLICES
import mainLayoutReducer from "./mainLayoutSlice";
import userDataReducer from "./userDataSlice";
import schedulePeriodReducer from "./schedulePeriodSlice";
import loadingReducer from "./loadingSlice";
import draftDataSlice from "./draftDataSlice";

// API
import { tmsApi } from "../api";
import { daDataApi } from "../api/thirdPartyApis/daDataApi";

import {
  persistReducer,
  FLUSH,
  REHYDRATE,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER,
} from "redux-persist";

import storage from "redux-persist/lib/storage";

const persistConfig = {
  key: "root",
  storage,
};

const persistedMainLayoutReducer = persistReducer(
  persistConfig,
  mainLayoutReducer
);

const persistedDraftReducer = persistReducer(
  persistConfig,
  draftDataSlice
);

const persistedLoadingReducer = persistReducer(persistConfig, loadingReducer);

const store = configureStore({
  reducer: {
    // PERSISTED SLICES
    mainLayout: persistedMainLayoutReducer,
    loading: persistedLoadingReducer,
    draftData: persistedDraftReducer,
    // schedulePeriod: persistedSchedulePeriodReducer,

    // SLICES
    userData: userDataReducer,
    schedulePeriod: schedulePeriodReducer,

    // API
    [tmsApi.reducerPath]: tmsApi.reducer,
    [daDataApi.reducerPath]: daDataApi.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
    })
      .concat(tmsApi.middleware)
      .concat(daDataApi.middleware),
});

export default store;

Draft 切片

import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  draftData: null,
};

export const draftDataSlice = createSlice({
  name: "draftData",
  initialState,
  reducers: {
    setDraftData: (state, action) => {
      state.draftData = action.payload;
    },
    clearDraftData: (state) => {
      state.draftData = null;
    },
  },
});

export const { setDraftData, clearDraftData } = draftDataSlice.actions;

export default draftDataSlice.reducer;

表单组件

const isDraft = location.search;

const draftData = useSelector((state) => state.draftData.draftData);

const formik = useFormik({
  initialValues: isDraft
    ? draftData
    : {
      ...mainInfoValues(employeeInfo),
      ...vehicleInfoValues(employeeInfo),
      ...docsValues(employeeInfo),
    },
  onSubmit: // here onSubmit func
})

useEffect(() => {
  dispatch(setDraftData(values));
}, [values]);

return(
  <form onSubmit={handleSubmit}>
    <Section overflow="inherit">
      {(() => {
        switch (step) {
          case 1:
            return (
              <MainInfo
                values={values}
                handleChange={handleChange}
                setFieldValue={setFieldValue}
                setValues={setValues}
                touched={touched}
                errors={errors}
                handleBlur={handleBlur}
                isStepOneEmpty={isStepOneEmpty}
                resetForm={resetForm}
                handleStep={handleStep}
              />
            );

          case 2:
            return (
              <VehicleInfo
                values={values}
                handleChange={handleChange}
                setFieldValue={setFieldValue}
                setValues={setValues}
                touched={touched}
                errors={errors}
                handleBlur={handleBlur}
                isStepTwoEmpty={isStepTwoEmpty}
                resetForm={resetForm}
                handleStep={handleStep}
              />
            );

          case 3:
            return (
              <AttachDocs
                values={values}
                handleChange={handleChange}
                setFieldValue={setFieldValue}
                setValues={setValues}
                touched={touched}
                errors={errors}
                handleBlur={handleBlur}
                isStepThreeEmpty={isStepThreeEmpty}
                resetForm={resetForm}
                handleStep={handleStep}
              />
            );

          default:
            break;
        }
      })()}
    </Section>
  </form>
)

问题原因与解决方案

核心问题

你给每个需要持久化的切片都使用了同一个persistConfig(key为"root"),这会导致多个持久化 reducer 互相覆盖存储的数据。Redux Persist 中,每个持久化 reducer 的存储key必须唯一,或者采用根持久化的方式统一管理。

解决方法

方法1:根持久化(推荐)

只对整个根 reducer 做持久化,统一管理所有需要存储的切片:

修改 Store 配置:

import { configureStore, combineReducers } from "@reduxjs/toolkit";
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";

//SLICES
import mainLayoutReducer from "./mainLayoutSlice";
import userDataReducer from "./userDataSlice";
import schedulePeriodReducer from "./schedulePeriodSlice";
import loadingReducer from "./loadingSlice";
import draftDataSlice from "./draftDataSlice";

// API
import { tmsApi } from "../api";
import { daDataApi } from "../api/thirdPartyApis/daDataApi";

import {
  FLUSH,
  REHYDRATE,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER,
} from "redux-persist";

// 合并需要持久化的reducer
const rootReducer = combineReducers({
  mainLayout: mainLayoutReducer,
  loading: loadingReducer,
  draftData: draftDataSlice,
  userData: userDataReducer,
  schedulePeriod: schedulePeriodReducer,
});

const persistConfig = {
  key: "root",
  storage,
  // 可选:只持久化指定切片,注释则默认全部持久化
  // whitelist: ["mainLayout", "loading", "draftData"]
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

const store = configureStore({
  reducer: {
    persisted: persistedReducer,
    // API reducer 无需持久化
    [tmsApi.reducerPath]: tmsApi.reducer,
    [daDataApi.reducerPath]: daDataApi.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
    })
      .concat(tmsApi.middleware)
      .concat(daDataApi.middleware),
});

export const persistor = persistStore(store);
export default store;

在应用入口用PersistGate包裹根组件:

import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import store, { persistor } from './store';

ReactDOM.render(
  <Provider store={store}>
    <PersistGate loading={null} persistor={persistor}>
      <App />
    </PersistGate>
  </Provider>,
  document.getElementById('root')
);

注意:修改后,状态获取路径需要调整,比如原来的state.draftData.draftData要改为state.persisted.draftData.draftData。

方法2:给每个切片设置唯一存储key

如果坚持单独持久化每个切片,需为每个切片的persistConfig设置不同的key:

// DraftData 专属配置
const draftPersistConfig = {
  key: "draftData",
  storage,
};
const persistedDraftReducer = persistReducer(draftPersistConfig, draftDataSlice);

// MainLayout 专属配置
const mainLayoutPersistConfig = {
  key: "mainLayout",
  storage,
};
const persistedMainLayoutReducer = persistReducer(mainLayoutPersistConfig, mainLayoutReducer);

// Loading 同理设置唯一key

这种方式不需要调整状态路径,但每个切片会单独存储在localStorage中,管理成本较高。

额外检查点

  1. 确保Formik的values是可序列化的:如果包含函数、Symbol等不可序列化数据,Redux Persist无法存储,会导致数据丢失,可在setDraftData前过滤或转换这些数据。
  2. 查看浏览器localStorage:打开开发者工具 > Application > LocalStorage,确认存储项是否存在且数据正确。

内容的提问来源于stack exchange,提问作者David Abramov

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 06:44:57