Search code examples
javascriptreduxredux-toolkitformikredux-persist

Formik values not saving in redux persist slice


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;

slice:

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;

component:

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>
)

I am trying to save values from formik to persist slice, everything ok, until redirect to another page and refresh it. When I come back my slice is empty, but if refresh current page with form, everything is ok and slice not empty.

Steps:

  1. Make some changes in values at form
  2. Slice fullfiled
  3. Redirect to another page
  4. Refresh page
  5. Back to page with form
  6. Slice is empty

Solution

  • Issue

    I believe the issue is that your persisted reducers all use the same persistence key property so they end up overwriting each other.

    const persistConfig = {
      key: "root", // <-- key => "persist:root"
      storage,
    };
    
    const persistedMainLayoutReducer = persistReducer(
      persistConfig, // <-- key <= "persist:root"
      mainLayoutReducer
    );
    
    const persistedDraftReducer = persistReducer(
      persistConfig, // <-- key <= "persist:root"
      draftDataSlice
    );
    
    const persistedLoadingReducer = persistReducer(
      persistConfig, // <-- key <= "persist:root"
      loadingReducer
    );
    

    Solution Suggestions

    Use separate persistence keys for each slice reducer

    const rootPersistConfig = {
      key: "root", // <-- key => "persist:root"
      storage,
    };
    
    const persistedMainLayoutReducer = persistReducer(
      rootPersistConfig, // <-- key <= "persist:root"
      mainLayoutReducer
    );
    
    const draftPersistConfig = {
      key: "draft", // <-- key => "persist:draft"
      storage,
    };
    
    const persistedDraftReducer = persistReducer(
      draftPersistConfig, // <-- key <= "persist:draft"
      draftDataReducer
    );
    
    const loadingPersistConfig = {
      key: "loading", // <-- key => "persist:loading"
      storage,
    };
    
    const persistedLoadingReducer = persistReducer(
      loadingPersistConfig, // <-- key <= "persist:loading"
      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,
      },
      ...
    });
    

    Use single persistence configuration and whitelist/blacklist the reducers you want to persist.

    const rootReducer = combineReducers({
      // PERSISTED SLICES
      mainLayout: mainLayoutReducer,
      loading: loadingReducer,
      draftData: draftDataReducer,
      // schedulePeriod: schedulePeriodReducer,
    
      // SLICES
      userData: userDataReducer,
      schedulePeriod: schedulePeriodReducer,
    
      // API
      [tmsApi.reducerPath]: tmsApi.reducer,
      [daDataApi.reducerPath]: daDataApi.reducer,
    });
    
    const persistConfig = {
      key: "root",
      storage,
      // Only persist these root reducers
      whitelist: ["mainLayout", "loading", "draftData"],
    };
    
    const persistedRootReducer = persistReducer(
      persistConfig,
      rootReducer
    );
    
    const store = configureStore({
      reducer: persistedRootReducer,
      ...
    });