Search code examples
javascriptreactjstypescriptreact-reduxredux-toolkit

Changing from Redux to Redux Toolkit


I am new to React and am trying to learn by coding.

I need some help/advice with the code, with converting this Redux store to Redux Toolkit. Here I'm using a function called configureStore. What is good way of changing this into using the 'configureStore' which comes from '@reduxjs/toolkit'?

This is for learning purposes. That 'createRootReducer' comes from my reducers.js which combines

const createRootReducer = (history) => combineReducers({
    articles: articlesReducer,
    selection: selectionReducer,
});

My store.js file:

import { createBrowserHistory } from "history";
import { createStore, compose, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { routerMiddleware } from "connected-react-router";
import createRootReducer from "./reducers";

export const history = createBrowserHistory();

const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

export default function configureStore(preloadedState) {
  const store = createStore(
    createRootReducer(history),
    preloadedState,
    storeEnhancers(applyMiddleware(routerMiddleware(history), thunk))
  );
  return store;
}


Solution

  • Note in advance:

    There is an open issue related to connected-react-router.

    In order to get your setup to work, make sure to install history v4.10.1 - newer versions are causing errors:

    Uncaught Could not find router reducer in state tree, it must be mounted under "router" #312


    1. Middleware updates

    The redux-dev-tools and redux-thunk are already included in redux-toolkit.

    If you need to import additional middleware, you can add these in by using getDefaultMiddleware.

    getDefaultMiddleware is useful if you want to add some custom middleware, but also still want to have the default middleware added as well:

    So with this in mind, you can remove redux-thunk from your package.json.


    2. Remove redux imports

    You no longer need to import createStore, compose, applyMiddleware, combineReducers from redux. All of these are handled internally in the configureStore API provided by @reduxjs/toolkit.

    You can also remove redux from package.json.


    3. Apply args to configureStore from @reduxjs/toolkit.


    The updated store could look like this:

    // IMPORTANT: be sure to install history v4.10.1
    // see open issue: https://github.com/supasate/connected-react-router/issues/312#issuecomment-647082777
    import { createBrowserHistory, History } from "history";
    import { configureStore } from "@reduxjs/toolkit";
    import {
      routerMiddleware,
      connectRouter,
      RouterState
    } from "connected-react-router";
    import selectionReducer from "./reducers/selection";
    import articlesReducer from "./reducers/articles";
    import todosReducer, { I_TodoState } from "./reducers/todos";
    
    export const history = createBrowserHistory();
    
    // combineReducers will be handled internally by configureStore
    const rootReducer = (history: History<any>) => ({
      articles: articlesReducer,
      selection: selectionReducer,
      todos: todosReducer,
      router: connectRouter(history)
    });
    
    const preloadedState = {};
    export const store = configureStore({
      middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware().concat(routerMiddleware(history)),
      reducer: rootReducer(history),
    
      preloadedState
    });
    

    If you pass an object to the reducer param in configureStore, the reducers will be combined. So you no longer need to make a rootReducer with combineReducers


    Here is a demo link.


    From your initial post, it looks like you only had three middlewares:

    __REDUX_DEVTOOLS_EXTENSION_COMPOSE__, thunk, and routerMiddleware.

    The errors you are seeing happen because @redux/toolkit is offering extra protection for correct immutability and serialization of your state. It does so by including redux-immutable-state-invariant in its default middleware.

    Your prior setup did not have this middleware, and that's why you are only seeing these errors now. If you had redux-immutable-state-invariant installed, you would've seen these errors in your previous setup.

    To achieve an identical setup to what you had before, you do not need to include the defaultMiddleware, however it would be a very good idea to go through your reducers and see why your state is not immutable and/or serializable.

    Here is an identical setup to what you had before, only with @redux/toolkit

    import { configureStore } from '@reduxjs/toolkit';
    import { routerMiddleware, connectRouter } from 'connected-react-router';
    import { createBrowserHistory } from 'history';
    import thunk from 'redux-thunk';
    import { rootReducer } from './reducer';
    
    export const history = createBrowserHistory();
    
    // combineReducers will be handled internally by configureStore
    const rootReducer = (history) => ({
      articles: articlesReducer,
      selection: selectionReducer,
      router: connectRouter(history)
    });
    
    const preloadedState = {};
    export const store = configureStore({
      middleware: [thunk, routerMiddleware(history)],
      reducer: rootReducer(history),
      preloadedState,
    });
    

    It looks like the dev tools are configured already: Store Setup, so I did not add them here. You should be able to still use them in your browser's developer tools.

    You should look into why your current state is not immutable/serializable. It is possible there are circular references in your state, or your state is being directly mutated somewhere. This can lead to some nasty bugs down the line, because Redux only truly works if the state is immutable.

    However, you can still use @redux/toolkit with your current setup.