Rematch with Hooks

Published on June 24, 2019

cover picture

If you’ve been using Rematch for managing state in your application, the latest release of react-redux that adds support for hooks should get you really excited.

Rematch has always tried to keep compatibility with existing react-redux API, and that stays true for the newly released version that supports hooks!

In addition to having more readable and reusable code, hooks allow us to do less work when adding type safety with TypeScript.

Let’s look at an example of doing things “the old way”. Here’s a component that stores a list of users in Redux store, and loads them when the component is mounted:

import React, { useEffect, FC } from 'react'
import { connect } from 'react-redux'
import UserList from '../../components/UserList'
import { RootState, RootDispatch } from '../../store'
type UsersProps = ReturnType<typeof mapState> & ReturnType<typeof mapDispatch>
const Users: FC<UsersProps> = ({ users, load }) => {
useEffect(() => {
load()
}, [])
return <UserList users={users} />
}
const mapState = (state: RootState) => ({
users: state.users,
})
const mapDispatch = (dispatch: RootDispatch) => ({
load: dispatch.users.load,
})
export default connect(mapProps, mapDispatch)(Users)

This code looks and works fine, but there are a couple of issues that we couldn’t address in pre-hook era. One of them is related to typing connect component. Higher order components are notoriously difficult to type properly due to the difficulty of inferring types of the properties being passed down to the component inside. To get around this problem, we have to define types for props being passed to the component separately (type UsersProps), and then manually set them for the component (FC<UsersProps>).

With hooks, we can replace mapState function with useSelector hook, mapDispatch with useDispatch, and we can drop our difficult-to-type connect HOC altogether, leaving us with concise and fully typed code:

import React, { useEffect, FC } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import UserList from '../../components/UserList'
import { RootState, RootDispatch } from '../../store'
const Users: FC = () => {
const users = useSelector((state: RootState) => state.users)
const dispatch = useDispatch<RootDispatch>()
useEffect(() => {
dispatch.users.load()
}, [])
return <UserList users={users} />
}
export default Users

If we need to work with multiple actions, we can create a custom useRematchDispatch hook that allows us to have the familiar syntax that we used for writing mapDispatch functions:

import React, { useEffect, FC } from 'react'
import { useSelector, useDispatch } from 'react-redux'
import UserList from '../../components/UserList'
import { RootState, RootDispatch } from '../../store'
const useRematchDispatch = <D extends {}, MD>(selector: (dispatch: D) => MD) => {
const dispatch = useDispatch<D>()
return selector(dispatch)
}
const Users: FC = () => {
const users = useSelector((state: RootState) => state.users)
const { load } = useRematchDispatch((dispatch: RootDispatch) => ({
load: dispatch.users.load
}))
useEffect(() => {
load()
}, [])
return <UserList users={users} />
}
export default Users

useRematchDispatch hook can also come in handy if we want to refactor existing Rematch application, because it allows us to copy mapDispatch functions with minimum changes.


If you want to learn more about Rematch, check out my free course on YouTube.