reduxredux-toolkitredux-reducersredux-actions

Refactoring with createSlice reduxtoolkit


I'm having trouble refactoring with createSlice, I'm a beginner with redux-toolkit and have looked through the documentation but still having problems.if someone could point me in the right direction that would be fantastic. This is the working code


const SET_ALERT = 'setAlert';
const REMOVE_ALERT = 'alertRemoved';

export const setAlert =
  (msg, alertType, timeout = 5000) =>
  (dispatch) => {
    const id = nanoid();
    dispatch({
      type: SET_ALERT,
      payload: { msg, alertType, id },
    });

    setTimeout(() => dispatch({ type: REMOVE_ALERT, payload: id }), timeout);
  };

const initialState = [];

export default function alertReducer(state = initialState, action) {
  const { type, payload } = action;

  switch (type) {
    case SET_ALERT:
      return [...state, payload];
    case REMOVE_ALERT:
      return state.filter((alert) => alert.id !== payload);
    default:
      return state;
  }
}

Solution

  • Your current setAlert action creator creates a thunk action (an action which takes dispatch as an argument) so it cannot be an action creator that is automatically generated by createSlice.


    createSlice

    You can keep the setup very similar to what you have now. You would have two separate actions for setting and removing an alert and a thunk for dispatching both. The underlying basic actions can be created with createSlice.

    import { createSlice, nanoid } from "@reduxjs/toolkit";
    
    const slice = createSlice({
      name: "alerts",
      initialState: [],
      reducers: {
        addAlert: (state, action) => {
          // modify the draft state and return nothing
          state.push(action.payload);
        },
        removeAlert: (state, action) => {
          // replace the entire slice state
          return state.filter((alert) => alert.id !== action.payload);
        }
      }
    });
    
    const { addAlert, removeAlert } = slice.actions;
    
    export default slice.reducer;
    
    export const setAlert = (msg, alertType, timeout = 5000) =>
      (dispatch) => {
        const id = nanoid();
        dispatch(addAlert({ msg, alertType, id }));
    
        setTimeout(() => dispatch(removeAlert(id)), timeout);
      };
    

    CodeSandbox


    createAsyncThunk

    This next section is totally unnecessary and overly "tricky".

    We can make use of createAsyncThunk if we consider opening the alert as the 'pending' action and dismissing the alert as the 'fulfilled' action. It only gets a single argument, so you would need to pass the msg, alertType, and timeout as properties of an object. You can use the unique id of the thunk which is action.meta.requestId rather than creating your own id. You can also access the arguments of the action via action.meta.arg.

    You can still use createSlice if you want, though there's no advantage over createReducer unless you have other actions. You would respond to both of the thunk actions using the extraReducers property rather than reducers.

    import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
    
    export const handleAlert = createAsyncThunk( "alert/set", (arg) => {
      const { timeout = 5000 } = arg;
      return new Promise((resolve) => {
        setTimeout(() => resolve(), timeout);
      });
    });
    
    export default createReducer(initialState, (builder) =>
      builder
        .addCase(handleAlert.pending, (state, action) => {
          const { alertType, msg } = action.meta.arg;
          const id = action.meta.requestId;
          // modify the draft state and don't return anything
          state.push({ alertType, msg, id });
        })
        .addCase(handleAlert.fulfilled, (state, action) => {
          const id = action.meta.requestId;
          // we are replacing the entire state, so we return the new value
          return state.filter((alert) => alert.id !== id);
        })
    );
    

    example component

    import { handleAlert } from "../store/slice";
    import { useSelector, useDispatch } from "../store";
    
    export const App = () => {
      const alerts = useSelector((state) => state.alerts);
      const dispatch = useDispatch();
    
      return (
        <div>
          {alerts.map((alert) => (
            <div key={alert.id}>
              <strong>{alert.alertType}</strong>
              <span>{alert.msg}</span>
            </div>
          ))}
          <div>
            <button
              onClick={() =>
                dispatch(
                  handleAlert({
                    alertType: "success",
                    msg: "action was completed successfully",
                    timeout: 2000
                  })
                )
              }
            >
              Success
            </button>
            <button
              onClick={() =>
                dispatch(
                  handleAlert({
                    alertType: "warning",
                    msg: "action not permitted"
                  })
                )
              }
            >
              Warning
            </button>
          </div>
        </div>
      );
    };
    
    export default App;
    

    CodeSandbox