0%
November 15, 2022

Redux Toolkit Quick Setup

react

Dependency

yarn add @reduxjs/toolkit react-redux @types/react-redux

Folder Structures

hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
store.ts
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
import dictSlice from "../slices/dictSlice";

export const store = configureStore({
  reducer: {
    dict: dictSlice.reducer,
  },
  devTools: true,
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;
dictSlice.ts

The following is a simple start-up template:

import { createSlice, PayloadAction } from "@reduxjs/toolkit";

export type DictState = {
  selectedNoteId: string,
  selectedPageId: string,
  selectedVocabId: string,
  imageText: string,
  searchText: string,
};

const initialState: DictState = {
  selectedNoteId: "",
  selectedPageId: "",
  selectedVocabId: "",
  imageText: "",
  searchText: "",
};

const dictSlice = createSlice({
  name: "dict",
  initialState,
  reducers: {
    setSearchText: (state, action: PayloadAction<string>) => {
      state.searchText = action.payload;
    },
  },
});

export default dictSlice;
util/registerEffects.ts
import {
    AnyAction,
    ListenerEffect,
    ListenerMiddlewareInstance,
    ThunkDispatch,
    isAnyOf
} from "@reduxjs/toolkit";
// import msgUtil from "./msgUtil";
// import appSlice from "../redux/slices/appSlice";

type Effect = ListenerEffect<any, unknown, ThunkDispatch<unknown, unknown, AnyAction>, unknown>;

/**
 * actionMessageList consists of objects either of the form { action, content } or  of the form { rejections } / { rejections, content }. When content is absent, the error message is supposed to be returned by thunkAPI.rejectWithValue
 * in createAsyncThunk function.
 */
const messageDispatch = ({ contentType, content }: { contentType: string, content: string }) => {
    if (contentType === "sucesss") {
        // snackbarUtils.success(content)
    } else if (contentType === "info") {
        // snackbarUtils.info(content)
    } else if (contentType === "warning") {
        // snackbarUtils.warning(content)
    } else if (contentType === "error") {
        // snackbarUtils.error(content);
    }
}

export default (
    middleware: ListenerMiddlewareInstance<
        unknown,
        ThunkDispatch<unknown, unknown, AnyAction>,
        unknown
    >,
    actionMessageList: {
        action?: any,
        rejections?: any[],
        content?: string
        effect?: Effect
        contentType?: "sucesss" | "info" | "error" | "warning"
    }[]
) => {
    for (const actionMessage of actionMessageList) {
        const { action, rejections, content, effect, contentType = "sucesss" } = actionMessage;

        if (action) {
            let effect_: Effect;
            if (effect) {
                effect_ = effect;
            } else if (content) {
                effect_ = async (action, { dispatch }) => {
                    messageDispatch({ contentType, content })
                    // dispatch(appSlice.actions.updateNotification(
                    //     { open: true, content: content || "No Message" }
                    // ))
                };
            } else {
                effect_ = async (action, thunkAPI) => { };
            }

            middleware.startListening({ actionCreator: action, effect: effect_ });

        } else if (rejections) {
            if (effect) {
                // @ts-ignore
                middleware.startListening({ matcher: isAnyOf(...rejections), effect });
            } else {
                middleware.startListening({
                    // @ts-ignore
                    matcher: isAnyOf(...rejections),
                    effect: async (action, { dispatch }) => {
                        if (content) {
                            messageDispatch({ contentType, content })
                            // dispatch(appSlice.actions.updateNotification(
                            //     { open: true, content: content || "No Message" }
                            // ))
                        } else {
                            const msg = action?.payload || "";
                            let errMsg = "Failed";
                            errMsg += ` (Reason: ${msg})`;
                            if (msg) {
                                msgUtil.error(msg);
                            }
                        }
                    }
                })
            }

        }
    }
}