7 steps to understand React Redux

Dec 26, 2019☕ ☕ 14 min Follow me on TwitterTraduire en francais

Subscribe to receive the free weekly article

React is awesome, we can't say it enough. But where it comes to the state management part, things become tricky. There is so much terminology to retain: state, store, actions, reducers, middleware, etc. With medium size or bigger react apps, managing our state can be really hard as our application grows. We need to manage it either by redux or alternatives like the context API, flux etc. In this article, we will focus on redux and how it works with react. Redux is a stand-alone library, it's framework agnostic, that's mean you can use it with other frameworks or just vanilla JavaScript.

In this post, i will lead you through 7 steps to understand react redux in the easiest way.

Prerequisites

This post assumes that you have at least a basic to mid-level understanding of React and ES6. Then, you'll need to create a fresh react app with this command:

npx create-react-app react-redux-example

And add to your react app the redux and react-redux packages by running in your shell

npm install redux react-redux

Then, we need to create some files.

  • Add a containers folder in the src, then create Articles.js file.
import React, { useState } from "react"
import Article from "../components/Article/Article"
import AddArticle from "../components/AddArticle/AddArticle"

const Articles = () => {
  const [articles, setArticles] = useState([
    { id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
    { id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" },
  ])
  const saveArticle = e => {
    e.preventDefault()
    // the logic will be updated later
  }

  return (
    <div>
      <AddArticle saveArticle={saveArticle} />
      {articles.map(article => (
        <Article key={article.id} article={article} />
      ))}
    </div>
  )
}

export default Articles
  • Add a components folder in the src, then create AddArticle/AddArticle.js and Article/Article.js.
  • In the Article.js
import React from "react"
import "./Article.css"

const article = ({ article }) => (
  <div className="article">
    <h1>{article.title}</h1>
    <p>{article.body}</p>
  </div>
)

export default article
  • In the AddArticle.js
import React, { useState } from "react"
import "./AddArticle.css"

const AddArticle = ({ saveArticle }) => {
  const [article, setArticle] = useState()

  const handleArticleData = e => {
    setArticle({
      ...article,
      [e.target.id]: e.target.value,
    })
  }
  const addNewArticle = e => {
    e.preventDefault()
    saveArticle(article)
  }

  return (
    <form onSubmit={addNewArticle} className="add-article">
      <input
        type="text"
        id="title"
        placeholder="Title"
        onChange={handleArticleData}
      />
      <input
        type="text"
        id="body"
        placeholder="Body"
        onChange={handleArticleData}
      />
      <button>Add article</button>
    </form>
  )
}
export default AddArticle
  • In the App.js
import React from "react"
import Articles from "./containers/Articles"

function App() {
  return <Articles />
}
export default App

So, if you've done with the prerequisite, we can move on and demystify what is a state.

1. What is a state?

The heart of every react stateful component is its state. It determines how the component should render or behave. To really understand the state, we must apply it to real examples. Is user authenticated? is a state that controls if a user is authenticated or not, is modal open? is also a state which look if a given modal is open or not same as a list of articles or a counter ect.

// Class based component
state = {
  articles: [
    { id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
    { id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" },
  ],
}
// React hooks
const [articles, setArticles] = useState([
  { id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
  { id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" },
])

Now we know what is a state, it's time to introduce redux and dive deeper into it.

2. What is redux and why we need it?

Managing our state without redux or alternatives can be tough. Imagine we have to check on every component if the user is authenticated or not. To handle that use-case, we have to pass props through every component and following the application growth, it's just impossible to manage our state like that. And there is where redux really shines.

Redux is an independent library that helps us manage our state by giving access to our components the state it needs via a central store. Redux stores the whole state of our app in an immutable object tree.

Another broad term: store, to understand it well we first need to explain what is a reducer?

3. What is a reducer?

A reducer is a pure function that receives the old (previous) state and an action as arguments, then returns as output the updated state. The reducer handles only synchronous code, that's mean no side effect like HTTP request or anything like that. We can still handle asynchronous code with redux and we'll learn how to doing it later. By the way, if you get confused by the term action, no worries, it will be much clearer later. So, Let's create our very first reducer.

The structure of your files is totally up to you, however i'll follow the convention and create a store folder in the project to hold our reducers, actions etc. Then, create a reducer.js file.

  • In reducer.js
const initialState = {
  articles: [
    { id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
    { id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" },
  ],
}

const reducer = (state = initialState, action) => {
  return state
}
export default reducer

As i say earlier, a reducer is just a function that receives the previous state and an action as parameters and return the updated state. Here, we have not a previous state, so it will be undefined, therefore we need to initialize it with initialState which hold our predefined articles.

Now we've set up our reducer, it's time to create our store

4. What is a store?

A store holds the whole state tree of our react app. It's where our application state live. You can see it as a big JavaScript object. To create a store we need a reducer to pass as an argument. We already have a reducer, let's connect it to our store.

  • In our index.js file.
import React from "react"
import ReactDOM from "react-dom"
import { createStore } from "redux"

import "./index.css"
import App from "./App"
import reducer from "./store/reducer"

const store = createStore(reducer)

ReactDOM.render(<App />, document.getElementById("root"))

To create a store, we first need to import createStore from the redux package, then import our reducer and finally pass it as an argument to the store createStore(reducer). With that, we successfully create our store, but we've not done yet, we have to connect it to our react app.

5. How to connect our store to React?

To connect the store to react, we need to import a helper function named Provider from the react-redux package. Then wrap our App component with Provider and pass as props the store which has as value our current store.

  • In our index.js file.
import React from "react"
import ReactDOM from "react-dom"
import { createStore } from "redux"
import { Provider } from "react-redux"

import "./index.css"
import App from "./App"
import reducer from "./store/reducer"

const store = createStore(reducer)

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

Then, we need to connect our component to the redux store.

  • In Articles.js
import React from "react"
import { connect } from "react-redux"

import Article from "../components/Article/Article"
import AddArticle from "../components/AddArticle/AddArticle"

const Articles = ({ articles }) => {
  const saveArticle = e => {
    e.preventDefault()
    // the logic will be updated later
  }
  return (
    <div>
      <AddArticle saveArticle={saveArticle} />
      {articles.map(article => (
        <Article key={article.id} article={article} />
      ))}
    </div>
  )
}

const mapStateToProps = state => {
  return {
    articles: state.articles,
  }
}

export default connect(mapStateToProps)(Articles)

Here, we first import connect(), a function which returns a higher order function and receive as input a component. It helps us to connect our component to the store and give access to get the state.

Then, we declare a new function named mapStateToProps() (you can name it whatever you like). It's used to get our state from the redux store. The function receives as parameter the state stored in redux and returns a JavaScript object that will hold our articles.

And to reach the store, we need to pass mapStateToProps() to the connect function. It will take our component Articles and return a wrapper component with the props it injects. That's mean, we can now get our state from the store. The state is received by the component through props, we can still show the articles as before but now through redux.

We've successfully connect our store to react and get our state from it. Now, let's dive into actions

6. What is an action?

An action is a payload of information that contains a type like REMOVE_ARTICLE or ADD_ARTICLE etc. Actions are dispatched from your component. It sends data from your react component to your redux store. The action does not reach the store, it's just the messenger. The store is changed by reducer.

To create an action in our project, we need to create in our store folder a new file named actionTypes.js.

export const ADD_ARTICLE = "ADD_ARTICLE"

Then, we need to go to our Articles.js file and add this following code.

import React from "react"
import { connect } from "react-redux"

import Article from "../components/Article/Article"
import AddArticle from "../components/AddArticle/AddArticle"
import * as actionTypes from "../store/actionTypes"

const Articles = ({ articles, saveArticle }) => (
  <div>
    <AddArticle saveArticle={saveArticle} />
    {articles.map(article => (
      <Article key={article.id} article={article} />
    ))}
  </div>
)

const mapStateToProps = state => {
  return {
    articles: state.articles,
  }
}

const mapDispatchToProps = dispatch => {
  return {
    saveArticle: article =>
      dispatch({ type: actionTypes.ADD_ARTICLE, articleData: { article } }),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Articles)

Then, we need to import everything from actionTypes.js. And create a new function mapDispatchToProps which receive a function dispatch as parameter. The mapDispatchToProps return an object which has a property saveArticle. It's a reference to a function that will dispatch an action in our store.

saveArticle holds an anonymous function that receives our article as argument and return the dispatch function. It receives the type and the data to update as parameters. And as you guess, it will dispatch the action in our store.

Finally, we need to pass mapDispatchToProps as second argument to the connect function. And to make it work, we need to update our reducer and add the action ADD_ARTICLE.

  • In store/reducer.js
import * as actionTypes from "./actionTypes"

const initialState = {
  articles: [
    { id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
    { id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" },
  ],
}

const reducer = (state = initialState, action) => {
  switch (action.type) {
    case actionTypes.ADD_ARTICLE:
      const newArticle = {
        id: Math.random(), // not really unique but it's just an example
        title: action.article.title,
        body: action.article.body,
      }
      return {
        ...state,
        articles: state.articles.concat(newArticle),
      }
  }
  return state
}
export default reducer

As you can see, we import our actionTypes. Then, we check in our reducer function if the action's type is equal to ADD_ARTICLE. If it's the case, first create a new object which holds our article then append it to our articles array. Before we return the state, we copy the old state, then concat it with the new article. In that way, we keep our state safe and immutable.

7. How to handle asynchronous code with redux?

The reducer as i say earlier handles only synchronous code. To execute asynchronous code, we need to use an action creator. It's a function which returns a function or an action i should say. So, to use it in our project, we need to create a new file actionCreators.js.

  • In store/actionCreators.js
import * as actionTypes from "./actionTypes"

export const addArticle = article => {
  return {
    type: actionTypes.ADD_ARTICLE,
    article,
  }
}

Here, we declare a new action creator named addArticle. It's a function that receives the article as argument and returns the type of the action and the value. By the way, article is the same as article: article, it's just an ES6 convenient syntax. Now we can move on and change the function mapDispatchToProps in the Articles.js file.

  • In Articles.js
import React from "react"
import { connect } from "react-redux"

import Article from "../components/Article/Article"
import AddArticle from "../components/AddArticle/AddArticle"
import { addArticle } from "../store/actionCreators"

const Articles = ({ articles, saveArticle }) => (
  <div>
    <AddArticle saveArticle={saveArticle} />
    {articles.map(article => (
      <Article key={article.id} article={article} />
    ))}
  </div>
)

const mapStateToProps = state => {
  return {
    articles: state.articles,
  }
}

const mapDispatchToProps = dispatch => {
  return {
    saveArticle: article => dispatch(addArticle(article)),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Articles)

As you can see, we first import our action creator addArticle, then in the mapDispatchToProps function, we update the argument passed to dispatch. Now, it receives the action creator and its value article.

But we've not done yet, we need to add a new package redux-thunk to our project to be able to handle asynchronous code.

npm install react-redux

redux-thunk is a middleware that will help us handle asynchronous code. A middleware provides a way to interact with actions that have been dispatched to the store before they reach the reducer. Now let's implement it to our project.

  • In index.js
import React from "react"
import ReactDOM from "react-dom"
import { createStore, applyMiddleware } from "redux"
import { Provider } from "react-redux"
import thunk from "redux-thunk"

import "./index.css"
import App from "./App"
import reducer from "./store/reducer"

const store = createStore(reducer, applyMiddleware(thunk))

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

In this code block, we first import applyMiddleware from redux and thunk from redux-thunk. Then to make it work, we need to pass to createStore a second argument or enhancer which receives our middleware thunk. By doing this, we are now able to deal with asynchronous code. Let's now update our action creator.

  • In store/actionCreators.js
import * as actionTypes from "./actionTypes"

export const addArticle = article => {
  return {
    type: actionTypes.ADD_ARTICLE,
    article,
  }
}

export const simulateHttpRequest = article => {
  return dispatch => {
    setTimeout(() => {
      dispatch(addArticle(article))
    }, 3000)
  }
}

For this post, we will just simulate an HTTP request.

Here, we have a new action creator simulateHttpRequest which receive the article as input and return a function. Due to the thunk middleware, we can access to dispatch because our middleware runs between the dispatching of our action and the point of time the action reaches the reducer. Therefore, we can get dispatch as argument. Then, wait 3 seconds with setTimeout to just simulate an HTTP request before dispatching the action and add the article to our array of articles.

We've changed our action creators a little bit, to make it work again, we need to update Articles.js.

  • In Articles.js
import React from "react"
import { connect } from "react-redux"

import Article from "../components/Article/Article"
import AddArticle from "../components/AddArticle/AddArticle"
import { simulateHttpRequest } from "../store/actionCreators"

const Articles = ({ articles, saveArticle }) => (
  <div>
    <AddArticle saveArticle={saveArticle} />
    {articles.map(article => (
      <Article key={article.id} article={article} />
    ))}
  </div>
)

const mapStateToProps = state => {
  return {
    articles: state.articles,
  }
}

const mapDispatchToProps = dispatch => {
  return {
    saveArticle: article => dispatch(simulateHttpRequest(article)),
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(Articles)

Here, the only thing we have to do is change addArticle to simulateHttpRequest, in that way, everything should work again, and now we're able to handle asynchronous code through redux.

You can find the finished project here

Conclusion

When it comes to dealing with medium size to bigger react apps, managing our state can be really hard. And a package like redux can make it very easy. There are also some alternatives like the context API (+hooks) which is very helpful and does not require a third party library, but dive into redux is still relevant.

However, redux is overkill for simple react app like our project, we don't need redux to manage our state, but it's easier to understand how redux works with a very simple app.

Resources

React Redux official documentation
Redux devtools
Redux Best practices
Redux saga
The Context API