DEV Community

loading...

Redux vs Context API

Ayushman Bilas Thakur
Web development is my ❤. I love writing blog posts and experimenting with new things!
Originally published at ayushmanbthakur.com on ・6 min read

I'm sure, if you have somehow stumbled upon this post, you have some basic knowledge of React or any component-based Front-End framework. These frameworks can store data in two ways namely - component level state and app-level state. It is really easy and always preferable to have a component-level state only. But sometimes we need app-level state management. For example - if you have a TodoList in one component and count of the total number of TODOs and number of done and undone TODOs in other components then it will be a better decision to use an app-level state. Without a component level state, you will need to pass the TODOs from component to component.

In React there are mainly two ways to manage state. One is Redux. Redux can not only be used with React but also can be used with other frameworks.

On the other hand Context API is the built-in app-level state management in React.

So in this post, we are going to compare the working of both Redux and Context API and find out which one to use. Spoiler Alert, it depends on your preference.

Working with Redux

Packages needed

  • React
  • Redux : for the functions like createStore(), combineReducer()
  • React-Redux : contains the methods like useDispatch (used to dispatch an action) and useSelector (used to select things from the global state) Provider is also a part of React-redux.

Components of redux

reducer : these are functions with state and actions passed in. These work with action.type in switch cases and return the updated state it optionally needs to accept payload to work properly. Sometimes you will need to merge separate reducers before creating a store (generally in reducer folder for each reducer)

store : store is the hub of all data. It is also passed to the provider (generally created in index.js, but the combining of reducers happen in an index.js in reducer folder)

provider : a React based component which takes store as an argument (generally created in index.js)

actions : functions providing/returning payload and action type to the dispatcher which will call the required reducer. (generally created in a separate file called actions.js)

Folder Structure

Here is the folder structure I use for working with Redux. This is a simple app where a reducer is used to count the number of button taps. Disclaimer : The reducer2.js is created just for showing how to combine two reducers, you may or may not use that. So without further adieu, let's look at the folder structure along with the relevant code.

  • src/

    • actions
    • index.js [this file stores all the actions which we need to call using dispatcher] example:
export const action_a = (data) => {
    return {
        type: "ACTION_NAME",
        //generally action names are written in all caps
        payload: data
    }
}
  • reducers

    • reducer1.js. example:
const initialState = 0
export const reducer1 = (state = initialState, action) => {
    switch(action){
        case 'ACTION_NAME':
            return state + payload;
        // always return the complete updated set,
        // using spread operator will be helpful if you have
        // an object in state
        default:
            return state;
    }
}
- reducer2.js
- index.js [for combining all the reducers] example:
import { combineReduce } from "Redux";
import { reducer1 } from "./reducer1";
import { reducer2 } from "./reducer2";

export default megaReducer = combineReducer({
  reducer1,
  reducer2
});
  • App.js [React App component]

  • index.js [Main injecting component of React. We will use this to inject our combined reducer to our app, using provider, found in the React-Redux package. Here I have used Redux DevTools to debug it in the console. It is a chrome extension found here]:

import React from 'react'
import ReactDOM from 'react-dom';
import App from './App'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import megaReducer from './reducers'

const store = createStore(megaReducer,
//this is for devtools-redux, you may or may not use that
window. __REDUX_DEVTOOLS_EXTENSION__
&& window. __REDUX_DEVTOOLS_EXTENSION__ ()
);

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

Now the only thing we need is the ability to access and update the state from the global state. Let's see the steps one by one:

Accessing the state using useSelector:

useSelector() is a method provided by React-redux package to select a reducer from the combined reducer and access its values. To show how does it work let's edit the App.js

import React from 'react';
import {useSelector} from 'React-redux';

function App(){
    const count = useSelector(state => state.reducer1)

    return(
        <div>
            <h1>Number: {{count}}</h1>
        </div>
    );
}

export default App;

The useSelector function takes in a callback function which returns the required reducer from the combined reducer.

Updating the state using useDispatch:

Previously we used useSelector() to select a state from the combined reducer. Now we will see how to update the state, so we will need to modify the App.js again:

import React from 'react';
import {useSelector, useDispatch} from 'react-redux';

function App(){

    const dispatch_control = useDispatch();
    const count = useSelector(state => state.reducer1)

    return(
        <div>
            <h1>Number: {{count}}</h1>
        </div>
    );
}

export default App;

at first, I imported the useDispatch function and initialized it as dispatch_control. Now dispatch_control will contain the function returned by the useDispatch() which will finally let us dispatch an action. All that is now left is to import the action and use it using dispatch_control:

import React from 'react';
import {useSelector, useDispatch} from 'react-redux';
import {action_a} from './actions';

function App(){

    const dispatch_control = useDispatch();
    const count = useSelector(state => state.reducer1)

    return(
        <div>
            <h1>Number: {{count}}</h1>
            <button onClick={() => dispatch_control(action_a(1))} >
                +1
            </button>
        </div>
    );
}
export default App;

So here we passed the action to be dispatched imported from ./actions to the onClick event listener of the button "+1" and passed in the payload of 1 as previously we used a payload with the action definition and the reducer action.

So this was the basic overview of using Redux with React. There is still a lot to explore Redux, which I might do in another post.

Now let's jump to context API.

Working with Context API

Context API is the built-in way of React to handle global state management and it is easier than Redux

Important things

provider : This is a React component with a state and it returns JSX

context : it is created using a function called createContext()

Structure of Context.js

import React, {useState, createContext} from 'react'

export const xyzContext = createContext();

export const xyzProvider = (props) => {

    const [number, setNumber] = useState(0);

    return(
        <xyzContext.Provider value = {[number, setNumber]}>
            {props.childern}
        </xyzContext.Provider>
    )
}

So in this code, we created a new context named xyzContext. Then the state was created using React Hooks. So we are exporting two things, the context and the provider(the React component). The props.children is used to have components inside the Provider component

Now just import the Provider and wrap the App with that component. Let's use the App.js:

import React from 'react';
import { xyzProvider } from './Context'

function App(){
    return(
        <xyzProvider>
            <div>
                <h1>Number: </h1>
            </div>
        </xyzProvider>
        );
    }

export default App;

Now that we have wrapped our app with the provider we can use the context and the useContext() hook provided by React. So let's render our number:

import React from 'react';
import {useContext} from 'react';
import { xyzProvider, xyzContext } from './Context';

function App(){
    const [number, setNumber] = useContext(xyzContext);
    return(
        <xyzProvider>
            <div>
                <h1>Number: {{number}}</h1>
            </div>
        </xyzProvider>
    );
}
export default App;

Wow! now you can see the number from the global state. Now, the only thing left is to update the number. With the setNumber provided by useContext it will be really easy:

import React from 'react';
import {useContext} from 'react';
import { xyzProvider, xyzContext } from './Context';

function App(){
    const [number, setNumber] = useContext(xyzContext);
    const increaseNumber = () => {
        setNumber(prevNumber => prevNumber + 1);
    }
    return(
        <xyzProvider>
            <div>
                <h1>Number: {{number}}</h1>
                <button onClick="increaseNumber()" >
                    +1
                </button>
            </div>
        </xyzProvider>
    );
}

export default App;

So here we used an onClick event listener to fire up the increaseNumber function. In the increaseNumber function, we used the setNumber function which takes a function as an argument. In this function, we pass the previous state and return the new state. In case, if your state is an object then use the spread operator

Conclusion

According to me, the main advantage of Context API over Redux is that instead of importing actions and using them we get to manipulate the state directly on the component we are currently on. Context API is also easy to set up and is as effective as Redux. Moreover, Context API is the in-built solution, so you don't need to worry about third parties implementing new changes. So I would choose Context API to work with rather than Redux. But keep one thing in mind - Redux is the industry standard.

Discussion (15)

Collapse
mbackermann profile image
Mauricio Ackermann

Nice post. One of the best things about redux is how we can use middlewares, for example saga, to intercept the call and make async calls for APIs, for example. How would you manage the API Calls with Context API?

Collapse
ayushmanbthakur profile image
Ayushman Bilas Thakur Author

First of all, thank you for the compliment. It matters to me a lot.

Now coming to the question. Context API actually works by creating a React Component, so doing asynchronous calls will be defining another function for that and calling that when required(Obviously you will need to pass that function with the component you are returning so that you can call it from the other components).

Collapse
nymhays profile image
nymhays

Hello , Ive stumbled upon your post while deciding upon context api or redux for a next js project , and a user from reddit said a year ago, "Context is actually not great as a Redux replacement, since every context consumer does re-render whenever any value in the context changes, while in Redux only those components must be rendered for which the selected data changed." , is this still true in 2020 . And what do you think about Apollo state vs state manager (redux , mobx , redux) . I am using Apollo graphql in my Nextjs client.

Thread Thread
ayushmanbthakur profile image
Ayushman Bilas Thakur Author

For a bigger level project, I would still suggest using Redux, as it is still now the industry standard. I don't exactly have the complete knowledge of the inner working of redux and context api, especially how they handle content rendering, so I can not enlighten you about that. At the end of the day, the technology you choose depends on two things - what your app needs and how much comfortable you are with that technology.

Thread Thread
nymhays profile image
nymhays

Thanks for replying , that seems clear to me now . context-api+usereducer is my default pick currently for state manager unless I need redux dev tools , the rendering rumor is still to be investigated by me.

Collapse
gilmeir profile image
Gil Meir

Hi, nice post :)

Please refer to this line:
<xyzContext.Provider value = {[number, setNumber]}

It creates a new context value everytime the xyzProvider re-renders.
That's because of the new inline array constructed on each render.

In your case the <App/> never re-renders (no state/props change) but in other cases if <App/> gets re-rendered, it will cause the provider to re-render, hence all its consumers will re-render , even if the context did not actually change.

See Ryan Florence's post: medium.com/@ryanflorence/react-con...

Collapse
jameslong886 profile image
jameslong886 • Edited

By writing
<button onClick="increaseNumber()" > +1 </button>
I'm not sure if the author really understands React or not. Should he simply copy the code from VS Code? Worst yet, he could not fix it after a year and a half.

Collapse
rashtay profile image
Rahul Shetty

I wouldn't proceed with this approach. The app would re-render whenever the state changes unless a mechanism is added to handle the unnecessary re-rendering. Also, we would need a solution for intercepting API calls (side-effects).

Collapse
4cody profile image
Cody Shaner

As noted by a few others here, context was never created as a replacement for redux and it's implementation is such that use case scenarios are for 'low frequency changes' to state, as the entire component tree within the provider is re-rendered at every state change.

Collapse
datnq profile image
Joey

I'm wrote an module to hanlde single datastore using Context API, which also handle to avoid re-rendering.

Basically, I was trying to reduce complexity of Redux by only pass data and dispatch function to Component, so we don't need to define something like reducer or action.

You can check example below:
datnq.github.io/unidata/

Collapse
mistermalm profile image
Jonatan Malm • Edited

Hey guys, great post btw! Something that would be good to consider is if you are doing another project in let's say Vue instead of React, using Redux will still be able, when Context API is not

Cheers,

Collapse
amansaxena001 profile image
Amansaxena001

Context Api might fulfill all the requirements upto a certain level of project but for projects where your components range from 100 and above redux might be a better solution for managing them.

Collapse
doubles078 profile image
Daniel Donohue • Edited

Hey Ayushman - Nice post! I was playing around with your context example a little bit and noticed a small typo: "xyzContext.Povider" -> Provider. Thanks!

Collapse
ayushmanbthakur profile image
Ayushman Bilas Thakur Author

Thank you for pointing that out. I will edit it as soon as possible.

Collapse
sudhans72788514 profile image
Sudhanshu

Great post! just one thing, kindly fix this line in the last snippet:

onClick="increaseNumber()"

thanks!