Changing from Redux to Redux Toolkit
Asked Answered
L

2

17

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;
}
Lorin answered 8/10, 2021 at 22:4 Comment(0)
O
19

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.

Outcome answered 9/10, 2021 at 0:32 Comment(19)
i did exactly as you said, but getting new kind of error: 1: redux-toolkit.esm.js:301 Uncaught (in promise) RangeError: Maximum call stack size exceeded at trackProperties (redux-toolkit.esm.js:301) at trackProperties (redux-toolkit.esm.js:312) 2: Uncaught RangeError: Maximum call stack size exceeded at trackProperties (redux-toolkit.esm.js:301) at trackProperties (redux-toolkit.esm.js:312)Lorin
my reducers and action is coded with old redux style and not redux toolkit, can it stay that wat while i converted store to using redux toolkit ?Lorin
Existing reducers and actions work with redux toolkit. The error you posted suggests state is being mutated. The error is thrown from the immutableStateInvariantMiddleware. Perhaps there are circular references in your state causing infinite recursion in the middleware. It's hard for me to tell without seeing your codebase. You could try and see if the error goes away by changing getDefaultMiddleware() to getDefaultMiddleware({ immutableCheck: false }). If it does go away, then you should check and see where the state is being mutated. Redux state should always be immutable.Outcome
its also giving : A non-serializable value was detected in an action, in the path: payload. Value: Take a look at the logic that dispatched this action: {type: 'GET_FLOORPLAN', payload: img} (See redux.js.org/faq/…) (To allow non-serializable values see: redux-toolkit.js.org/usage/…)Lorin
i solved those error with this : export const store = configureStore({ middleware: (getDefaultMiddleware) => getDefaultMiddleware({ immutableCheck: false, serializableCheck: false, }).concat(routerMiddleware(history)), reducer: rootReducer(history), preloadedState, }); but that is not right way, is there a good way to check where the state is mutated, fast way so i can really get away with those?Lorin
You should look through the reducers and see if any state is not being copied and returned. You can get quicker hints about the problematic state by going into your node_modules and putting a console.log where the error are i.e. inside the trackProperites function. You can go into node_modules/@reduxjs/toolkit/dist/redux-toolkit.esm.js and log out some of the parameters passed to trackProperties. It looks like this condition if (!isImmutable(obj)) { is always evaluating to true, which is sending the middleware into an infinite recursion loop.Outcome
i dont have this node_modules/@reduxjs/toolkit/dist/redux-toolkit.esm.js inside my redux folder i have dist, es, lib, src folders there is no toolkitLorin
Maybe just command-click on redux-toolkit.esm.js on the error in your terminal. It will open up the file if you are using vscode. Alternatively, in the root of the project in your terminal, you can run find **/redux-toolkit.esm.js to get the filepath if you're on Mac or Linux (I don't know the command for Windows). You just need to find just looking for the redux-toolkit.esm.js in your node_modules. Unfortunately, it's impossible for me to know any more than this without seeing the source code. I would be just guessing at this point.Outcome
when clicking it on console it takes to this: function trackProperties(isImmutable, ignorePaths, obj, path) { if (ignorePaths === void 0) { ignorePaths = []; } if (path === void 0) { path = ""; } var tracked = { value: obj }; if (!isImmutable(obj)) { tracked.children = {}; for (var key in obj) { var childPath = path ? path + "." + key : key; if (ignorePaths.length && ignorePaths.indexOf(childPath) !== -1) { continue; }tracked.children[key] = trackProperties(isImmutable, ignorePaths, obj[key], childPath);}} return tracked;}Lorin
that code is in red and when hovered it gives: Uncaught (in promise) RangeError: Maximum call stack size exceededLorin
Below the if (!isImmutable(obj)) line, it would be helpful to add a new line with console.log(obj);. It seems like this obj is not immutable, and if you can log it you might get info on the problematic state, which would point you to the problematic reducers. The reason for this is that trackProperties is called recursively if this condition !isImmutable(obj) is true. Infinite recursion errors will yield RangeError: Maximum call stack size exceeded.Outcome
Hey it's a bit hard for me to do this kind of debugging via StackOverflow. I made an update to my original post with some code you can use. The key issue here is that redux toolkit is detecting problems with your state that your prior setup did not detect. See the update I posted above. I hope this helps.Outcome
one question you said 'The redux-dev-tools and redux-thunk are already included in redux-toolkit.' So why you added thunk if its already included, here your code : ' middleware: [thunk, routerMiddleware(history)], 'Lorin
So if you set the middleware array, you need to explicitly include all middleware that you plan to use. From redux-toolkit.js.org/api/configureStore#middleware - "If this option is provided, it should contain all the middleware functions you want added to the store. configureStore will automatically pass those to applyMiddleware. If not provided, configureStore will call getDefaultMiddleware and use the array of middleware functions it returns.Outcome
could you possibly add also one version with typescript in it ? would help alotLorin
The demo link I have posted above is written in TypeScript: codesandbox.io/s/stoic-taussig-kl8jt?file=/src/store.ts When I get some time I can send you an updated link.Outcome
with that it gives me 'Argument of type '(dispatch: any) => Promise<void>' is not assignable to parameter of type 'AnyAction'. Property 'type' is missing in type '(dispatch: any) => Promise<void>' but required in type 'AnyAction'.ts(2345)'Lorin
i made new question related to this error: #69707664Lorin
regards the error Maximum call stack size exceeded: in our case the reason was that we stored an object which was too nested (firebase user). Thats why the trackProperties was calling itself over and over againMag
A
0

I have used the new changes made by redux to setup my store, so there is a lot of changes you can do to this code which is more easier than the one you wrote. let do this

Now you don't need this

import { createStore, compose, applyMiddleware } from "redux";
import thunk from "redux-thunk";

You Need This

 import { configureStore } from '@reduxjs/toolkit';
 import { composeWithDevTools } from '@redux-devtools/extension';

Instead configuring your store like this

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

  return store;

Configure it this way

first try to combine your createReducer like this

       const rootReducer = (history) => ({
  articles: articlesReducer,
  selection: selectionReducer,
  router: connectRouter(history)
});

and then

const store = configureStore({
  reducer: createReducer,
  // for the preloaded, if you have your initial state in diffrent file
  // import and set it like this "preloadedState: myState"
  preloadedState: {},
  devTools: composeWithDevTools(),
  // the thunk middleware will be automatically provided by getDefaultMiddleware
  // and you dont need to import getDefaultMiddleware configureStore will handle it
  middleware: (getDefaultMiddleware) =>   getDefaultMiddleware().concat(routerMiddleware(history)),
});
export default store;

and this video link on how i set my store may help setup redux

Antitrust answered 8/4, 2024 at 23:55 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.