React Redux Hooks and TypeScript - Part 2
In the last post, we implemented a Redux store where actions were synchronous. In this post, we are going to start from the same store and make the actions asynchronous.
State
As a reminder, here’s our store state:
type Person = {
id: number;
name: string;
};
type AppState = {
people: Person[];
};
So, our app state contains an array of people.
This app is going to be deliberately simple so that we can focus on the Redux Store and how a React component interacts with it in a strongly-typed manner.
Allowing the store to work with asynchronous actions
We are going to use Redux Thunk to allow Redux to work with asynchronous actions. This is middleware that we define when the store is created:
function configureStore(): Store<AppState> {
const store = createStore(
rootReducer,
undefined,
applyMiddleware(thunk) );
return store;
}
applyMiddleware
is a function from Redux that allows us to add middleware to the store, and thunk
is Redux Thunk’s middleware.
Actions and action creators
Our action creators are now going to be asynchronous. Here is the action creator for adding a person:
const addPerson = (personName: string) => async (
dispatch: Dispatch<AddPersonAction>
) => {
await wait(200);
dispatch({
type: "AddPerson",
payload: personName,
} as const);
};
We are faking an asynchronous function with a wait
function in our example.
The addPerson
function returns a function rather than the action object as it did when the function was synchronous. The inner function takes in a parameter, dispatch
, which is another function that is used to dispatch the action. That’s a lot of functions!
Notice that we have explicitly typed the dispatch
parameter with Dispatch<AddPersonAction>
. Dispatch
is a standard type from Redux that we’ve passed the type for our action, AddPersonAction
, into. Here’s the definition for AddPersonAction
:
type AddPersonAction = {
readonly type: "AddPerson";
readonly payload: string;
};
Here’s the action creator for removing a person:
type RemovePersonAction = {
readonly type: "RemovePerson";
readonly payload: number;
};
const removePerson = (id: number) => async (
dispatch: Dispatch<RemovePersonAction>
) => {
await wait(200);
dispatch({
type: "RemovePerson",
payload: id,
} as const);
};
Reducer
The reducer action
parameter type is a created differently to the synchronous version:
type Actions =
| AddPersonAction
| RemovePersonAction;
We inferred the action types in the synchronous version, but here we have explicitly created the types.
The reducer function is exactly the same as the synchronous version:
function peopleReducer(
state: Person[] = [],
action: Actions
) {
switch (action.type) {
case "AddPerson":
return state.concat({
id: state.length + 1,
name: action.payload,
});
case "RemovePerson":
return state.filter(
(person) => person.id !== action.payload
);
default:
neverReached(action);
}
return state;
}
function neverReached(never: never) {}
Cool!
Connecting components
Connecting React components to the store is exactly the same as synchronous version. useSelector
is used to get data from the store:
const people: Person[] = useSelector(
(state: AppState) => state.people
);
useDispatch
is used to invoke store actions:
const dispatch = useDispatch();
...
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
dispatch(addPerson(newPerson));
...
};
...
const dispatchNewPerson = (id: number) => () => {
dispatch(removePerson(id));
};
...
<button onClick={dispatchNewPerson(person.id)}>Remove</button>
Nice!
This example can be found in CodeSandbox at https://codesandbox.io/s/react-redux-async-e0zcw?file=/src/index.tsx
Wrap up
Redux needs a library like Redux Thunk to handle asynchronous actions. React components interact with synchronous and asynchronous actions in the same way. The main difference is in the action creators. Asynchronous action creators return a function that eventually dispatches an action object, whereas synchronous action creators return the action object immediately.
If you to learn more about using TypeScript with React, you may find my course useful: