Diego
Diego

Reputation: 681

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 ?

Upvotes: 0

Views: 2256

Answers (2)

foloinfo
foloinfo

Reputation: 693

Thanks to the previous answer, my solution was slightly different, so I will add it here.

Make sure you disable/remove redux-logger if you have getDefaultMiddleware().concat(logger) like from this official example.
It says that the logger middleware is disabled for production, but somehow not in my case. The logger was making the production iOS app very slow and causing a lot of frame drops (Android and dev builds were fine).

I ended up doing this.

const store = configureStore({
  middleware: getDefaultMiddleware => {
    if(process.env.NODE_ENV === 'production') {
      // Don't add any dev related middleware in production
      return getDefaultMiddleware({
        serializableCheck: false,
        immutableCheck: false,
      })
    }else{
      return getDefaultMiddleware({
        serializableCheck: {
          // redux-persist
          ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'],
        },
      }).concat(logger)
    }
  },

Upvotes: 0

Mihail Vratchanski
Mihail Vratchanski

Reputation: 439

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,
    }),
})

Upvotes: 3

Related Questions