rt2zz / redux-persist

persist and rehydrate a redux store
MIT License
12.95k stars 866 forks source link

non-serializable value error #988

Closed rseemann closed 5 years ago

rseemann commented 5 years ago

Hi folks πŸ‘‹,

I just installed the redux-persist in a project still in its early stages and I'm facing this error message in the console as first thing when the page loads. I haven't done much besides including the lib as is, and my reducers are pretty simple for now, so I believe it has something to do with redux-persist itself?

Error:

index.js:1446 A non-serializable value was detected in an action, in the path: `register`. Value: Ζ’ register(key) {
    _pStore.dispatch({
      type: _constants__WEBPACK_IMPORTED_MODULE_2__["REGISTER"],
      key: key
    });
  }

Code above is followed by this message:

SS of the whole thing: image

Could anyone help me pointing me in the right direction? It does not break the application, but the error message is there in the console.

Cheers,

ZechyW commented 5 years ago

Are you using redux-starter-kit? It looks like the message is caused by the included serializable-state-invariant-middleware (https://redux-starter-kit.js.org/api/getdefaultmiddleware).

As far as I can tell, passing the register and rehydrate functions in the action is central to how redux-persist currently works, but the message can be suppressed by modifying the store configuration to not include that particular middleware function.

rseemann commented 5 years ago

That is exactly what is going on. Further investigating I noticed this in the default middleware. I'll just remove the middleware, since it's not really essential to what I'm doing.

Cheers!

SeedyROM commented 5 years ago

@rseemann How exactly did you solve this? Please post your configureStore function.

rseemann commented 5 years ago

@SeedyROM I removed the getDefaultMiddleware from the list of middlewares. There is no real fix there, it is a problem between opinions. The serializable-state-invariant-middleware complains when it sees a non-serializable object, a function in this case, being passed as an action; redux-persist needs to do so. Sometimes you just have to pick a side πŸ€·β€β™€οΈ .

Anyway, here's my configureStore.

const store = configureStore({
  reducer: rootReducer,
  middleware: [thunk, logger]
})
SeedyROM commented 5 years ago

@rseemann Where exactly are you getting thunk and logger from, I saw that in the documentation but I was a little bit lost. Did you manually configure them both?

Still a redux noob! Sorry!

SeedyROM commented 5 years ago

@rseemann I was also having issues with PersistGate never rendering, did you run into anything of the sort? I know this probably isn't the place to ask this, but I'm sleepy and desperate.

rseemann commented 5 years ago

@SeedyROM I'm using them as they come, no custom config.

import logger from 'redux-logger'
import thunk from 'redux-thunk'

You might have to install those yourself, although I believe redux-starter-kit will have them already, I think it is better to have more atomic dependencies.

About the PersistGate error you mentioned, I never saw this, I'm sorry. Also, don't code while sleepy, man! hahaha

SeedyROM commented 5 years ago

@rseemann I figured that was what was needed, but I wanted to make sure instead of battling against some configuration misunderstanding.

Yeah the PersistGate issue is strange, I'll figure it out in the morning. I don't normally do professional work this late, but dead lines my dude. lol (Don't worry, I'm quarantined in a feature branch haha)

:rocket: Thanks for the help.

acroix commented 5 years ago

Same issue for me, redux-persist is using default redux configuration and redux-starter-kit is waiting for a string.

It would be nice to have a property into configureStore like persist: true inside redux-starter-kit and handling this error by using the immer pattern from redux-starter-kit

ty @rseemann

markerikson commented 5 years ago

We're not going to modify configureStore to offer options specific to redux-persist. If the serializability check is an issue, you have the ability to modify the store setup by changing the included middleware to either leave it out or customize what fields it inspects.

sospedra commented 5 years ago

There's a solution since redux-starter-kit@0.70

Now you can opt-out the serialization using serializableCheck attr:

import { configureStore, getDefaultMiddleware } from 'redux-starter-kit'

// ...

export const store = configureStore({
  reducer,
  middleware: getDefaultMiddleware({
    serializableCheck: false,
  }),
})
markerikson commented 5 years ago

Or, ignore a specific action type for the check:

export const store = configureStore({
  reducer,
  middleware: getDefaultMiddleware({
    serializableCheck: {
        ignoredActions: [someReduxPersistActionType]
    }
  }),
})
aldreth commented 4 years ago

For anyone else struggling to find out what [someReduxPersistActionType] should be, here's my redux-starter-kit & redux-persist config

import React from "react";
import ReactDOM from "react-dom";
import { configureStore, getDefaultMiddleware } from "redux-starter-kit";
import { Provider } from "react-redux";
import {
  persistStore,
  persistReducer,
  FLUSH,
  REHYDRATE,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER
} from "redux-persist";
import storage from "redux-persist/lib/storage";
import { PersistGate } from "redux-persist/integration/react";

import App from "./App";
import rootReducer from "./reducers";

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

const persistedReducer = persistReducer(persistConfig, rootReducer);

const store = configureStore({
  reducer: persistedReducer,
  middleware: getDefaultMiddleware({
    serializableCheck: {
      ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
    }
  })
});

let persistor = persistStore(store);

ReactDOM.render(
  <Provider store={store}>
    <PersistGate loading={null} persistor={persistor}>
      <App />
    </PersistGate>
  </Provider>,
  document.getElementById("root")
);
rikinshah23 commented 4 years ago

@aldreth Thanks for the code πŸ‘

I am using redux-persist with redux toolkit. here is my store configuration.

I haven't implemented or configured store before. I intend to persist user state after login. Currently after login, if I reload app in emulator it always goes back to login screen.

is my store configured properly?

import {configureStore} from '@reduxjs/toolkit';
import authReducer from '../features/login/authSlice';
import AsyncStorage from '@react-native-community/async-storage';
import {persistReducer, persistStore} from 'redux-persist';
import {combineReducers} from 'redux';
import hardSet from 'redux-persist/lib/stateReconciler/hardSet';
import autoMergeLevel2 from 'redux-persist/lib/stateReconciler/autoMergeLevel2';

const reducers = combineReducers({
  auth: authReducer,
  // other reducers goes here...
});

const persistConfig = {
  key: 'root',
  storage: AsyncStorage,
//  stateReconciler: hardSet,
};

const _persistedReducer = persistReducer(persistConfig, reducers);

export const store = configureStore({
  reducer: _persistedReducer,
});
export const persistor = persistStore(store);

Here in index.js, I use PersistGate

import React from 'react';
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import {store, persistor} from './src/stores/store';
import {Provider} from 'react-redux';
import {storeUser, storeRefreshToken} from './src/features/login/authSlice';
import {PersistGate} from 'redux-persist/lib/integration/react';

const RNRedux = () => (
  <Provider store={store}>
    <PersistGate loading={null} persistor={persistor}>
      <App />
    </PersistGate>
  </Provider>
);
AppRegistry.registerComponent(appName, () => RNRedux);

image

aldreth commented 4 years ago

@rikinshah23 You need to configure the store as in my comment above


const persistedReducer = persistReducer(persistConfig, rootReducer);

const store = configureStore({
  reducer: persistedReducer,
  middleware: getDefaultMiddleware({
    serializableCheck: {
      ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
    }
  })
});
rikinshah23 commented 4 years ago

@aldreth Thanks I was able to solve the issue. I was directed to your solution and had help from @markerikson on a separate thread that I created.

Jonathan0wh commented 4 years ago

For anyone else struggling to find out what [someReduxPersistActionType] should be, here's my redux-starter-kit & redux-persist config

import React from "react";
import ReactDOM from "react-dom";
import { configureStore, getDefaultMiddleware } from "redux-starter-kit";
import { Provider } from "react-redux";
import {
  persistStore,
  persistReducer,
  FLUSH,
  REHYDRATE,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER
} from "redux-persist";
import storage from "redux-persist/lib/storage";
import { PersistGate } from "redux-persist/integration/react";

import App from "./App";
import rootReducer from "./reducers";

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

const persistedReducer = persistReducer(persistConfig, rootReducer);

const store = configureStore({
  reducer: persistedReducer,
  middleware: getDefaultMiddleware({
    serializableCheck: {
      ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
    }
  })
});

let persistor = persistStore(store);

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

This should be added to README here regarding the compatibility between redux-persist and redux-toolkit and redux-starter-kit, because redux-toolkit and redux-starter-kit are pretty commonly used.

markerikson commented 4 years ago

We just added a section on this to the RTK Usage Guide page, here:

https://redux-toolkit.js.org/usage/usage-guide#use-with-redux-persist

clarkkozak commented 4 years ago

How would something like await persistor.flush() be called in a slice? It seems that if I import persistor from the store, the reducer no longer works.

gkatsanos commented 4 years ago
+ import { configureStore, getDefaultMiddleware } from "@reduxjs/toolkit";

export const store = configureStore({
  reducer: persistedReducer,
+ middleware: getDefaultMiddleware({
+   serializableCheck: {
+     ignoredActions: ["persist/PERSIST"],
    },
  }),
});

for me this fixed the issue.

ghasemikasra39 commented 3 years ago

Are there any differences between these two?

const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) =>
      getDefaultMiddleware({
        serializableCheck: {
          ignoredActions: ["persist/PERSIST"]
        }
      }).concat(middleware)
});
const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) =>
      getDefaultMiddleware({
        serializableCheck: {
          ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
        }
      }).concat(middleware)
});

because both works for me.

princefishthrower commented 2 years ago

And what if we still get errors after implementing these recommendations? πŸ˜… The serialization error goes away but now I get:

redux-toolkit.esm.js:246 Uncaught (in promise) RangeError: Maximum call stack size exceeded
    at trackProperties (redux-toolkit.esm.js:246)

as soon as I revert back to plain ol' react-redux, this error goes away...

wahas-mughal commented 2 years ago

redux-starter-kit

getDefaultMiddleware() has now moved to @reduxjs/toolkit, and could be used by getting it in the argument of the callback function like this

export const store = configureStore({ reducer: persistedReducer, middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: { ignoreActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], }, }), });

diep89 commented 2 years ago

redux-starter-kit

getDefaultMiddleware() has now moved to @reduxjs/toolkit, and could be used by getting it in the argument of the callback function like this

export const store = configureStore({ reducer: persistedReducer, middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: { ignoreActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], }, }), });

Thanks! Just a comment: correct arg is ignoredActions (more info: link).

Wilsonrx10 commented 1 year ago

quem ainda tiver o mesmo problema atualmente , eu resolvi desse jeito :

export const store = configureStore({ middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false, }), redutor: { user: persistedReducer, }, });

Bird-Forest commented 6 months ago

Hello! My problem is that the data is not saved to localStorage. Use RTK Query. Plese help me. My stor import { combineSlices, configureStore } from '@reduxjs/toolkit'; import { setupListeners } from '@reduxjs/toolkit/query'; import { postsApi } from '../redux/PostsSlice'; import { userApi } from '../redux/UserSlice'; import { persistStore, persistReducer, FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER, } from 'redux-persist'; import storage from 'redux-persist/lib/storage';

const userPersistConfig = { key: 'user', storage, whitelist: ['user'], blacklist: [postsApi.reducerPath, userApi.reducerPath], };

const rootReducer = combineSlices({

[userApi.reducerPath]: persistReducer(userPersistConfig, userApi.reducer), });

export const store = configureStore({ reducer: rootReducer, middleware: getDefaultMiddleware => [ ...getDefaultMiddleware({ serializableCheck: { ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], }, }), postsApi.middleware, userApi.middleware, ], devTools: process.env.NODE_ENV !== 'production', });

setupListeners(store.dispatch); export const persistor = persistStore(store);

My Slice import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const userApi = createApi({ reducerPath: 'user',

baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:4000/api/users' }), tagTypes: ['Users'], endpoints: builder => ({ getUser: builder.query({ query: id => /${id}, providesTags: ['Users'], }), signUpUser: builder.mutation({ query: values => ({ url: '/signup', method: 'POST', body: values, }), invalidatesTags: ['Users'], }), signInUser: builder.mutation({ query: values => ({ url: '/signin', method: 'POST', body: values, }), invalidatesTags: ['Users'], }), updateUser: builder.mutation({ query: values => ({ url: '/update', method: 'PUT', body: values, }), invalidatesTags: ['Users'], }), updateAvatar: builder.mutation({ query: formData => ({ url: '/avatar', method: 'PATCH', body: formData, }), invalidatesTags: ['Users'], }), updateLikes: builder.mutation({ query: ({ id, count }) => ({ url: /${id}/counter, method: 'PATCH', body: count, }), invalidatesTags: ['Users'], }), logOutUser: builder.mutation({ query: () => ({ url: '/logout', // method: 'DELETE', method: 'POST', }), invalidatesTags: ['Users'], }), }), });

export const { useGetUserQuery, useSignInUserMutation, useSignUpUserMutation, useUpdateUserMutation, useUpdateAvatarMutation, useLogOutUserMutation, useUpdateLikesMutation, } = userApi;

The state contains a complex object. user: { queries: {}, mutations: { xkgQOHNKtIxY2Pi6bzmEd: { requestId: 'xkgQOHNKtIxY2Pi6bzmEd', status: 'fulfilled', endpointName: 'signInUser', startedTimeStamp: 1712830350747, data: { id: '6616dd9035336472865f647b', name: 'Tom', email: 'tom@gmail.com', viewsCount: 0, }, fulfilledTimeStamp: 1712830351548 } },

How to get the User from the state and move it to localStorage ?