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;