Redux VS React Context: Which one should you choose?

Ibrahima Ndaw - Jan 2 '20 - - Dev Community

Originally posted on my blog



React context has been there for a while. With the coming of React hooks, it's now much better. It has so many advantages, including the fact that the context API doesn't require any third-party libraries. We can use it in React apps to manage our state like redux.

In this article, we're going to manage our state with React context, to see by ourselves if it's better than redux regarding the state's management. By the way, this post is the follow-up of my previous one.


Note: This article covers only the context API. We're going to build the same project with React context. If you're interested in how to manage state with redux, my previous post might help you here.

Otherwise let's get started.

Prerequisites

To be able to follow along, you have to know at least the basics to advance features of React and particularly React hooks. A good grasp of redux can also help.

Setting Up the Project

If you're good to go, we can now create a new React app by running:

npx create-react-app react-context-hooks-example
Enter fullscreen mode Exit fullscreen mode

Then, we have 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"

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" },
  ])

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

export default Articles
Enter fullscreen mode Exit fullscreen mode
  • 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
Enter fullscreen mode Exit fullscreen mode
  • In the AddArticle.js
import React, { useState } from "react"
import "./AddArticle.css"

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

  const handleArticleData = e => {
    setArticle({
      ...article,
      [e.target.id]: e.target.value,
    })
  }
  const addNewArticle = e => {
    e.preventDefault()
    // The logic will come later
  }

  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
Enter fullscreen mode Exit fullscreen mode
  • In the App.js
import React, { Fragment } from "react"
import Articles from "./containers/Articles"
import AddArticle from "./components/AddArticle/AddArticle";


function App() {
  return (
    <Fragment>
       <AddArticle />
       <Articles />
    </Fragment>
    )
}
export default App
Enter fullscreen mode Exit fullscreen mode

So, if you've done with all the instructions above, we can move on and start implementing the context API.

Create a context

A context helps us to handle state without passing down props on every component. Only the needed component will consume the context. To implement it, we need to create (it's optional) a new folder named context in our project and add this code below to aricleContext.js.

  • In context/aricleContext.js
import React, { createContext, useState } from "react";

export const ArticleContext = createContext();

const ArticleProvider = ({ children }) => {
  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 = article => {
    const newArticle = {
      id: Math.random(), // not really unique but it's just an example
      title: article.title,
      body: article.body
    };
    setArticles([...articles, newArticle ]);
  };
  return (
    <ArticleContext.Provider value={{ articles, saveArticle }}>
      {children}
    </ArticleContext.Provider>
  );
};

export default ArticleProvider;
Enter fullscreen mode Exit fullscreen mode

The React library gives us access to a method called createContext. We can use it to as you might guess create a context. Here, we pass nothing to our context ArticleContext, but you can pass as argument object, array, string, etc. Then we define a function that will help us distribute the data through the Provider. We give to our Provider two values: the list of articles and the method to add an article. By the way, articles: articles and saveArticle:saveArticle is the same as articles and saveArticle it's just a convenient syntax in case it confuses you.


Now we have a context, however, we need to provide the context in order to consume it. To do that, we need to wrap our higher component with ArticleProvider and App.js might be the perfect one. So, let's add it to App.js.

Provide the context

  • In App.js
import React from "react";

import ArticleProvider from "./context/articleContext";
import Articles from "./containers/Articles";
import AddArticle from "./components/AddArticle/AddArticle";

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

Enter fullscreen mode Exit fullscreen mode

As you see here, we first import our context provider ArticleProvider and wrap components that need to consume the context. Now, what about consuming the context? and how we can do that. You might be surprised how easy it is to consume the context with hooks. So, let's do that.

Consume the context

We're going to consume the context in two components: Articles.js and AddArticle.js.

  • In Articles.js
import React, { useContext } from "react";

import { ArticleContext } from "../context/articleContext";
import Article from "../components/Article/Article";

const Articles = () => {
  const { articles } = useContext(ArticleContext);
  return (
    <div>
      {articles.map(article => (
        <Article key={article.id} article={article} />
      ))}
    </div>
  );
};

export default Articles;
Enter fullscreen mode Exit fullscreen mode

With React hooks, we've now access to the useContext hook. And as you might guess, it will help us consume the context. By passing to our context ArticleContext as an argument to useContext, it gives us access to our state holden in articleContext.js. Here, we just need articles. Therefore, we pull it out and map through our articles and show them. Now, let's move on to AddArticle.js.

  • In AddArticle.js
import React, { useState, useContext } from "react";
import "./AddArticle.css";
import { ArticleContext } from "../../context/articleContext";

const AddArticle = () => {
  const { saveArticle } = useContext(ArticleContext);
  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;
Enter fullscreen mode Exit fullscreen mode

As the previous case, here again we use useContext to pull out saveArticle from our context. With that, we can now safely add a new article through the React Context.

We now manage our whole application's state through the React Context. However, we can still improve it through another hook named useReducer.

Enhance the context with useReducer

The useReducer hook is an alternative to useState. It's mostly used for the more complex state. useReducer accepts a reducer function with the initial state of our React app, and returns the current state, then dispatches a function.

This will be much more clear when we start implementing it. Now, we've to create a new file reducer.js in our context folder and add this code block below.

  • In reducer.js
export const reducer = (state, action) => {
  switch (action.type) {
    case "ADD_ARTICLE":
      return [
        ...state,
        {
          id: Math.random(), // not really unique but it's just an example
          title: action.article.title,
          body: action.article.body
        }
      ];
    default:
      return state;
  }
};

Enter fullscreen mode Exit fullscreen mode

As you can see, the function reducer receives two parameters: state and action. Then, we check if the type of action is equal to ADD_ARTICLE (you can create a constant or file to avoid mistyping) if it's the case add a new article to our state. This syntax might be familiar if you've used redux. Now, the logic to add a new article is handled by the reducer. We've not done yet, let's add it to our context file.

import React, { createContext, useReducer } from "react";
import { reducer } from "./reducer";
export const ArticleContext = createContext();

const ArticleProvider = ({ children }) => {
  const [articles, dispatch] = useReducer(reducer, [
    { id: 1, title: "post 1", body: "Quisque cursus, metus vitae pharetra" },
    { id: 2, title: "post 2", body: "Quisque cursus, metus vitae pharetra" }
  ]);

  return (
    <ArticleContext.Provider value={{ articles, dispatch }}>
      {children}
    </ArticleContext.Provider>
  );
};

export default ArticleProvider;
Enter fullscreen mode Exit fullscreen mode

Here, we start by importing the useReducer hook and our function reducer. As I mentioned earlier, useReducer takes a function. Therefore, we've to pass our reducer function to it and as second argument the initial state of our application. Now, useReducer gives us access to our articles and a dispatch function (you can name it whatever you like). And we can now, update our provider with these new values given by useReducer.

You can already see that our context file is now much cleaner. By renaming the function which adds a new article to dispatch, we've now to update a little bit our AddArticle.js file.

  • In AddArticle.js
import React, { useState, useContext } from "react";
import "./AddArticle.css";
import { ArticleContext } from "../../context/articleContext";

const AddArticle = () => {
  const { dispatch } = useContext(ArticleContext);
  const [article, setArticle] = useState();

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

  const addNewArticle = e => {
    e.preventDefault();
    dispatch({ type: "ADD_ARTICLE", 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;
Enter fullscreen mode Exit fullscreen mode

Now, instead of pulling out saveArticle, now we get the dispatch function. It expects a type of action ADD_ARTICLE and a value article which will be the new article. With that, our project is now managed through the context API and React Hooks.

Redux VS the React Context: Who wins?

You can now clearly see the difference between Redux and React Context through their implementations on our project. However, Redux is far from dead or be killed by React Context. Redux is such a boilerplate and requires a bunch of libraries. But it remains a great solution towards props drilling.

The context API with hooks is much easier to implement and will not increase your bundle size.

However, who wins? in my opinion, for low-frequency updates like locale, theme changes, user authentication, etc. the React Context is perfectly fine. But with a more complex state which has high-frequency updates, the React Context won't be a good solution. Because, the React Context will trigger a re-render on each update, and optimizing it manually can be really tough. And there, a solution like Redux is much easier to implement.

You can find the finished project here

GitHub logo ibrahima92 / react-context-hooks-example

simple project managed through React context

Find the full article here

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .