Sie sind auf Seite 1von 10

Clean Up Redux Code with React-Redux Hooks

Max González Follow


Jul 24, 2019 · 6 min read

preducks, a React-Redux prototyping app (with an accompanying npm module) that generates
boilerplate code using React-Redux hooks and TypeScript.

If you search “Redux hooks” on Google, you won’t find a lot of resources about
the cool new React-Redux hooks API. What you will find are bunch of articles
about how to use React hooks as a replacement for Redux and other state
management libraries.

But this was never the point of hooks. React hooks were made to allow
developers to write functional components that can do all the things we once
needed class components for, like using state or lifecycle methods. React hooks
improve the developer experience by optimizing the organization,
maintainability, and readability of React code. They allow us to share logic
between components without using complicated design patterns like render
props or higher-order components, and they cut down on the dense boilerplate
required by JavaScript classes. They don’t really add any new functionality to
React; functional components using hooks can’t do anything that class
components can’t. But they do allow us to write much cleaner code.

If these improvements make you feel comfortable with using React to manage
your application state without an external library, go for it! But React hooks are
by no means the death of state management libraries like Redux. In fact, React-
Redux version 7.1.0, released in June 2019, provides developers with its own set
of hooks, with methods like useSelector() and useDispatch() . Like React’s

hooks API, these hooks let us cleanly organize our logic within the body of a
functional component. They also eliminate the need to wrap our components in
a higher-order connect() component to connect to our store.

Let’s look at a ridiculously simple example to see how this works. (This assumes
basic knowledge of how React and Redux applications work.)

Here’s a ridiculously simple example page.

Here we have a web page that lets you increment and decrement a counter.
Super exciting, right? Behind the scenes, we have a whole React and Redux app
setup to manage the state of the counter. (Fork this GitHub repo and play around
with the code!)
1 export const increment = count => ({
2 type: 'CHANGE_COUNT',
3 payload: count + 1
4 });
5
6 export const decrement = count => ({
7 type: 'CHANGE_COUNT',
8 payload: count - 1
9 });

actions.js hosted with by GitHub view raw


Here are some action creators to increment and decrement the counter in our store. I could have
declared a CHANGE_COUNT constant to protect against misspellings, but the store is small
enough to skip that step.

1 import { createStore } from 'redux';


2
3 const initialState = {
4 count: 0
5 }
6
7 const counterReducer = (state = initialState, action) => {
8 switch(action.type) {
9 case 'CHANGE_COUNT':
10 return {
11 ...state,
12 count: action.payload
13 }
14 default:
15 return state;
16 }
17 };
18
19 export default createStore(counterReducer);

A reducer to change the count in the Redux store, exported as a store.

This is business as usual for Redux. The new hooks API doesn’t reduce this part
of the boilerplate. Now let’s look at how we connect our store to the React app
with the counter:
1 import React from 'react';
2 import {render} from 'react-dom';
3 import App from './components/App.jsx';
4 import {Provider} from 'react-redux';
5 import store from './reducers/counterReducer';
6
7 render(
8 <Provider store={store}>
9 <App />
10 </Provider>,
11 document.getElementById('root')
12 );

Business as usual once again. When we call render(), we wrap our App component in the Redux
Provider component to give our application access to the Redux store. React-Redux hooks don’t
change this either.

Now let’s look at how our App component would get access to data from the
Redux store without using the React-Redux hooks API:
A React component connecting to the Redux store without React-Redux hooks.

This is how we get our simple counter app to work with React and Redux. But
that’s a lot of boilerplate for something so simple. Let’s take a look at what’s
going on here. First of all, we have a constructor that does nothing but give our
class component access to the props being passed down to it. This component
doesn’t actually need to be a class component since it’s not using local state or
any lifecycle methods, so let’s quickly refactor it to a functional component so we
can bring in the hooks:
Okay, that’s a bit nicer, but it’s still more complicated than it needs to be. We still
need to define mapStateToProps() and mapDispatchToProps() functions to
connect to the Redux store, and wrap our App component in the connect()

higher-order component so we can pass these pieces of the store and dispatching
functions down to our App component as props. We also have to write
this.props over and over again (or, to avoid that, we can use object
destructuring assignment like I did to assign our props to variables) in order to
access these pieces of the store and dispatching functions. It’s all a bit messy,
isn’t it? Let’s clean this up a bit.

First, let’s look at useSelector() . This new React-Redux hook takes the place of

mapStateToProps() and allows you to directly hook into your Redux store
without needing to pass state as props from a higher-order component. This
function takes a callback as an argument. That callback takes the entire Redux
store as its argument, but you don’t have to worry about grabbing the store
yourself, because useSelector() gets it from the Provider that we wrapped our
App component in. In the body of the callback, you can return whichever piece
of the store you want to have access to and save it as a variable in your
component.

Since our Redux store’s initial state has a count property with the initial value of
0 , we can write const count = useSelector(store => store.count) which selects
the count property from our store and assigns it to the count constant we just
declared. Before we press any buttons, that variable will have the value of 0.

Let’s refactor our component to use the useSelector() hook.


1 import React from 'react';
2 import {useSelector, connect} from 'react-redux';
3 import * as actions from '../actions/actions';
4
5 const App = props => {
6 const {increment, decrement} = props;
7 const count = useSelector(store => store.count);
8
9 return (
10 <div>
11 <h1>The count is {count}</h1>
12 <button onClick={() => increment(count)}>+</button>
13 <button onClick={() => decrement(count)}>-</button>
14 </div>
15 );
16 }
17
18 const mapDispatchToProps = dispatch => ({
19 increment: count => dispatch(actions.increment(count)),
20 decrement: count => dispatch(actions.decrement(count))
21 });
22
23 export default connect(null, mapDispatchToProps)(App);

This is a lot nicer already. We can get direct access to values from our Redux
store now, and not rely on some higher-order component we didn’t write to pass
it to our component as props. We can reference our count constant anywhere in
the body of our component and it will have the current value from our Redux
store. If any actions are dispatched that update the count property in our store,
our count constant will be updated as well. We can simply call useSelector()

again with a different selector and assign it to another variable if we need to


make another selection from the store.

But, speaking of dispatching actions, our mapDispatchToProps() function still


forces us to use connect() to get functions as props that we’d rather directly
access in our component. Let’s do something about this.
The other main React-Redux hook, useDispatch() , takes care of this for us. This

one is super simple. Just call useDispatch() in your component, and it will
return a function you can use to dispatch actions to your Redux store. Pass a call
to an action creator to this returned function, and it will work just like a function
from mapDispatchToProps() .

Let’s refactor our component one last time to implement the useDispatch()

hook:

1 import React from 'react';


2 import * as actions from '../actions/actions';
3 import {useSelector, useDispatch} from 'react-redux';
4
5 const App = () => {
6
1 const
importdispatch = useDispatch();
React from 'react';
7
2 const
importcount = useSelector(store
{connect} => store.count);
from 'react-redux';
8
3 import * as actions from '../actions/actions';
9
4 return (
10
5 <div>
class App extends React.Component {
11
6 <h1>The count{is {count}</h1>
constructor(props)
12
7 <button onClick={() => dispatch(actions.increment(count))}>+</button>
super(props);
13
8 } <button onClick={() => dispatch(actions.decrement(count))}>-</button>
14
9 </div>
15
10 );
render() {
16
11 }
const {count, increment, decrement} = this.props;
17
12
18
13 export
return default
( App;
14 <div>
Now we don’t have to define our dispatch functions outside of our component and rely on a HOC
15 <h1>The count is {count}</h1>
to pass them as props. With useDispatch(), we can define them, or just inline them, right in our
16 <button onClick={() => increment(count)}>+</button>
component!
17 <button onClick={() => decrement(count)}>-</button>
18 </div>
Now
19 that);
we can access our store and the dispatch function directly in our
20 }
component, there’s no longer any need to wrap App in a higher-order
21 }
component
22
to pass anything to it as props. This results in much cleaner code
where
23 everything
const is defined
mapStateToProps where
= store => ({it’s relevant. If you need to change any logic
24 count: store.count
25 });
pertaining to Redux, you won’t have to look outside of the body your component
anymore. Now, everything is nicely contained.

Obviously, this example is so simple that it hardly makes sense to use Redux here
(or even React, really). But these hooks can be used the exact same way they are
here, even if they’re in a more complex component that also uses React hooks
like useState() or useEffect() .

Admittedly, there still is a lot of time-consuming boilerplate writing you need to


do to set up a React-Redux app, even if you’re using the hooks APIs. Prototyping
apps such as preducks (pictured at the top of the article) can help you eliminate
a lot of the mindless setup and truly optimize your developer experience.
(There’s even a node module for extremely quick setup: think create-react-app
but with Redux boilerplate too). preducks also uses TypeScript. Much like
hooks, TypeScript also improves your developer experience by bringing strict,
static type checking into JavaScript, letting you eliminate bugs and keep better
track of the shape you expect your data to be. This gets useful when your React-
Redux apps get large and your data gets complex. Give preducks a try if you’re
interested in seeing React Redux hooks in action (along with React hooks and
TypeScript).

(For a more complex example of how to use React-Redux hooks along with
TypeScript, check out this GitHub repo showing how to build a to-do app with
these technologies.)

React Redux Hooks Typescript JavaScript

About Help Legal


1 import React from 'react';
2 import {connect} from 'react-redux';
3 import * as actions from '../actions/actions';
4
5 const App = props => {
6 const {count, increment, decrement} = props;
7
8 return (
9 <div>
10 <h1>The count is {count}</h1>
11 <button onClick={() => increment(count)}>+</button>
12 <button onClick={() => decrement(count)}>-</button>
13 </div>
14 );
15 }
16
17 const mapStateToProps = store => ({
18 count: store.count
19 });
20
21 const mapDispatchToProps = dispatch => ({
22 increment: count => dispatch(actions.increment(count)),

Das könnte Ihnen auch gefallen