How Redux Reducers Work
In this tutorial, we’re going to learn the concept of reducers and how they work, specifically in React applications. In order to understand and better use Redux, a solid understanding of reducers is essential. Reducers provide a way to update an application’s state using an action. It is an integral part of the Redux library.
This tutorial is for developers who want to learn more about Redux Reducers. An understanding of React and Redux would be beneficial. At the end of the tutorial, you should have a better understanding of the role Reducers play in Redux. We’ll be writing code demos and an application to better understand Reducers and how it affects the state in an application.
What Is A Reducer
A Reducer is a pure function that takes the state of an application and action as arguments and returns a new state. For example, an authentication reducer can take an initial state of an application in form of an empty object and an action that tells it that a user has logged in and returned a new application state with a logged-in user.
Pure functions are functions that do not have any side effects and will return the same results if the same arguments are passed in.
Below is an example of a pure function:
const add = (x, y) => x + y;
add(2, 5);
The example above returns a value based on the inputs, if you pass 2
and 5
then you’d always get 10
, as long as it's the same input nothing else affects the output you get, that’s an example of a pure function.
Below is an example of a reducer function that takes in a state and an action.
const initialState = {};
const cartReducer = (state = initialState, action) => {
// Do something here
}
Let’s define the two parameters that a reducer takes in, state
and action
.
State
A state is the data that your component(s) is working with — it holds the data that a component requires and it dictates what a component renders. Once a state
object changes, the component re-renders. If an application state is managed by Redux, then the reducer is where state changes happen.
Action
An action, is an object that contains the payload of information. They are the only source of information for the Redux store to be updated. Reducers update store based on the value of the action.type
. Here we will define the action.type
as ADD_TO_CART
.
According to the official Redux documentation, actions are the only things that trigger changes in a Redux application, they contain the payload for changes to an application store. Actions are JavaScript objects that tell Redux the type of action to be performed, usually they’re defined as functions like the one below:
const action = {
type: 'ADD_TO_CART',
payload: {
product: 'margarine',
quantity: 4
}
}
The code above is a typical payload
value that contains what a user is sending and it will be used to update the state of the application. As you can see from above, the action object contains the type of action and a payload object that would be necessary for this particular action to be performed.
Updating State Using Reducers
To show how reducers work, let’s look at the number counter below:
const increaseAction = {
type: 'INCREASE',
};
const decreaseAction = {
type: 'DECREASE'
};
const countReducer = (state = 0, action) => {
switch(action.type){
case INCREASE:
return state + 1;
case DECREASE :
return state -1;
default:
return state;
}
};
In the code above, increaseAction
and decreaseAction
are actions used in the reducer to determine what the state
is updated to. Next, we have a reducer function called countReducer
, which takes in an action
and an initial state
whose value is 0
. If the value of action.type
is INCREASE
, we return a new state that is incremented by 1, else if it is DECREASE
a new state that is decremented by 1 is returned. In cases where none of those conditions are meant, we return state
.
Updating State Using Reducers: The Spread Operator
State can’t be directly changed, to create or update state, we can use the JavaScript spread operator to make sure we don’t change the value of the state directly but instead to return a new object that contains a state passed to it and the payload of the user.
const contactAction = {
type: 'GET_CONTACT',
payload: ['0801234567', '0901234567']
};
const initialState = {
contacts: [],
contact: {},
};
export default function (state = initialState, action) {
switch (action.type) {
case GET_CONTACTS:
return {
...state,
contacts: action.payload,
};
default:
return state;
}
In the code above, we are using a spread operator to make sure we don’t change the state value directly, this way we can return a new object that is filled with the state that is passed to it and the payload that’s sent by the user. By using a spread operator, we can make sure that the state stays the same as we add all new items to it and also replace the contacts field in the state if it was present before.
Redux Reducers In Action — A Demo
To better understand Redux Reducers and how they work, we will be implementing a simple Movie details finder app, the code and working version can be found here on Codesandbox. To get started, go to your terminal and initialize a react app using the command below:
create-react-app movie-detail-finder
Once our project initialized, next let’s install the packages we’d need for our application.
npm i axios reactstrap react-redux redux redux-thunk
Once, the packages are installed, let’s start our development server using the command:
npm start
The above command should start our project development server in our browser. Next let’s open our project in our text editor of choice, inside our project src
folder, delete the following files: App.css
, App.test.js
, serviceWorker.js
and setupTests.js
. Next, let’s delete all code that references the deleted files on our App.js
.
For this project, we will be using Open Movie Database API to get our movie information, content and images for our application, here is a link to the API, you’d need to register and get access keys in order to use it for this application, Once you’re done, let’s proceed with our application by building components.
Building App Components
First, inside of our src
folder in our project directory, create a folder called components and inside the folder, let’s create two folders called Movie
and Searchbar
, our component should look like the image below:
Building Movie Component
Let’s build the Movies
component, which will outline the structure of the movie details we will be getting from our API. To do this, inside the Movies
folder of our component, create a new file Movie.js
, next create a class based component for the API results, let’s do that below.
import React, { Component } from 'react';
import { Card, CardImg, CardText, CardBody, ListGroup, ListGroupItem, Badge } from 'reactstrap';
import styles from './Movie.module.css';
class Movie extends Component{
render(){
if(this.props.movie){
return (
<div className={styles.Movie}>
<h3 className="text-center my-4">
Movie Name: {this.props.movie.Title}
</h3>
<Card className="text-primary bg-dark">
<CardImg className={styles.Img}
top src={this.props.movie.Poster}
alt={this.props.movie.Title}/>
<CardBody>
<ListGroup className="bg-dark">
<ListGroupItem>
<Badge color="primary">Actors:</Badge>
{this.props.movie.Actors}
</ListGroupItem>
<ListGroupItem>
<Badge color="primary">Genre:</Badge>
{this.props.movie.Genre}
</ListGroupItem>
<ListGroupItem>
<Badge color="primary">Year:</Badge>
{this.props.movie.Year}
</ListGroupItem>
<ListGroupItem>
<Badge color="primary">Writer(s):</Badge>
{this.props.movie.Writer}
</ListGroupItem>
<ListGroupItem>
<Badge color="primary">IMDB Rating:</Badge>
{this.props.movie.imdbRating}/10
</ListGroupItem>
</ListGroup>
<CardText className="mt-3 text-white">
<Badge color="secondary">Plot:</Badge>
{this.props.movie.Plot}
</CardText>
</CardBody>
</Card>
</div>
)
}
return null
}
}
export default Movie;
In the code above, Using components from the package reactstrap
, you can check out the documentation here. We built a Card component that includes the movie name, Image, genre, actor, year, movie writer, rating, and plot. To make it easier to pass data from this component, we built data to be as props to other components. Next, let’s build our Searchbar
component.
Building Our Searchbar Component
Our Searchbar
component will feature a search bar and a button component for searching movie components, let’s do this below:
import React from 'react';
import styles from './Searchbar.module.css';
import { connect } from 'react-redux';
import { fetchMovie } from '../../actions';
import Movie from '../Movie/Movie';
class Searchbar extends React.Component{
render(){
return(
<div className={styles.Form}>
<div>
<form onSubmit={this.formHandler}>
<input
type="text"
placeholder="Movie Title"
onChange={e => this.setState({title: e.target.value})}
value={this.state.title}/>
<button type="submit">Search</button>
</form>
</div>
<Movie movie={this.props.movie}/>
</div>
)
}
}
In the code above, we are importing connect
from react-redux
which is used to connect a React component to the Redux store, provides the component with information from the store and also provides functions used to dispatch actions to the store. Next, we imported the Movie
component and a function fetchMovie
from actions.
Next, we have a form tag with an input box for entering our movie titles, using the setState
hook from React, we added an onChange
event and value that will set the state of title
to the value entered in the input box. We have a button
tag to search for movie titles and using the Movie
component that we imported, we passed the properties of the component as props
to the result of the search.
Next for us is to write a function to submit our movie title to the API in order to send results to us, we also need to set the initial state of the application. let’s do that below.
class Searchbar extends React.Component{
state = {
title: ''
}
formHandler = (event) => {
event.preventDefault();
this.props.fetchMovie(this.state.title);
this.setState({title: ''});
}
Here, we set the initial state of the application to empty strings, we created a function formHandler
that takes in an event parameter and passes the fetchMovie
function from action and setting the title as the new state of the application. To complete our application, let’s export this component using the connect property from react-redux
, to do this we’d use the react redux mapToStateProps
property to select the part of the data our component would need, you can learn more about mapToStateProps
here.
const mapStateToProps = (state) => {
return { movie: state.movie }
}
export default connect(mapStateToProps, { fetchMovie })(Searchbar)
Let’s add styles to our form by creating a file Searchbar.module.css
and adding the styles below:
.Form{
margin: 3rem auto;
width: 80%;
height: 100%;
}
input{
display: block;
height: 45px;
border: none;
width: 100%;
border-radius: 0.5rem;
outline: none;
padding: 0 1rem;
}
input:focus, select:focus{
border: 2px rgb(16, 204, 179) solid;
}
.Form button{
display: block;
background: rgb(16, 204, 179);
padding: 0.7rem;
border-radius: 0.5rem;
width: 20%;
margin-top: 0.7rem;
color: #FFF;
border: none;
text-decoration: none;
transition: all 0.5s;
}
button:hover{
opacity: 0.6;
}
@media(max-width: 700px){
input{
height: 40px;
padding: 0 1rem;
}
.Form button{
width: 40%;
padding: 0.6rem;
}
}
Once we’ve done the above, our search bar component should look similar to the image below:
Creating Actions For Application
In this component, we will be setting up Redux actions for our application, First, inside the src
directory, create a folder named actions
and inside the folder, we’d create an index.js
file. Here we’d create a function fetchMovie
that takes in a title parameter, and fetches movie from the API using Axios. Let’s do this below:
import axios from 'axios';
export const fetchMovie = (title) =>
async (dispatch) => {
const response = await
axios.get(
https://cors-anywhere.herokuapp.com/http://www.omdbapi.com/?t=${title}&apikey=APIKEY
);
dispatch({
type: 'FETCH_MOVIE',
payload: response.data
})
}
In the code above, we imported axios
and created a function called fetchMovie
which takes in a title
parameter by using async/await so that we can make a request to the API server. We have a dispatch
function that dispatches to the Redux the action object that is passed to it. From what we have above, we’re dispatching an action with the type FETCH_MOVIE
and the payload that contains the response we got from the API.
NOTE: The apikey
in the request will be replaced with your own apikey
after registering at OmdbAPI.
Creating App Reducers
In this section, we are going to create reducers for our application.
const fetchMovieReducer = (state = null, action) => {
switch(action.type){
case 'FETCH_MOVIE':
return action.payload;
default:
return state;
}
}
const rootReducer = (state, action) => {
return {
movie: fetchMovieReducer(state, action)
}
}
export default rootReducer;
In the code above, we created a fetchMovieReducer
that takes in a default state of null
and an action
parameter, using a switch operator, for case FETCH_MOVIE
we will return the value of the action.payload
which is the movie we got from the API. If the action we tried performing isn’t in the reducer, then we return our default state.
Next, we created a rootReducer
function that will accept the current state and an action as input and returns the fetchMovieReducer
.
Putting It Together
In this section, we’d finish our app by creating our redux store in the index.js
, let’s do that below:
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import App from './App';
import 'bootstrap/dist/css/bootstrap.min.css';
import './index.css';
import reducers from './reducers';
const store = createStore(reducers, applyMiddleware(thunk))
ReactDOM.render(
<Provider store={store}>
<>
<App/>
</>
</Provider>,
document.getElementById('root')
)
In the code above, we created the application store
using the createStore
method by passing the reducer we created and a middleware. Middlewares are addons that allow us to enhance the functionalities of Redux. Here we are making use of the Redux Thunk middleware using applyMiddleware
. The Redux Thunk middleware is necessary for our store to do asynchronous updates. This is needed because by default, Redux updates the store synchronously.
To make sure our application knows the exact store to use, we wrapped our application in a Provider
component and passed the store as a prop, by doing this, other components in our application can connect and share information with the store.
Let’s add a bit of style to our index.css
file.
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
body{
background: rgb(15, 10, 34);
color: #FFF;
height: 100vh;
max-width: 100%;
}
Rendering And Testing A Movie Detail Finder
In this section, we are going to conclude our application by rendering our application in our App.js
, to do this, let’s create a class-based component named App
and initialize our Searchbar and input field.
import React from 'react';
import Searchbar from './components/Searchbar/Searchbar';
import styles from './App.module.css';
class App extends React.Component{
render(){
return(
<div className={styles.App}>
<h1 className={styles.Title}>Movies Search App</h1>
<Searchbar/>
</div>
)
}
}
export default App;
Here, we created an App class based component with a h1
that says Movie Search App and added our Searchbar
component. Our application should look like the image below:
A working demo is available on Codesandbox.
Conclusion
Reducers are an important part of Redux state management, with reducers we can write pure functions to update specific areas of our Redux applications without side effects. We’ve learned the basics of Redux reducers, their uses, and the core concept of reducers, state, and arguments.
You can take this further by seeing the documentation on Redux reducers here. You can take this further and build more on Redux reducers, let me know what you build.
Resources
- React-Redux documentation
- Redux documentation
connect()
functionapplyMiddleware
function