UseSelector and UseDispatch: A Guide to React-Redux Hooks

React-Redux offers a set of hooks that you can use to create cleaner code. This guide will introduce you to two of them: useSelector and useDispatch.
Software Engineer
July 12, 2022
Updated: July 13, 2022
Software Engineer
July 12, 2022
Updated: July 13, 2022

React Redux offers a set of hooks as an alternative to the existing connect() higher-order component. These hooks allow you to connect to the Redux store and dispatch actions without having to wrap your components in connect().

This guide will cover how to implement the React-Redux hooks useSelector and useDispatch in your application.

UseSelector and useDispatch in React Redux

useSelector and useDispatch are a set of hooks to use as alternatives to the existing connect() higher-order component. The equivalent of map state to props is useSelector. It takes in a function argument that returns the part of the state that you want. The equivalent of map dispatch to props is useDispatch. We can invoke useDispatch and store it to a variable, dispatch. Dispatch will work with the allActions imported from the actions folder.

More in Software Engineering10 iOS Development Tips for Swift and Xcode

 

Getting Started

Begin by installing the following in your app:

npm install redux
npm install react-redux

The examples in this guide will be referring to my repository, available here.

Here is a quick gif of the app’s functionality:


 

 

There are two separate states, one for keeping track of the counter and one for keeping track of a logged-in user. We will have separate files to handle each state.

 

Create an Actions and Reducers Folder

A screenshot of an actions and reducers folder in React Redux
An actions and reducers folder. Image: Screenshot

 

Actions

Let’s begin by defining the actions for the counter in counterActions.js. A couple of methods are necessary: increment and decrement. We will export these two methods in an object.

const increment = () => {
    return {
        type: "INCREMENT"
    }
}

const decrement = () => {
    return {
        type: "DECREMENT"
    }
}

export default {
    increment,
    decrement
}

Similarly, let’s define the actions for the current user in userActions.js, which will also have two methods, setUser and logOut, that will be exported in an object.

const setUser = (userObj) => {
    return {
        type: "SET_USER",
        payload: userObj
    }
}

const logOut = () => {
    return {
        type: "LOG_OUT"
    }
}

export default {
    setUser,
    logOut
}

To be organized, we import these two files under one location, the index.js file, within the actions folder. We create a variable allActions and set it to an object that contains the imported actions that will be exported.

import counterActions from './counterActions'
import userActions from './userActions'

const allActions = {
    counterActions,
    userActions
}

export default allActions

 

Reducers

Similar to the approach taken for the actions file structure, we create a separate reducers folder to hold the user and the counter reducers. Let’s start with the counter reducer, counter.js.

A reducer function takes in two arguments, the state and the action. The state doesn’t necessarily have to be set to an object. In this case, the default value of the state is set to an integer.

As we defined earlier, an action returns an object that can contain two keys: type and, optionally, a payload. Based on the action type, the value of the state will be changed. Keep in mind that a default case is necessary in the event that an action type is called that doesn’t exist to prevent the app from breaking.

const counter = (state = 1, action) => {
    switch(action.type){
        case "INCREMENT":
            return state + 1
        case "DECREMENT":
            return state - 1
        default: 
            return state
    }
}

export default counter

For the current user reducer, currentUser.js, the state will be set to an empty object that will contain the keys user and loggedIn. Notice the difference in what is being returned between counter and currentUser. In all cases, the counter reducer returns an integer since its initial value was an integer. In the case of the current user reducer, an object is always returned.

const currentUser = (state = {}, action) => {
    switch(action.type){
        case "SET_USER":
            return {
                ...state,
                user: action.payload,
                loggedIn: true
            }
        case "LOG_OUT":
            return {
                ...state,
                user: {},
                loggedIn: false
            }
        default:
            return state
    }
}

export default currentUser;

We need to combine these reducers into one. Under reducers/index.js, let’s import the reducer files as well as combineReducers:

import {combineReducers} from 'redux'
import currentUser from './currentUser'
import counter from './counter'
import {combineReducers} from 'redux'

const rootReducer = combineReducers({
    currentUser,
    counter
})

export default rootReducer

Combine reducers does what its name implies and combines the separate reducer files into one. It takes in one argument: an object that contains the reducer files. Now that our actions and reducers are set, let’s move on to implementing Redux in our app.

 

Implement Redux

In our index.js under the src folder, we will import the following:

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

A Redux store will be created with the method createStore. It takes in two arguments, the rootReducer, which is the file that combined our reducers, and the Redux devtools extension.

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

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

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

Finally, we wrap our app component with the provider component from React Redux. The provider component will take in one prop, store, which will be set to the store from createStore.

Master Software EngineeringCreating an Npm-Only Build Step for JavaScript — the Easy Way

 

Implement useSelector and useDispatch

We import the following hooks from React Redux: useSelector and useDispatch. Before, we had to import connect() from React Redux and wrap our components with it in order to map state to props and map dispatch to props.

 

useSelector

The equivalent of map state to props is useSelector. It takes in a function argument that returns the part of the state that you want. In this case, we have the following keys from state defined: counter and currentUser. We defined these earlier when combining reducers.

const counter = useSelector(state => state.counter)
// 1
const currentUser = useSelector(state => state.currentUser)
// {}

Thus, the variables counter and currentUser are set to the state defined by their respective reducers.

 

useDispatch

The equivalent of map dispatch to props is useDispatch. We will invoke useDispatch and store it to a variable, dispatch. Dispatch will work with the allActions imported from the actions folder. For example, useEffect calls a dispatch with the following action: allActions.userActions.setUser(user). User is defined as:

const user = {name: "Rei"}

Keep in mind, allActions is an object with userActions and counterActions as keys. Just as a refresher on the setUser function defined in userActions.js:

const setUser = (userObj) => {
   return {
      type: "SET_USER",
      payload: userObj
    }
}

setUser will return an object with type and payload. Dispatch will take this object and look through the reducers that match the action type. In this particular case, it is located in currentUser.js within reducers folder.

case "SET_USER":
   return {
   ...state,
   user: action.payload,
   loggedIn: true
}
import React, {useEffect} from 'react';
import {useSelector, useDispatch} from 'react-redux'
import './App.css';
import allActions from './actions'


const App = () => {
  const counter = useSelector(state => state.counter)
  const currentUser = useSelector(state => state.currentUser)

  const dispatch = useDispatch()

  const user = {name: "Rei"}

  useEffect(() => {
    dispatch(allActions.userActions.setUser(user))
  }, [])

  return (
    <div className="App">
      {
        currentUser.loggedIn ? 
        <>
          <h1>Hello, {currentUser.user.name}</h1>
          <button onClick={() => dispatch(allActions.userActions.logOut())}>Logout</button>
        </> 
        : 
        <>
          <h1>Login</h1>
          <button onClick={() => dispatch(allActions.userActions.setUser(user))}>Login as Rei</button>
        </>
        }
      <h1>Counter: {counter}</h1>
      <button onClick={() => dispatch(allActions.counterActions.increment())}>Increase Counter</button>
      <button onClick={() => dispatch(allActions.counterActions.decrement())}>Decrease Counter</button>
    </div>
  );
}

export default App;

And there you have it. The React-Redux hooks useSelector and useDispatch are implemented in the React App. Compared to the connect() alternative, the code is cleaner and more organized.

Thank you for reading!

Expert Contributors

Built In’s expert contributor network publishes thoughtful, solutions-oriented stories written by innovative tech professionals. It is the tech industry’s definitive destination for sharing compelling, first-person accounts of problem-solving on the road to innovation.

Learn More

Jobs at Bloomberg L.P.

Great Companies Need Great People. That's Where We Come In.

Recruit With Us