blog.jakoblind.no

react-redux with hooks is here!

In an alpha version.

The hottest thing in the React community right now is hooks. And it’s pretty sweet. It makes the code much compact and easy to read and work with.

If you use Redux with React you’re familiar with the connect()() function. This is a higher-order component that injects props to the React components with actions and data from the store.

But now that we are in 2019 you can use hooks for this instead!

Warning: follow this tutorial at your own risk! Hooks are currently in alpha which means that it can be changed or deleted in stable releases. My personal bet is to learn this now even though it's alpha because it's fun. And by the way, feedback is very welcome by the Redux team.

A quick translation of connect()() to hooks

The official docs on this topic are extensive and in-depth. It covers many edge cases. This is a TLDR; version of the docs. I’ll only show you the happy-path. I’ll take you step-by-step and refactor an old connect to using the new hooks.

First an example with the old connect function

Ok so we have the connect function that looks something like this

const AppContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(App);

The mapStateToProps and mapDispatchToProps function for a simple increase counter app looks like this:

const mapStateToProps = state => ({ counter: state.counter })
const mapDispatchToProps = {
  increase: () => ({ type: "INCREASE_COUNTER" })
};

I assume you already know how it works, if not, these posts might help.

An (almost) complete example of a React component using our connect function looks like this:

import React from 'react'
import { connect } from 'react-redux'

const increase = () => ({ type: "INCREASE_COUNTER" });

const App = ({ counter, increase }) => {
  return (
    <div>
      The counter is { counter }
      <button onClick={ increase }>increase</button>
    </div>
  )
}

const mapStateToProps = state => ({ counter: state.counter })
const mapDispatchToProps = {
  increase: () => ({ type: "INCREASE_COUNTER" })
};

const AppContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(App);

The mapStateToProps maps counter from the Redux state to the prop counter that is injected into the React component.

The mapDispatchToProps maps an action creator to the prop increase that is injected into the React component.

What is not visible in this example is that the root component of the app must be wrapped inside the Provider component.

Now let’s translate this to hooks!

First you must install the latest alpha version of react-redux. As I mentioned earlier hooks is not yet available of the stable version of react-redux. At the time of writing this post, 7.1.0-alpha.4 is the latest version. Check out what is the latest version when you read this here.

npm install react-redux@7.1.0-alpha.4 --save

The code that uses hooks looks like this:

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'

const increase = () => ({ type: "INCREASE_COUNTER" });

const App = props => {
  const counter = useSelector(
    state => state.counter
  );
  const dispatch = useDispatch()

  return (
    <div>
        the counter is { counter }
        <button onClick={ () => dispatch(increase()) }>increase</button>
    </div>
  );
}

We use two hooks: useSelector and useDispatch. We use useSelector to get the data out from the store and use it in the component, and we use useDispatch to be able to dispatch action creators. You don’t have to use both useDispatch and useSelector, you can only use one of them if you want.

Just as with connect, we must wrap the root component with Provider for this to work.

useSelector

In this example the useSelector is very similar to the old mapStateToProps - with one exception: it returns a value instead of an object. This is useful if your React component cares about one value from the store, like in this example.

In the old mapStateToProps function we had to return an object even if we only wanted to use one value. We no longer have this limitation with hooks.

useSelector can however still return object if you need to extract many values from the store:

const { counter } = useSelector(
  state => ({ counter: state.counter })
);

useDispatch

We no longer have access to something similar to mapDispatchToProps. The way we now dispatch actions is with calling dispatch and passing in an action. We get the dispatch by calling useDispatch hook.

const dispatch = useDispatch()

And we dispatch it by calling dispatch passing in the return value from the action creator

<button onClick={ () => dispatch(increase()) }>increase</button>

More react-redux hooks

That’s it. I haven’t dig into the details, edge cases, and performance optimizations because the docs do that just perfectly: react-redux hooks docs. In the docs you can also learn about more hooks if you need more fine-grained control like useStore .


tags: