Integrating Redux to your React App [A Step by Step Guide]

Written by majo_vanilla | Published 2020/05/10
Tech Story Tags: react | redux | react-state | mapstatetoprops-react-redux | mapdispatchtoprops-react-redux | react-top-story | latest-tech-stories | hackernoon-top-story

TLDR React is a way to store your app's component data, keep track of the changes, and then render them faster than using only JavaScript. Using React on your app allows you to update the information you display on the browser without having to reload the whole page. Using Redux ensures you follow a specific path to modify the state, making it almost impossible to change it directly (something you already know you shouldn't do). All this while being incredibly easy to set up. The Data Flow is a great graphic representation of how Redux works.via the TL;DR App

My first approach to React was enough to fall in love with this framework: it's easy to work with, has a simple syntax, and it's so fast that you can build a simple application in less than a day.

Using React on your app allows you to update the information you display on the browser without having to reload the whole page, as it works with a virtual DOM.
One of the best characteristics React has is the state. It is a way to store your app's component data, keep track of the changes, and then render them faster than using only JavaScript.
However, once your app starts growing, managing tens of states in multiple containers starts getting difficult and becomes inefficient. That's where Redux comes into play.
Redux is an amazing tool that allows you to keep all your application's state in one centralized
store
.
What's the advantage? Each component's state will now be handled by your Redux store and it will be available for the rest of your application's components without having to send it as
props
from parent to child.
That way it's so easy to work with that centralized store instead of having to navigate through all your containers trying to find where you were passing the props you need when you want to add or change them.
Moreover, using Redux ensures you follow a specific path to modify the state, making it almost impossible to change it directly (something you already know you shouldn't do). All this while being incredibly easy to set up.

Before you start

Got hooked? Ok, let's add Redux to your application. You will need a running app that uses React. I won't talk you through that process in this article, but you can check the React documentation if you need help.
Just in case, I will remind you of some key concepts I will be talking about in this article:
  • Store: the object that contains the state of the application.
  • Actions: objects that describe what you want to change on the Redux store's state.
  • Reducers: functions that handle actions and tell the Redux store how to modify the state.
The image above it's a great graphic representation of how Redux works. With that Data Flow in mind, let's get to work!

Installing and Setting Up Redux

To install Redux on your app, you will need to use
Node
. On your terminal, cd into your project's directory and type
npm install redux react-redux
. This will not only install Redux but also add another package (react-redux) to connect your app with the Redux store.
Since you are working on an already functional React app, you will only need to add and change a few things on your components to connect them to the new store that will handle the state.
I will explain the process in a somewhat reverse way: you will create the store, link the reducers, then build them, and finally, you will create the actions file.
The above process will need you to import files and functions before creating them. I believe this way is better because you get to see the big picture first and then work with the details, making it less likely to miss a step.
1. Edit to your index.js

This is the file where rendering to the DOM. In there, you will need to:
import { createStore } from 'react-redux'
and also
import { Provider } from 'Redux'
.

With those functions you will create the Redux store where your app's state will live, and then make it available to all the components using the Provider. Your index.js file should look something like this:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './App';
import rootReducer from './store/reducers/rootReducer';

const store = createStore(rootReducer);

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}><App /></Provider>
  </React.StrictMode>,
  document.getElementById('root'),
);
Notice how you need to create the store using
const store = createStore(rootReducer)
that takes a reducer as an argument (you will create the reducer next).

Also, remember to enclose your App component by the Provider component you just imported and passing the store as a prop.
2. Create the rootReducer file

On the above example you already are importing a reducer called
rootReducer
from the corresponding location and passing it as an argument to
createStore()
.
import rootReducer from './store/reducers/rootReducer';

const store = createStore(rootReducer);
Depending on the size of your application you can have one or more reducers to specify how the application's state changes. That is perfect, as long as you connect all of them using the rootReducer.
Now let's create that file in the proper location and start working with it.

First, you need to
import { combineReducers } from 'redux'
. This will allow you to connect all the reducers your application has. In this case I'm using only two, but you can have as many as needed (remember to import them, too).
import { combineReducers } from 'redux';
import authReducer from './authReducer';
import postReducer from './postReducer';

const rootReducer = combineReducers({
  auth: authReducer,
  posts: postReducer,
});

export default rootReducer;
The above code shows how to import all the reducers your app has (remember we are going backward, but you can create them before adding them to the rootReducer, it's your choice).
As you can see,
combineReducers()
takes an object as an argument (if you have more than one reducer). The object's key will be the name of the state (and you will use this exact name in your components), and the object's value will specify which reducer to use to change the Redux state.
There are other ways to set up the rootReducer and to use this method. You can check more details on the Redux documentation here and here.
3. Create the reducers
Let's say you are building a collective blog and you want to keep track of the posts your users create and update them on the main page. You will need a reducer to track the state and render it when it changes.
A reducer is just a function that explains how to modify your state, depending on the given action (more on that later).
A good practice is to keep all your reducers (including the rootReducer) in a Reducers folder
Now, on your reducer file, you will need: 1) an
initialState
that stores a default version of your state (your app will use it the first time), and 2) the
reducer function
.
You should pay attention to the name of your reducer and make sure it is the same you included on the rootReducer file.
const initState = {
  posts: [],
};

const postReducer = (state = initState, action) => {
  if (action.type === 'CREATE_POST') {
    const post = {
      id: action.id,
      title: action.title,
      category: action.category,
    };

    const nextPosts = { ...state.posts, post }; // state.posts is refering to the key of the rootReducer

    return { ...state, posts: nextPosts }; // adds the new object to the current state
  }

  return state;
}

export default postReducer;
As you can see, the reducer function takes
initState
and
action
as arguments. It then checks all the actions to find the match and make the adjustments to the state.
In the example above, I am using the
'CREATE_POST'
action to create a new post and then add it to the current
state.posts
that I previously referred to on the rootReducer. After that, I return a new object with the final state and the modified
posts.state
.
4. Actions and actions' creators
Now, where does that
'CREATE_POST'
come from? It is a type of action. Actions are the way you communicate with the store. It's the only information that will be sent to it.
You can start by creating an
actions
folder to store your action files. To follow the example above, that folder would contain a
postActions.js
file that looks like this:
// This is the action creator
export function addPost(title, content) {

  // This is the action
  return {
    type: 'ADD_POST',
    title,
    content,
  };
}

// This is the action creator
export function deletePost(id) {

  // This is the action
  return {
    type: 'DELETE_POST',
    id,
  };
}
The code above shows a list of
action creators
or functions that enclose and return objects called
actions
. This function is the one you will call in the component when you want to update the state.
You can add as many actions and action creators as you need. I will just include two to create and delete posts. Inside each action, you need to return the action's
type
to indicate what action to choose when sending the information to the store.
Each action will take different arguments, depending on the necessary information to change the state. Those arguments are also returned. For example,
addPost()
takes two arguments:
title
and
content
to create a new post. You need to return them inside each action so that they are accessible to change the state.
5. Connect your component to the Redux store
Ok, remember we are going backward. This is the final step. Up until now, you haven't changed anything on your app, just added more files. Now will be the time to get rid of the state you have on each component and instead connect them to the store.
To do this, we will import a function called
{ connect } from 'react-redux'
into our component's file.
import React from 'react';
import PostSummary from './PostSummary';
import { connect } from 'react-redux';

const PostList = props => {
  const { posts } = props;

  return (
    <div className="post-list section">
      {posts && posts.map(post => (
        <PostSummary post={post} key={post.id} />
      ))}
    </div>
  )
};

const mapStateToProps = state => ({
  posts: state.posts,
});

export default connect(mapStateToProps)(PostList);
This is a usual component that uses Redux to store the state. Don't worry if you don't understand everything, I will dissect it in a few.
Following the blog example, this would be the component that renders the list of posts. It doesn't have a state (we don't need it) but we are passing some
props
.
I am using destructuring to create the constant
posts
from the props. That constat it's an array with a list of all the posts taken directly from the Redux store.
How is that happening? The code is including a
mapStateToProps()
:
const mapStateToProps = state => ({
  posts: state.posts,
});
This function takes the application's state as a parameter and then returns an object with the piece of state that you need, and passes it to the component in the form of
props.
After setting that up, you need to:
  1. Export the component while calling
    connect()
    .
  2. Pass the recently created
    mapStateToProps()
    as an argument.
  3. Add the component's name between parenthesis. You can read more about how
    connect()
    works, here.
export default connect(mapStateToProps)(PostList);
Then, the PostList component can connect to the Redux store, grab the posts from the state, and then render it or do whatever we want with it.
But, how do we change the state? In a very similar way, we can add another function that will dispatch one of our action creators and modify the state without changing something we don't want to.
const mapDispatchToProps = dispatch => ({
  addPost: post => {
    dispatch(addPost(post.title, post.content));
  },
});

export default connect(mapStateToProps, mapDispatchToProps)(PostList);
Don't be intimidated by the code above. I will explain line by line and you can also check this thorough explanation.
On the first line,
mapDispatchToProps()
takes
dispatch()
as the first argument. On line two, it returns a new function
addPost()
with
post
as the argument.
Finally, on the third line, the function calls
dispatch()
inside, and passes the action creator
addPost()
as the argument. If you remember,
addPost()
takes the post's title and content as arguments, so we need to pass them, too as
post.title
and
post.content
.
It might be confusing to see two
addPost()
but please keep in mind that they are not the same. You could easily change the first
addPost()
name to something entirely different with no errors, but you can't do the same with the second one, since it's referring to the action creator.
That's why you should also remember to import the action creator using
import { addPost } from '../actions/'
in the beginning of your file.
With
mapDispatchToProps()
we are not only connecting to the Redux store but also making use of the previously created actions and reducers to change the store's state.
You can return multiple functions inside
mapDispatchToProps()
, depending on what you want your component to do and how you want it to interact with your application's state.
As you will see after migrating all the component states to one centralized store, your application's flow will be more organized and you will be able to access the state from anywhere, making your website more flexible.
A quick reminder: not all component states are useless when working with Redux, and there will be cases when you want your Redux store to handle some parts of the state, and your components another part.
And that's it! You should have a fully functional React - Redux application!
Overview:
  1. Install
    redux
    and
    react-redux
    .
  2. Create the
    store
    and enclose your
    app component
    with the
    Provider
    .
  3. Create your
    rootReducer
    with connect().
  4. Create the individual reducers (if necessary).
  5. Build your
    action creators
    .
  6. Create
    mapStateToProps()
    and
    mapDispatchToProps()
    functions on your components.
  7. Export your component with
    connect().
  8. Delete your previous component state.
I would love to hear from you! Please send a message with your feedback on this article to any of my social media Twitter / LinkedIn / Github

Written by majo_vanilla | Full-stack software developer
Published by HackerNoon on 2020/05/10