Search code examples
next.jsreduxredux-toolkit

Why action doesn't properly dispatch using redux toolkit?


I have this slice:

const studentSlice = createSlice({
  name: 'studentSlice',
  initialState: {
    data: {},
    loading: false,
    error: null,
  },
  reducers: {
    setData: (state, action) => {
      state.data = action.payload;
    },
    setLoading: (state, action) => {
      state.loading = action.payload;
    },
    setError: (state, action) => {
      state.error = action.payload;
    },
  },
});
export const { setData, setLoading, setError } = studentSlice.actions;
export default studentSlice.reducer;

I have tried to create an action that uses multiple reducer:

export const getStudentData =
  (): any => async (dispatch: any) => {
    try {
      console.log("here");
      dispatch(setLoading(true));
      const {
        data,
        isLoading,
        error
      } = await personApi.endpoints.getStudentList.useQueryState(); // Using RTK Query
      console.log(data);

      if (isLoading) {
        return;
      }

      if (error) {
        throw error;
      }

      dispatch(setData(data));
      dispatch(setLoading(false));
    } catch (error) {
      dispatch(setError(error));
    }
  };

In my Components, using useEffect, I am trying to dispatch the action

const StudentProfile = () => {
    const dispatch = useDispatch();
    useEffect(() => {
        dispatch(getStudentData())
    }, [])
    
    const students = useSelector((state: any) => state.student.data))

    return ... // some components that use this students
}

But the getStudentData action doesn't execute (no log printed). Why this happened and how to dispatch the action properly?


I have recently tried using extraReducers here but the state of the slice doesn't change either.


Here is my API slice code:

const personApi = api.injectEndpoints({
  endpoints: (builder) => ({
    getStudentList: builder.query<StudentsResult, void>({
      query: () => ({
        url: 'students',
        method: 'POST',
        body: JSON.stringify({
          // some fields
        }),
      }),
      providesTags: ['Students'],
    }),
  }),
});

Solution

  • I want to store the data from createApi to the [studentSlice] slice.

    You can create reducer cases to match the pending, fulfilled, and rejected statuses of active queries/mutations.

    const studentSlice = createSlice({
      name: 'studentSlice',
      initialState: {
        data: {},
        loading: false,
        error: null,
      },
      reducers: {
        ....
      },
      extraReducers: builder => {
        builder
          .addMatcher(
            personApi.endpoints.getStudentList.matchPending,
            (state) => {
              state.loading = true;
            },
          )
          .addMatcher(
            personApi.endpoints.getStudentList.matchFulfilled,
            (state, action) => {
              state.loading = false;
              state.error = null;
              state.data = action.payload;
            },
          )
          .addMatcher(
            personApi.endpoints.getStudentList.matchRejected,
            (state, action) => {
              state.loading = false;
              state.error = action.payload;
            },
          );
      },
    });
    

    Initiate the getStudentList query using either the generated useGetStudentList or useLazyGetStudentList hooks.

    const queryResult = useGetStudentList();
    
    const [getStudents, queryResult] = useLazyGetStudentList();
    

    Alternatively, if you really still wanted to use a Thunk, then use createAsyncThunk so the pending, fulfilled, and rejected actions are generated for you, and initiate the endpoint manually. Note that since you still need the extraReducers that there's no net gain over the preferred method above using the query matchers.

    Example:

    import { createAsyncThunk } from '@reduxjs/toolkit';
    
    export const getStudentData = createAsyncThunk(
      "students/getStudentData",
      async (_, thunkApi) => {
        try {
          const { data, error } = await thunkApi.dispatch(
            personApi.endpoints.getStudentList.initiate()
          );
    
          if (error) {
            throw error;
          }
    
          return data;
        } catch (error) {
          return thunkApi.rejectWithValue(error);
        }
      }
    );
    
    const studentSlice = createSlice({
      name: 'studentSlice',
      initialState: {
        data: {},
        loading: false,
        error: null,
      },
      reducers: {
        ....
      },
      extraReducers: builder => {
        builder
          .addCase(getStudentData.pending, (state) => {
            state.loading = true;
          })
          .addCase(getStudentData.fulfilled, (state, action) => {
            state.loading = false;
            state.error = null;
            state.data = action.payload;
          })
          .addCase(getStudentData.rejected, (state, action) => {
            state.loading = false;
            state.error = action.payload;
          });
      },
    });