Home > OS >  Getting a 'Actions must be plain objects' error when trying to work with non-async code in
Getting a 'Actions must be plain objects' error when trying to work with non-async code in

Time:02-02

I created a basic setup for my Redux state management using these docs from the Redux site: https://redux.js.org/usage/usage-with-typescript.

My basic setup is as follows and is essentially copied from the Redux docs linked above:

store.ts

export const store = configureStore({
    reducer: {
        counter: counterReducer,
        otherCounter: otherCounterReducer, // this one is currently unused and not related to any errors
    },
});

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;

storeHooks.ts

// Use throughout your app instead of plain `useDispatch` and `useSelector`
type DispatchFunc = () => AppDispatch;
export const useAppDispatch: DispatchFunc = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

counterSlice.ts

// Define a type for the slice state
interface CounterState {
    value: number;
}

// Define the initial state using that type
const initialState = {
    value: 0,
} as CounterState;

export const counterSlice = createSlice({
    name: 'counter',
    // `createSlice` will infer the state type from the `initialState` argument
    initialState,
    reducers: {
        increment: (state) => {
            state.value  = 1;
        },
        decrement: (state) => {
            state.value -= 1;
        },
        // Use the PayloadAction type to declare the contents of `action.payload`
        incrementByAmount: (state, action: PayloadAction<number>) => {
            state.value  = action.payload;
        },
    },
});

export const { increment, decrement, incrementByAmount } = counterSlice.actions;

// Other code such as selectors can use the imported `RootState` type
export const selectCount = (state: RootState) => state.counter.value;

export default counterSlice.reducer;

The issue is that when I try to use this code to dispatch an action with this code I get an error:

CounterPage.tsx

import { useAppDispatch } from '../../hooks/storeHooks';
import {
    increment,
    selectCount,
} from '../../store/features/counter/counterSlice';
import { store } from '../../store/store';

export const CounterPage = () => {
    const count = selectCount(store.getState());
    const dispatchIncrement = useAppDispatch(increment());

    return (
        <ErrorBoundary>
            <h1>Counter</h1>
            <span>Counter: {count}</span>
            <button onClick={dispatchIncrement}>Increment counter</button>
        </ErrorBoundary>
    );
};

Trying to run this code gives me the following error when clicking on the button to increment the count: Uncaught Error: Actions must be plain objects. Use custom middleware for async actions. How can I solve this?

CodePudding user response:

type DispatchFunc = () => AppDispatch doesn't take an argument.

You can just import it from you storeHooks.ts file, where you defined it correctly. useDispatch return a dispatch function, which you then use to dispatch the actions.

import { useAppDispatch } from './storeHooks';
import { selectCount } from './counterSlice.ts';

export const CounterPage = () => {
  const count = useSelectCount();
  const dispatch = useAppDispatch();

  return (
    <>
      <h1>Counter</h1>
      <span>Counter: {count}</span>
      <button onClick={() => dispatch(increment())}>Increment counter</button>
    </>
  );
};

The selector would need to be redefined like this:

export const useSelectCount = () =>
  useAppSelector((state) => state.counter.value);

Also, the hooks are usually defined this way. I'm not sure if the way you're doing it would work.

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

// Aliasing hooks to add types
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
  • Related