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.
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.
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:
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.
There are several ways to share data among components with certain drawbacks and limitations.
Components that are related to each other can share data by passing data as props.
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.
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.
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.
I am posting the complete project structure upfront, so you can have an understanding of the road map we follow.
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
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;
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>
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 LoginContext from './LoginContext';
ReactDOM.render(
<React.StrictMode>
<LoginContext subPages ={(<App />)} />
</React.StrictMode>,
document.getElementById('root')
);
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 is the top navigation bar shared between Post.js and Users.js. It is solely for navigation purposes. We use
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;
Just like TopMenu.js, we use
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;
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 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;
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>
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%;
}
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"
]
}
}
To build the project, run
Type http://localhost:3000. Notice that both Post.js and Users.js do not show any data without login.
Once the login is complete, Users.js and Posts.js can access login information via context API.