Search code examples
reactjsreduxredux-routerredux-toolkit

React toolkit and redux-first-router


I am digging into React with Redux for a rewrite of our product. A lot of fog around Redux was cleared by using Redux-Toolkit https://redux-toolkit.js.org/. Then I found that React-Router made state management messy and found a solution in redux-first-router https://github.com/faceyspacey/redux-first-router.

Now I want to combine these excellent libraries. But I think I'm doing something wrong in the configuration. Here is the code. Starting with a sandbox example at https://codesandbox.io/s/m76zjj924j, I changed the configureStore.js file into (for simplicity I have omitted code for the user reducer)

import { connectRoutes } from 'redux-first-router';
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'
import { routePaths } from '../routes';

const { reducer: location } = connectRoutes(routePaths);

const {
    middleware: routerMiddleware,
    enhancer: routerEnhancer,
    initialDispatch
  } = connectRoutes(routePaths, { initialDispatch: false });

export default function configureRouteStore() {
  const store = configureStore({
    reducer: {

      location: location
    },
    middleware: [...getDefaultMiddleware(), routerMiddleware],
    enhancers: (defaultEnhancers) => [routerEnhancer, ...defaultEnhancers]
  })
  initialDispatch();
  return store;
}

But now each time a change in route = Redux store is updated, I get an exception in the browser:

index.js:1 A non-serializable value was detected in the state, in the path: `location.routesMap.PROFILE.thunk`. Value: dispatch => {
  dispatch(USER_DATA_LOADED({
    avatar: null
  }));
  const avatar = `https://api.adorable.io/avatars/${Math.random()}`;
  setTimeout(() => {
    // fake async call
    dispatch(USER_… 
Take a look at the reducer(s) handling this action type: HOME.

I can see that this stems from the routes definitions if the route has a 'thunk' property defined as this: PROFILE: { path: "/profile/:username", thunk: fetchUserData },

If I change the thunk property to a serializable value (or remove it) the error is gone. Somehow now the thunk is added to the payload of the action to update paths. What...?

What to do? OK, I can get it work with the traditional Redux setup but as I am a big fan the redux toolkit it would be sweet for me and maybe a few more people out there to make it work with the toolbox.


Solution

  • I'm a Redux maintainer and creator of Redux Toolkit.

    Based on that error message and reading the Redux-First-Router source code, it looks like the library is indeed attempting to store thunk functions in the Redux store. This is a problem, because we specifically instruct users to never put non-serializable values like functions in state or actions.

    By default, Redux Toolkit adds a "serializable state invariant middleware" that warns you if non-serializable values are detected in state or actions, to help you avoid accidentally making this mistake.

    It is possible to pass some options to getDefaultMiddleware() to customize the behavior of these middlewares. There is currently an ignoredActions option, but I don't think we have an option to ignore specific sections of the state tree. The included redux-immutable-state-invariant middleware does have an ignore option for portions of the state, so perhaps we could add that approach.

    I've added https://github.com/reduxjs/redux-toolkit/issues/319 to see if we can add an option like that.

    In the meantime, you could potentially turn off the middleware by calling getDefaultMiddleware({serializableCheck: false}).

    update

    I've just published Redux Toolkit v1.2.3, which adds an ignoredPaths option to the serializability check middleware to allow ignoring specific keypaths within the state.

    Again, please note that this is purely an escape hatch to work around misbehaving libraries, and should not be used as a regular approach.