reactjstypescriptreduxreact-reduxredux-toolkit

Migration from redux to redux-toolkit made react app significantly slow


I have this react app, where i used to write redux code in an old way, i used redux and redux-thunk, but, updating this packages to newer version, i had this warning where the function that i used to create my store in the app just became deprecated, and they recommended to migrate to RTK.

But, after changing all the major stuff to this new way of writing redux, everything became slower, multiple actions are taking more time to get done, and that is impacting the UI, which feels uncomfortable.

So, i want to show you the way i'm writing my actions, my reducers, how i'm updating data and pretty much how i've done everything.

This is the store/config

import { configureStore } from '@reduxjs/toolkit';
import { useDispatch } from 'react-redux';

import reducers from './reducers';

export const storeConfig = configureStore({
    reducer: reducers,
    devTools: true,
});

// Types for dispatching actions and store's data itself

type AppDispatch = typeof storeConfig.dispatch;
export type reducersType = ReturnType<typeof reducers>;

// Types for custom useDispatch

export const useTypedDispatch = () => useDispatch<AppDispatch>();

These are my reducers

import { combineReducers } from '@reduxjs/toolkit';

import layout from './layoutReducer';
import pickupsNoAssign from './pickupsNoAssignReducer';

const reducers = combineReducers({ layout, pickupsNoAssign });

export default reducers;

This is my layout reducer with his actions


export interface ILayoutInitialState {
    authentication?: IAuthentication;
    authorizations?: IAuthorizations;

    applicationOption?: IAppOptionsPayload;
    isLeftDrawerOpen: boolean;

    terminalSeleccionada?: string | number;
    terminalNumber?: number;

    serverError: string;
}

const initialState: ILayoutInitialState = {
    isLeftDrawerOpen: true,
    serverError: '',
};

const layoutReducer = createSlice({
    name: layout,
    initialState,
    reducers: {
        selectedTerminal: (state, { payload: { id, terminal } }: PayloadAction<ISelectedTerminalPayload>) => {
            state.terminalSeleccionada = terminal;
            state.terminalNumber = id;
        },
        selectedAppOption: (state, { payload }: PayloadAction<IAppOptionsPayload>) => {
            state.applicationOption = payload;
        },
        leftDrawerAction: (state) => {
            return {
                ...state,
                isLeftDrawerOpen: !state.isLeftDrawerOpen,
            };
        },
    },
    extraReducers: (builder) => {
        // authenticating

        builder.addCase(authenticating.fulfilled, (state, { payload }) => {
            const isThereToken = localStorage.getItem('accessToken');

            if (!isThereToken) {
                localStorage.setItem('accessToken', JSON.stringify({ ...payload.token }));
            }

            state.authentication = payload?.token;

            state.authorizations = payload.data;

            state.applicationOption = payload.appSelected;

            state.terminalSeleccionada = `${payload?.token.terminal}-${payload?.token.abreviado}`;

            state.terminalNumber = payload?.token.terminal;
        });

        builder.addCase(authenticating.rejected, (state, { payload }) => {
            state.serverError = payload?.errorMessage!;
        });
    },
});

export const { selectedTerminal, selectedAppOption, leftDrawerAction } = layoutReducer.actions;

export default layoutReducer.reducer;
import { createAsyncThunk } from '@reduxjs/toolkit';

import {
    ITerminales,
    IAuthenthicatingParameters,
    IAuthenthicatingPayload,
} from '../../domain/entities/redux/interfaceLayout';
import { layout } from '../../domain/helpers/types';
import { gettingPermission } from '../../infrastructure/services/api/layout/appPermissions';

// Validating authorizations

export const authenticating = createAsyncThunk<
    IAuthenthicatingPayload,
    IAuthenthicatingParameters,
    {
        rejectValue: { errorMessage: string };
    }
>(`${layout}/authenticating`, async ({ token, navigate, goTo }, { rejectWithValue }) => {
    try {
        const data = await gettingPermission(token);

        if (data && data.isError !== true && data.response !== null) {
            // Selecting default app option
            const optionTitle = data.response[0].permisos.aplicaciones.find(
                (single) => single.id === 'sigo-distribucion',
            )?.menus[0].nombre;

            const optionApp = data.response[0].permisos.aplicaciones.find((single) => single.id === 'sigo-distribucion')
                ? data.response[0].permisos.aplicaciones.find((single) => single.id === 'sigo-distribucion')!.menus[0]
                      .sub_menu![0].nombre
                : undefined;

            // Adding default terminal
            const addDefaultTerminal: ITerminales = { id: token.terminal, abreviatura: token.abreviado };

            data.response[0].terminales.push(addDefaultTerminal);

            navigate(goTo);

            return { token, data, appSelected: { optionTitle, optionApp } };
        }
    } catch (err) {
        console.log(err);
    }
    return rejectWithValue({ errorMessage: 'Hubo un fallo al obtener los permisos' });
});

I'm pretty much saving user data, and manipulating some options in the UI.

Am i updating the state with bad practices ? Am i writing my actions/api calls (with createAsyncThunk) wrong ?

What could be causing such behavior ?


Solution

  • As mentioned RTK actually adds immutability and serializability checks: https://redux-toolkit.js.org/api/getDefaultMiddleware

    This is very likely the root cause of the delay, I have experience with the immutability check and it can take tens of seconds to validate some very large stores.

    RTK claim that the checks are not enabled in production mode, however, if you want to disable them completely do the following (code snipped from the above link + some small modification):

    const store = configureStore({
      reducer: rootReducer,
      middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware({
          thunk: true,
          serializableCheck: false,
          immutableCheck: false,
        }),
    })