The Startup
Published in

The Startup

Redux or React Context?

Should You Use React Context over Redux in React Application?

Redux is a library that helps to manage the state of your application.

Redux should be used in medium to large single-page applications with the complex data flow. Redux can add unnecessary complexity to your application if your application is really small with the simple data flow.

On the other hand, in v16.3 react team introduced Context which also helps to manage the state of a react application.

Why We Need State Management Tool

If we have multiple components that share the same piece of data and they don’t have any parent-child relationship then redux comes to solve the problem. In redux, we have a single piece of identical data that is in sync with all of the components that are using that piece of data.

One solution can be using event raising in Angular and using props in react. Later in a large codebase, this solution will turn into event spaghetti, and in react application we will face the problem prop drilling. From all over the codebase we will start getting events and it will be hard to keep track of all of them. So the data flow becomes unpredictable.

Facebook faced this problem back in 2014 that’s why they introduced Flux Architecture. Redux is the simplified and lightweight implementation of Flux Architecture.

The main benefits of redux

  • Decoupled architecture
  • Testability
  • Great tool support in Chrome, Firefox
  • Easy implementation of undo/redo features

When to Use Redux

In the following cases, we can use redux.

  • The same piece of data in multiple places
  • Multiple views that need to work with the same data in sync
  • Data can be changed by multiple actions/multiple users

Before we have context, redux used to solve the problem like event spaghetti and prop drilling. Redux brings a lot of complexity for simple applications. Later react team introduced the context concept for simplicity.

With context, we can pass data down our component tree without prop drilling. So in one component, we will provide some context or shared data and in another component, we will consume the data.

Now we will see how we can use react context in a maintainable and encapsulated way with typescript and react hooks. In our example, we will use the concepts of actions, reducer, and state from redux. So first we will have a look for the main concepts of redux.

Main Concept of Redux

  • Store — Single JS object that contains the state of the application.
  • Actions — Plain JS object that represents something that happened. Similar to events.
{type: ‘USER_LOGGED_IN’}
  • Reducer — A function that defines how the state changes in response to an action. It does not modify the state. It returns a new state.

In Redux, we don’t directly update the state. First we will dispatch an action. That will go to the store. The store passes the action to the root reducer. Based on the action the reducer returns a new state and the store updates internally.

Using Context in React Application

For the demonstration, the idea is to create a Header component that will be responsible for showing the Login or Logout button based on the status from the store. We will dispatch the LOGIN and LOGOUT action from the Header component.

We will do everything with 5 simple steps.
1. Create Actions
2. Create a State
3. Create Reducer
4. Create Context
5. Create Header component

1. Creating Actions

First, we have to define some of the actions that will be responsible for changing our application state. We have two actions Login and Logout.

Action.ts

export enum Action {
LOGIN = "LOGIN",
LOGOUT = "LOGOUT"
}

2. Creating State

Now we have to determine what kind of data we want to persist in our context store. So we will create an interface for that and also the initial value for the state. In our case, we will just store the username.

state.ts

export interface StateType {
username: string | null;
}
export const initialState: StateType = {
username: null
};

3. Creating Reducer

Pure Function — If we give the same input and always get the same output from a function then the function is a pure function. So there will be no side effects. It gives us easy testability and easy way of implementing undo/redo features.

Next, we will create the reducer. Reducer is a pure function. It is a function with two arguments. First is the default state which is typed StateType and second is an object with type and payload properties. type property defines the action type and payload contains optional data from the dispatcher.

Reducer.ts

import { StateType, initialState } from "./state";
import { Action } from "./Action";
export type ActionType = {
type: Action;
payload?: StateType;
};
const reducer = (state: StateType, { type, payload }: ActionType) => {switch (type) {
case Action.LOGIN:
return { ...state, ...payload };

case Action.LOGOUT:
return initialState;
default:
return state;
}
};
export default reducer;

For LOGIN action we will pass the username as a payload when we will dispatch the action. For LOGOUT we don’t need any kind of data as a payload. We will just reset the state with the initial state value.

4. Creating Context

Now we will create the context . First, we will define the ContextType and then we will create a Store. After that, we will create the StoreProvider .

Context.tsx

import React, { createContext, Dispatch, useReducer } from "react";
import reducer, { ActionType } from "./Reducer";
import { StateType, initialState } from "./state";
export interface ContextType {
state: StateType;
dispatch: Dispatch<ActionType>;
}
export const RootStore = createContext({} as ContextType);export const RootStoreProvider: React.FC = ({ children }) => {const [state, dispatch] = useReducer<React.Reducer<StateType, ActionType>>(reducer, initialState);const value = { state, dispatch };return <RootStore.Provider value={value}>{children}</RootStore.Provider>;
};

Now we will wrap our root component with this RootStoreProvider to access the store globally.

index.tsx

const App = () => {
return (
<RootStoreProvider>
....
</RootStoreProvider>
);
};

Now we are all set. We will create a simple Header component from where we will useRootStore to get the user information and also to update the information.

5. Creating the Header Component

Header/index.tsx

import React from "react";const Header = () => {
const username = null;
const toggleLoginLogoutHandler = () => {};
return (
<div
style={{
backgroundColor: "#dee5ec",
padding: 10,
display: "flex",
justifyContent: "space-between"
}}
>
<div>
<p>Header</p>
{username && <p>Logged in as {username}</p>}
</div>
<button onClick={toggleLoginLogoutHandler}>
{username ? "Logout" : "Login"}
</button>
</div>
);
};
export default Header;

Now we will use our RootStore to get the username and also dispatch the login and logout action.

import React, { useContext } from "react";
import { RootStore } from "../../Context";
import { Action } from "../../Action";
const Header = () => {
const {
state: { username },
dispatch
} = useContext(RootStore);
const toggleLoginLogoutHandler = () => {
if (username) {
dispatch({ type: Action.LOGOUT });
} else {
dispatch({ type: Action.LOGIN, payload: { username: "Ashraful" } });
}
};
.......

Finally, update the root component.

.......
import Header from "./src/components/Header";
const App = () => {
return (
<RootStoreProvider>
<Header />
</RootStoreProvider>
);
};

That’s all. We have implemented the react context with all of the concepts of redux components. Here is the link to the complete code.

I have been using react context in large scale enterprise applications immediately after it’s release. I was able to do almost everything with the context that I previously did with redux. So I will definitely recommend react context over redux. Only use redux if your application really needs it. Because redux comes with a lot of extra features and complexity that you might not want.

Should You Use React Context over Redux in React Application?

Redux is a library that helps to manage the state of your application.

Redux should be used in medium to large single-page applications with the complex data flow. Redux can add unnecessary complexity to your application if your application is really small with the simple data flow.

On the other hand, in v16.3 react team introduced Context which also helps to manage the state of a react application.

Why We Need State Management Tool

If we have multiple components that share the same piece of data and they don’t have any parent-child relationship then redux comes to solve the problem. In redux, we have a single piece of identical data that is in sync with all of the components that are using that piece of data.

One solution can be using event raising in Angular and using props in react. Later in a large codebase, this solution will turn into event spaghetti, and in react application we will face the problem prop drilling. From all over the codebase we will start getting events and it will be hard to keep track of all of them. So the data flow becomes unpredictable.

Facebook faced this problem back in 2014 that’s why they introduced Flux Architecture. Redux is the simplified and lightweight implementation of Flux Architecture.

The main benefits of redux

  • Decoupled architecture
  • Testability
  • Great tool support in Chrome, Firefox
  • Easy implementation of undo/redo features

When to Use Redux

In the following cases, we can use redux.

  • The same piece of data in multiple places
  • Multiple views that need to work with the same data in sync
  • Data can be changed by multiple actions/multiple users

Before we have context, redux used to solve the problem like event spaghetti and prop drilling. Redux brings a lot of complexity for simple applications. Later react team introduced the context concept for simplicity.

With context, we can pass data down our component tree without prop drilling. So in one component, we will provide some context or shared data and in another component, we will consume the data.

Now we will see how we can use react context in a maintainable and encapsulated way with typescript and react hooks. In our example, we will use the concepts of actions, reducer, and state from redux. So first we will have a look for the main concepts of redux.

Main Concept of Redux

  • Store — Single JS object that contains the state of the application.
  • Actions — Plain JS object that represents something that happened. Similar to events.
{type: ‘USER_LOGGED_IN’}
  • Reducer — A function that defines how the state changes in response to an action. It does not modify the state. It returns a new state.

In Redux, we don’t directly update the state. First we will dispatch an action. That will go to the store. The store passes the action to the root reducer. Based on the action the reduction returns a new state and the store updates internally.

Using Context in React Application

For the demonstration, the idea is to create a Header component that will be responsible for showing the Login or Logout button based on the status from the store. We will dispatch the LOGIN and LOGOUT action from the Header component.

We will do everything with 5 simple steps.
1. Create Actions
2. Create a State
3. Create Reducer
4. Create Context
5. Create Header component

1. Creating Actions

First, we have to define some of the actions that will be responsible for changing our application state. We have two actions Login and Logout.

Action.ts

export enum Action {
LOGIN = "LOGIN",
LOGOUT = "LOGOUT"
}

2. Creating State

Now we have to determine what kind of data we want to persist in our context store. So we will create an interface for that and also the initial value for the state. In our case, we will just store the username.

state.ts

export interface StateType {
username: string | null;
}
export const initialState: StateType = {
username: null
};

3. Creating Reducer

Pure Function — If we give the same input and always get the same output from a function then the function is a pure function. So there will be no side effects. It gives us easy testability and easy way of implementing undo/redo features.

Next, we will create the reducer. Reducer is a pure function. It is a function with two arguments. First is the default state which is typed StateType and second is an object with type and payload properties. type property defines the action type and payload contains optional data from the dispatcher.

Reducer.ts

import { StateType, initialState } from "./state";
import { Action } from "./Action";
export type ActionType = {
type: Action;
payload?: StateType;
};
const reducer = (state: StateType, { type, payload }: ActionType) => {switch (type) {
case Action.LOGIN:
return { ...state, ...payload };

case Action.LOGOUT:
return initialState;
default:
return state;
}
};
export default reducer;

For LOGIN action we will pass the username as a payload when we will dispatch the action. For LOGOUT we don’t need any kind of data as a payload. We will just reset the state with the initial state value.

4. Creating Context

Now we will create the context . First, we will define the ContextType and then we will create a Store. After that, we will create the StoreProvider .

Context.tsx

import React, { createContext, Dispatch, useReducer } from "react";
import reducer, { ActionType } from "./Reducer";
import { StateType, initialState } from "./state";
export interface ContextType {
state: StateType;
dispatch: Dispatch<ActionType>;
}
export const RootStore = createContext({} as ContextType);export const RootStoreProvider: React.FC = ({ children }) => {const [state, dispatch] = useReducer<React.Reducer<StateType, ActionType>>(reducer, initialState);const value = { state, dispatch };return <RootStore.Provider value={value}>{children}</RootStore.Provider>;
};

Now we will wrap our root component with this RootStoreProvider to access the store globally.

index.tsx

const App = () => {
return (
<RootStoreProvider>
....
</RootStoreProvider>
);
};

Now we are all set. We will create a simple Header component from where we will useRootStore to get the user information and also to update the information.

5. Creating the Header Component

Header/index.tsx

import React from "react";const Header = () => {
const username = null;
const toggleLoginLogoutHandler = () => {};
return (
<div
style={{
backgroundColor: "#dee5ec",
padding: 10,
display: "flex",
justifyContent: "space-between"
}}
>
<div>
<p>Header</p>
{username && <p>Logged in as {username}</p>}
</div>
<button onClick={toggleLoginLogoutHandler}>
{username ? "Logout" : "Login"}
</button>
</div>
);
};
export default Header;

Now we will use our RootStore to get the username and also dispatch the login and logout action.

import React, { useContext } from "react";
import { RootStore } from "../../Context";
import { Action } from "../../Action";
const Header = () => {
const {
state: { username },
dispatch
} = useContext(RootStore);
const toggleLoginLogoutHandler = () => {
if (username) {
dispatch({ type: Action.LOGOUT });
} else {
dispatch({ type: Action.LOGIN, payload: { username: "Ashraful" } });
}
};
.......

Finally, update the root component.

.......
import Header from "./src/components/Header";
const App = () => {
return (
<RootStoreProvider>
<Header />
</RootStoreProvider>
);
};

That’s all. We have implemented the react context with all of the concepts of redux components. Here is the link to the complete code.

I have been using react context in large scale enterprise applications immediately after it’s release. I was able to do almost everything with the context that I previously did with redux. So I will definitely recommend react context over redux. Only use redux if your application really needs it. Because redux comes with a lot of extra features and complexity that you might not want.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Ashraful Islam

Ashraful Islam

54 Followers

Software Engineer | Javascript Developer | React | React Native | Angular | NodeJS