In previous post we demonstrated how to use TypeScript with Redux and related libraries. And in this post we will talk about routing, an essential part that almost any React applications have to deal with.
Previous articles:
react-router-dom
The most common approach is using react-router-dom
. To use it we have to install the types as well:
$ npm install --save react-router-dom @types/react-router-dom
I’ll skip the basic setup part since it is very straightforward (see manual). The only thing I want to show is, when we need to use route parameters, we need the component to have match
property, which can be typed through RouteComponentProps
like this:
// Definition of IProps export interface IProps extends RouteComponentProps<{ id?: string }>, ReturnType<typeof mapStateToProps>, ReturnType<typeof mapDispatchToProps> { // other properties } // In component const TodoList: React.FC<IProps> = props => { const { match } = props; if (match.params.id) { ... } }
Note that the template parameter of RouteCompnentProps
is the type of match.params
. The definition of RouteCommponentProps
is:
export interface RouteComponentProps<Params extends { [K in keyof Params]?: string } = {}, ...> { ... }
So the type we provided must be a dictionary with optional string values.
connected-react-router
If you choose to use redux-observable
, then very likely you will need to use connected-react-router
. The reason is that, most of the time we want to use redirect after an asynchronized action completed, but with redux-observable
, asynchronized calls are done in epics, and components have no way to know when the calls are completed. So only epics know the correct timing and handle redirect accordingly. connected-react-router
wrapped <Redirect>
as an action so that we can handle redirects in epics.
Suppose we are on the Todo item details screen, and want to navigate back to item list after saving the item. The action stream should look like this:
- dispatch
TODO:SAVE:REQUEST
- The epic sends the async call on receiving the
TODO:SAVE:REQUEST
action - When async call returns, push
TODO:SAVE:SUCCESS
andREDIRECT
actions to action stream
The trick is that in the last step we need to convert one observable (response of the async call) to another observable with two actions. The epic can be written like this:
export const SaveTodoEpic: RootEpic = (actions$, $state, { todos }) => actions$.pipe( filter(isOfType(getType(listTodo.request))), mergeMap(action => todos.updateTodos$().pipe(mergeMap(res => of(listTodo.success(res), push('/'))), catchError(err => of(updateTodo.failure(err))), );
Since push
becomes one possible type of the action stream, we need to change store/types.d.ts
as well (note the CallHistoryMethodAction
type added to RootAction
:
declare module 'StoreTypes' { export type Store = StateType<typeof import('./index').default>; export type RootAction = ActionType<typeof import('./actions').default> | CallHistoryMethodAction; export type RootState = StateType<ReturnType>typeof import('./reducers').default>>; export type RootEpic = Epic<RootAction, RootAction, RootState, Services>; }
This should solve the problems for routing.
Originally posted at https://itnext.io/typing-react-4-routing-8a67f97930f0 .