Search code examples
reactjsreduxasync-awaitredux-thunkredux-reducers

Redux: Call thunk action from slice reducer action


I have a tree structure which is loading children on demand, this is my reducer. The problem I have is that when I want to call my thunk action from toggleExpandedProp I get exception (see bellow). What should I do?

import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { useDispatch } from 'react-redux';
import axios from 'axios';

const dispatch = useDispatch()

export const getRoot = createAsyncThunk('data/nodes/getRoot', async () => {
    const response = await axios.get('http://localhost:5000/api/nodes/root');
    const data = await response.data;
    return data;
});

export const getChildren = createAsyncThunk('data/nodes/getRoot', async params => {
    const response = await axios.get('http://localhost:5000/api/nodes/' + params.id + '/children');
    const data = await response.data;
    return data;
});

const initialState = {
    data: [],
    loading: 'idle'
};

// Then, handle actions in your reducers:
const nodesSlice = createSlice({
    name: 'nodes',
    initialState,
    reducers: {
        toggleExpandedProp: (state, action) => {
            state.data.forEach(element => {
                if(element.id === action.payload.id) {
                    element.expanded = !element.expanded;
                    dispatch(getChildren(element));
                }
            });
        }
    },
    extraReducers: {
      // Add reducers for additional action types here, and handle loading state as needed
      [getRoot.fulfilled]: (state, action) => {
        state.data = action.payload;
      },
      [getChildren.fulfilled]: (state, action) => {
        state.data.push(action.payload);
      }
    }
  })

export const { toggleExpandedProp } = nodesSlice.actions;

export default nodesSlice.reducer;

Exception has occurred. Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.

Solution

  • const dispatch = useDispatch()
    

    You can only use useDispatch inside of a function component or inside another hook. You cannot use it at the top-level of a file like this.

    You should not call dispatch from inside a reducer. But it's ok to dispatch multiple actions from a thunk. So you can turn toggleExpandedProp into a thunk action.

    You probably need to rethink some of this logic. Does it really make sense to fetch children from an API when expanding a node and then fetch them again when collapsing it?

    export const toggleExpandedProp = createAsyncThunk(
      "data/nodes/toggleExpandedProp",
      async (params, { dispatch }) => {
        dispatch(getChildren(params));
      }
    );
    

    This is kind of a useless thunk since we don't actually return anything. Can you combine it with the getChildren action, or do you need to call that action on its own too?

    const nodesSlice = createSlice({
      name: "nodes",
      initialState,
      reducers: {
      },
      extraReducers: {
        [toggleExpandedProp.pending]: (state, action) => {
          state.data.forEach((element) => {
            if (element.id === action.payload.id) {
              element.expanded = !element.expanded;
            }
          });
        },
        [getRoot.fulfilled]: (state, action) => {
          state.data = action.payload;
        },
        [getChildren.fulfilled]: (state, action) => {
          state.data.push(action.payload);
        }
      }
    });