Reactjs global state without redux: React state management with context api

Last updated : March 27, 2021

React js components can have access to shared data via context API. This article explains how to use the react context API to manage the application's global state without using complex libraries such as redux and prop drilling.

What is the global state in react?

Local state is sufficient when the data is shared within a single component. But when it comes to sharing data among multiple unrelated react components, we have to look for something beyond the local state. The global state is what we are looking to implement. Global component states are used when multiple components need access to a shared state.

What is prop drilling, and how can you solve it?

Prop drilling is the process of sharing data by passing through component props to get data to parts of the React component tree. Some disadvantages are:

  • Components outside the tree structure cannot receive data.
  • Some of the intermediate components involve may not need the data but still receive data as props to pass it on to the child components. Therefore, the data unnecessarily travels through several react components before reaching its consumer.

The simplest solution is to use the React context, and any component wrapped in the context has access to the data stored in the context. Redux is another solution that is more complex than React context.

React js prop drilling
Figure 1 : React js prop drilling

React js share data among components

There are several ways to share data among components with certain drawbacks and limitations.

Share data among parent and child components.

Components that are related to each other can share data by passing data as props.

Share data among unrelated components.

Sharing data by passing data in props helps when the components are related to each other and construct a tree structure. The react context is the right choice to share data among a collection of unrelated react components.

What is context in react?

React context helps to share data common for a collection of React components. For example, the react components can share the currently logged-in user, locale, user settings, etc.

React js context
Figure 2 : React js context

What do we build?

We will build a simple React js app with two pages, Posts, and Users. To demonstrate data sharing using the React context, we will only allow logged-in users to view data in Post and Users pages. Let's build the application step by step. At the end of this tutorial, you will have a working example of React data sharing using React context.

React js project structure

I am posting the complete project structure upfront, so you can have an understanding of the road map we follow.

React js context api Project structure
Figure 3 : React js context api Project structure

Main context component

First step is to create the context component to wrap all the components that want to share the global state. Note that all the components are wrapped in UserContext as the provider, obtained from React.createContext(''). The user object holds our logged-in users user name to share amongst components.

import React, { useState } from 'react';
export const UserContext = React.createContext('');
const LoginContext = ({ subPages }) => {
    const [user, setUser] = useState('');
    return (
        <UserContext.Provider value={[user, setUser]}>
            {subPages}
        </UserContext.Provider>
    )
}
export default LoginContext;

index.html

No changes are needed to index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

index.js

Since all the routing in our application is done in the App.js file, passing App.js to our context component will capture all the components routed.

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import UserContext from './LoginContext';
ReactDOM.render(
  <React.StrictMode>
    <UserContext subPages ={(<App />)} />
  </React.StrictMode>,
  document.getElementById('root')
);

App.js

Listed below is the App.js component. The App.js includes all the routes to other components. Note that implementations for Post.js, Users.js, and Login.js are listed below.

import './App.css';
import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
import Users from './Users'
import Posts from './Posts'
import Login from './Login';
const App = () => {
  return (
      <BrowserRouter>
        <React.Fragment>
          <Route path="/" component={Users} exact />
          <Route path="/posts" component={Posts} exact />
          <Route path="/login" component={Login} exact />
        </React.Fragment>
      </BrowserRouter>
  );
}
export default App;

TopMenu.js

TopMenu.js is the top navigation bar shared between Post.js and Users.js. It is solely for navigation purposes. We use import { UserContext } from './LoginContext' to access the user object that holds the currently logged-in user information. This user is in the UserContext, the wrapper for all the components. In other words, all the wrapped components now have access to the user object.

import React, { useContext } from 'react';
import { Link } from 'react-router-dom';
import {UserContext} from './LoginContext';
const TopMenu = () => {
    const [user] = useContext(UserContext);
    return (
        <div class="topnav">
            <Link to='/'>Users</Link>
            <Link to='/posts'>Posts</Link>
            <Link to='/login'>{user ? 'Welcome '+user : 'Login'}</Link>
        </div>
    );
}
export default TopMenu;

Users.js

Just like TopMenu.js, we use import { UserContext } from './LoginContext' to access the user object that holds the currently logged-in user information. Displays a message to login if the user-login is not logged in. User data is only displayed for logged in users.

import React, { useEffect, useState,useContext } from 'react';
import TopMenu from './TopMenu';
import { UserContext } from './LoginContext';
const Users = () => {
    const [userList, setUserList] = useState([]);
    const [user] = useContext(UserContext);
    useEffect(() => {
        {user && fetch('https://jsonplaceholder.typicode.com/users')
            .then(res => res.json())
            .then(data => {
                setUserList(data)
            })}
    }, []);
    return (
        <>
            <TopMenu />
            <table>
                {user ? 
                    <>
                        <tr><td colSpan='3' style={{textAlign:'center'}}><h3>Todays Users for : {user}</h3></td></tr>
                    <tr>
                        <th>Name</th>
                        <th>User Name</th>
                        <th>Email</th>
                    </tr>
                    {userList.map((userLine, index) => (
                    <tr>
                        <td>{userLine.name}</td>
                        <td>{userLine.username}</td>
                        <td>{userLine.email}</td>
                    </tr>
                        ))}
                    </>
            :
            <tr><td colSpan='3'>Please Login to view Users</td></tr>}
            </table>
        </>
    );
}
export default Users;

Posts.js

Similar to Users.js, displays a message if the user is not logged in. Post data is only displayed for logged in users.

import React, { useEffect, useState,useContext } from 'react';
import TopMenu from './TopMenu';
import {UserContext} from './LoginContext';
const Posts = () => {
    const [posts, setPosts] = useState([]);
    const [user] = useContext(UserContext);
    useEffect(() => {
        {user && fetch('https://jsonplaceholder.typicode.com/posts')
            .then(res => res.json())
            .then(data => {
                setPosts(data)
            })}
    }, []);
    return (
        <>
            <TopMenu />
            <table>
                {user ? 
                    <>
                    <tr><td colSpan='3' style={{textAlign:'center'}}><h3>Todays Posts for : {user}</h3></td></tr>
                    <tr>
                        <th>User</th>
                        <th>Title</th>
                        <th>Description</th>
                    </tr>
                    {posts.map((user, index) => (
                    <tr>
                        <td>{user.userId}</td>
                        <td>{user.title}</td>
                        <td>{user.body}</td>
                    </tr> ))}
                    </>
                : 
                    <tr><td colSpan='3'>Please Login to view Posts</td></tr>
                }
            </table>
        </>
    );
}
export default Posts;

Login.js

Login.js allows users to login to the application. This is where we set the user object. Note that this shared object doesn't have to be primitive and simple, it can be constructed with acomplex Json array that contains nested objects etc.

import React, { useContext } from 'react';
import { UserContext } from './LoginContext';
import { useHistory } from 'react-router-dom';
const Login = () => {
    const [user, setUser] = useContext(UserContext);
    const history = useHistory();
    const login = (e) => {
        e.preventDefault();
        setUser(e.target.userName.value);
        history.push("/");
    }
    return (
        <div className='logindiv'>
            <form id='commentForm' onSubmit={e => login(e)}>
                <table className='logintable'>
                    <tr><td>User Name</td><td><input type='text' name='userName'/></td></tr>
                    <tr><td>Password</td><td><input type='text'/></td></tr>
                    <tr><td colSpan='2'><button type='submit'>Submit</button></td></tr>
                </table>
            </form>
        </div>
    );
}
export default Login;

index.html

No changes to default index.html.


<html lang="en">
  <head>
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

App.css

Just for better styling. Not required.

.topnav {
  background-color: #333;
  overflow: hidden;
}
.topnav a {
  float: left;
  color: #f2f2f2;
  text-align: center;
  padding: 14px 16px;
  text-decoration: none;
  font-size: 17px;
}
.topnav a:hover {
  background-color: #4CAF50;
  color: black;
}
.topnav a.active {
  background-color: #4CAF50;
  color: white;
}
table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
}
td, th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 8px;
}
tr:nth-child(even) {
  background-color: #dddddd;
}
.logindiv{
  display: flex;
  justify-content: center;
}
.logintable{
  align-self: center;
  width: 100%;
}

package.json

No changes to the npm generated original file.

{
  "name": "reactapp",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-router-dom": "^5.2.0",
    "react-scripts": "4.0.3"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

Building and running the project

To build the project, run npm install To run the project, npm start

Type http://localhost:3000. Notice that both Post.js and Users.js do not show any data without login.

Page view without setting context
Figure 4 : Page view without setting context

Once the login is complete, Users.js and Posts.js can access login information via context API.

Login page to set the context
Figure 5 : Login page to set the context
Users page view after login
Figure 6 : Users page view after login
Posts page view after login
Figure 7 : Posts page view after login
Lance
By: Lance
Lance is a software engineer with over 15 years of experience in full-stack software development.
Read more...

Leave a comment

No Comments