in Front End

Typing React (4) Routing

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 and REDIRECT 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 pushbecomes 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 .

Write a Comment

Comment