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
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
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.
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!
Frequently Asked Questions
What are Redux and Hooks in React?
Redux is an application state management library for JavaScript. It can be used with UI frameworks or layers in JavaScript, including React. React Redux is a Redux UI binding library for React, which binds the two libraries together and helps optimize performance between them. Hooks are functions that let function components use state and other features in React applications.
What is useSelector used for?
UseSelector() is a custom hook included in the React Redux library, and it is used to extract data from the Redux store state for use in a React component. It does so by using a selector function.
What is useDispatch used for?
The useDispatch() hook is used to dispatch actions to the Redux store, and does so by returning a reference to the dispatch function from the Redux store. UseDispatch() is a custom hook included in the React Redux library.