loading...
Cover image for Redux VS React Context: Which one should you choose?

Redux VS React Context: Which one should you choose?

ibrahima92 profile image Ibrahima Ndaw Updated on ・8 min read

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

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
  • 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 = () => {
  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
  • 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

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;

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;

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;

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;

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;
  }
};

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;

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;

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

Posted on by:

ibrahima92 profile

Ibrahima Ndaw

@ibrahima92

JavaScript enthusiast, Full-stack developer and blogger

Discussion

markdown guide
 

Hi, I'm a Redux maintainer.

No, Context does not replace Redux, although there is some overlap. And yes, Redux will continue to be very relevant and useful for a long time. It's currently used by 50% of React apps, and lots of folks are continuing to learn it on a daily basis.

I covered this and a number of reasons to use Redux in my Reactathon 2019 talk on "The State of Redux", and my post Redux - Not Dead Yet!.

TL;DR:

  • Consistent architectural patterns
  • Debugging capabilities
  • Middleware
  • Addons and extensibility
  • Cross-platform and cross-framework usage
  • Depending on your app's setup, much better performance than working with just Context

Related to that, you might also want to read through these resources:

In addition, our new Redux Toolkit package is now our recommended approach for writing Redux logic. It includes utilities to simplify several common Redux use cases, including store setup, defining reducers, immutable update logic, and even creating entire "slices" of state at once.

 

Great feedback. Redux is far from dead or anything like that. Even if the React context + hooks can mimic his behavior in some cases, it's definitely not Redux. I will add your resources to the post. Thanks again for your comment.

 

While trying to understand the differences between Redux and Context, I stumbled upon this article. This is definitely lighter than others, but it still feels outdated - even being only a month old.
I started a new React project a couple of months ago and saw that Redux-Toolkit is already sort of a standard, and it's definitely a great tool - I always felt SO BAD when having to write all that boilerplate on Redux, I guess everyone felt that haha

However, on a quick read, I see still you mention the boilerplate on Redux and don't mention Toolkit. You should definitely update the conclusion by mentioning how Toolkit removes most of the boring boilerplate, and reinforcing that Context is great mainly for smaller tasks.

Nonetheless, congrats for the two articles comparing the tools! How about a last one with Toolkit? haha

I too am on the verge of giving up Redux because of the boilerplate stuff but you've given me new hope! I'll keep the Toolkit in mind when I'm about to discard Redux in my next project!

 

Could you elaborate on how using a reducer, a switch statement and a string-action called "ADD_ARTICLE", and returning the logic you want from that string-match -is easier than putting that code into an addArticle() function inside your context. I mean no offense to your method -I just see it all the time and I wonder what benefit it offers. Thanks

 

The reason why this approach is common is the fact that it's much cleaner. And as your react app grows, it will be easier to maintain your code. Imagine we have "ADD_ARTICLE", "REMOVE_ARTICLE", "UPDATE_ARTICLE", "ADD_ITEM", "REMOVE_ITEM" etc. you can use an if else block to check the action type, but in my opinion a switch statement is much more readable and cleaner. In that way separate your context from your reducer make sense. However, you're not restricted to follow this approach. You can do whatever you like. Thanks again for your comment.

 

This is a nice intro and overview!

At my workplace we've used Redux extensively and have now been using React Context a bit more. Both have their place, and React Context doesn't replace Redux which is probably a common misconception and isn't very clear in this article.

These days I lean towards React Context because most of my Redux experience hasn't been very fun. But it's really no fault of the library, it's really how it's been used in particular situations. Mainly, I've seen lots of people use Redux in places where it's not really needed or where it's overkill. It just unnecessarily over-complicates everything in those scenarios. But there are a few work projects where Redux has been great.

 

I totally agree with you. The reason why some folks hate redux is the fact that they even don't need it. Redux overkill and overcomplicate their simple project. Thanks again for your comment.

 

I think Redux is still alive and will be for a while, but not too much. In my opinion, the real advantage of using Redux is its architecture, it helps teams follow the same pattern. But for most cases, Context and Hooks are enough.

Great article btw.

 

You're absolutely right. Redux is far from dead even, however react context is getting better, and will be in the future a real solution for managing bigger react app. Thanks you too for reading it

 

"Context or Redux?" is too narrow question. Look at the Hookstate (hookstate.js.org/) It makes local state, lifted state, or global state equally simple and highly efficient. Moving local state to global is "+1 line" of code change.

 

Excellent article ... this sentence jumped out to me:

"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."

I had a deja vue when reading this - this is not the first time I've come across the fact that Redux (especially the "connect" API) intelligently optimizes rendering ... look at this article which says essentially the same thing:

itnext.io/how-existing-redux-patte...

This article is about using Redux with Connect versus using Redux with the Hook APIs - but I think you can make the same arguments about Redux with Connect versus Context with the Hook APIs.

So that's one thing. Second things is that I see people asserting that Context needs a lot less boiler plate than Redux, however I don't really see that ... even with Context and useReducer you're still writing reducers, actions, and so on - what's the big difference?

The way I see it, both the mental model and the way you program are virtually identical between Redux and Context. When I'm done writing reducers and actions with Context then I'm left with almost the same amount of boiler plate - so what have we gained?

(while Redux as a library is extremely lightweight, AND offers a bunch of advantages and capabilities which Context doesn't)

 
 

You've to replace const saveArticle = ({ article }) => {} with const saveArticle = ( article) => {} or destructuring the values with const saveArticle = ({ title, description }) => {} in context/index.js

 

Ahh yes, figured out too :)
github.com/navdeepsingh/react-cont...

Thanks for reverting back instantly.

 

Alright, let me check what's wrong and come back later.

 

Definitely, useContext is so much easier to implement. Avoid the hassle.

 
 

But what about memoized context to avoid reload when you use it ?

 

Hi @ibrahima92 , thank you so much for this post. I loved the idea however is a bit difficult to implement by using typescript (due to provider prop (value) type).

 

I am learning React, I will go with redux, first because of the middlewares, plugins, and easier to use than React Context.

 

BTW, React Context V/S Redux is still tabs V/S spaces, sandwhich V/S hotdog, vim V/S EMacs and Marvel V/S DC.

The cover photo should be superman V/S batman, not superman alone.

 

Interesting.
Thank you for sharing.
Personally I've used Redux for long time, but i will try context.

 

React Context is much better with hooks. Give it a try you won't be disappointed. Thanks again for reading it.

 

Thanks for sharing such detailed posts! it is always appreciated when a dev takes time to share knowledge. I'm gonna give it a go later!

 
 

Hi,

So in other word, for data that changes a lot like the articles example that you used here, it's not really a good practice to use React Context API?