Using Redux with Hooks in React

Using Redux in your projects may seem really cumbersome with all of the boilerplate that it requires. However, with hooks, using Redux with React can be much less of a daunting experience. This post assumes that you’re working with create-react-app and are familiar with Redux and React.

Part 1: index.js

First, install the following dependencies.

npm i redux react-redux

Once you have them installed, let’s go to your index.js where you will need to add these imports to it.

import { createStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './reducers';

The rootReducer function is a reducer which will be fired once the app first starts up. We have yet to create this function but we will do so very soon. While you’re still in index.js, add/edit these lines of code.

const store = createStore(
  rootReducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
);

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

As you can see above, the createStore function takes rootReducer as an argument which generates the initial store (Redux jargon for something that holds the entire state tree of the app). The second argument in createStore is just something you can add to make the Redux DevTools Chrome extension work.

The Provider component imported from react-redux wraps around the entire application and provides app-wide access to the store.

Part 2: Actions

Actions are just objects that contain a type and payload. I have a separate folder in my src folder called actions which contains index.js. This file generates all of my actions. Below is an example of how the file should look like.

export const increment = id => {
  return {
    type: 'INCREMENT',
    payload: id
  };
};

export const decrement = id => {
  return {
    type: 'DECREMENT',
    payload: id
  };
};

The increment and decrement functions are action creators. They are named exports and simply return an action when called.

Part 3: Reducers

Similar to what we did with actions, we will create another directory in the src folder and call it reducers. It will contain an index.js. It is very likely that you want more than one top-level key in your Redux store object. Redux has a utility function that combines all of your reducers, it is called combineReducers. My index.js file in the reducers folder looks like this:

import { combineReducers } from 'redux';
import countersReducer from './countersReducer';
import usersReducer from './usersReducer';

const rootReducer = combineReducers({
  counters: countersReducer,
  users: usersReducer
});

export default rootReducer;

This rootReducer is the same one that we imported and passed as an argument in the createStore call earlier in our main index.js file.

The countersReducer and usersReducer reducers are imported and organized in the store using combineReducers.

Before we get into an example of what a reducer looks like, let’s quickly talk about what a reducer is. A reducer is a function that receives two arguments: the previous state and an action. Its main function is to modify the previous state with an action to return a new state. A reducer should always be a pure function, which means that the return value is the same for the same arguments. Its evaluation has no side effects.

Here is an example of what my countersReducer.js looks like:

const countersReducer = (counters = [], action) => {
  switch (action.type) {
    case 'INCREMENT':
      return counters.map(counter => {
        if (counter.id === action.payload) {
          counter.count += 1;
        }
        return counter;
      });
    case 'DECREMENT':
      return counters.map(counter => {
        if (counter.id === action.payload) {
          counter.count -= 1;
        }
        return counter;
      });
    default:
      return counters;
  }
};

export default countersReducer;

As you can see, the reducer receives two arguments (previous state and an action). We generally use a switch statement to return a new state based on the type of action. Don’t forget to add the default case at the end.

Part 4: Pulling Data from Redux Store & Dispatching Actions

Hooks provided to us by the react-redux package make it easy to pull data from our Redux store and dispatch actions. First import these into the desired component.

import { useDispatch, useSelector } from 'react-redux';
import { increment, decrement } from '../actions'; // Wherever your actions folder is located relative to your component

To gather data from your store use the useSelector hook.

const counters = useSelector(state => state.counters);

It’s as simple as that. As for dispatching actions, all you need to do is this:

const dispatch = useDispatch();

return (
  <button onClick={() => dispatch(increment(id))}>
    Increment
  </button>
);

The increment function gets called and returns an action. The dispatch function then simply passes the action and calls the store’s reducing function along with the current state. This is the only way to trigger a state change in Redux.

Conclusion

I hope the information and instructions were clear in this post. If not, you can always leave me a comment here and I’ll get back to you as soon as I can.

Leave a Reply

Your email address will not be published. Required fields are marked *