State management in react without redux

State management in react without redux is a difficult task. We generally use props to share information across components, but in large scale projects it is a big problem as components are nested. This problem is called props drilling.To avoid that issue redux was introduced. Redux gives us a lot of flexibility to manage our state. We just need to define the state of our app in a root component and all the child components have access to view or change that state.

Recently react community has decided to solve this problem using their own context API.  Context API can do  everything which was previously done by redux with zero configuration. I am going to create a simple and real world app using context API.

Note:- Context API only supports 16.3 or higher version of react. For older versions please use any third party module.

In our application we are going to create a log-in functionality. When a user will press the log in button with his credentials, we are going to save all his information in the root component’s state and then we are going to modify it from it’s child component. Although we are focusing on context API, I am not going do any validation or error checking for our log-in component. I am using create-react-app for bootstraping a boilerplate react application and tachyons for styling.

Simple React App using Context API

Step 1

npx create-reract-app context
cd context
npm start
npm i tachyons react-router-dom

Type the above in your terminal one after another.

Step 2

Create 2 new directories in src folder named ‘config’ and ‘components’. Config contains all configuration regarding our context API and components contains all our components. Create new file inside config named config.js and paste the below code.


import React, { Component } from "react";
export const Context = React.createContext({ name: "test" });
export class ConfigContext extends Component {
constructor() {
super();
this.state = {
name: "John",
isLoggedIn: false,
userInfo: {}
};
}
logIn = (data) => {
console.log(data)
this.setState({
isLoggedIn: true,
userInfo : data
});
};
logOut = () => {
this.setState({
isLoggedIn: false,
})
}
delete = () => {
this.setState({
isLoggedIn: false,
userInfo : {}
})
}
changeName = () => {
this.setState({
name : "Rakesh"
})
}
render() {
const { isLoggedIn, userInfo, name } = this.state;
return (
<Context.Provider value={{ name, isLoggedIn, userInfo,changeName : this.changeName,logIn : this.logIn,logout : this.logOut,delete : this.delete }}>
{this.props.children}
</Context.Provider>
);
}
}

 

To use context API first we have to create a context variable.Context variable as 2 main methods Provider and Consumer. Provider is the component which will expose its value to it’s child components. On the other hand all the elements inside consumer can access the value provided by Provider. Here we are passing the state and all the methods which manipulate the state as value of Provider, so that, all the child components can access or modify the change. Note that I am passing this.props.children as it’s child, that means all the child components can access the value of Provider.

Step 3

In App.js we just do normal routing. So,nothing fancy is needed here. Note that App.js is the root component. So we have to wrap it inside Provider.


import React, { Component } from "react";
import Header from "./components/Header";
import Info from "./components/Info";
import { ConfigContext as Provider } from "./config/Context";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import "tachyons";
class App extends Component {
render() {
return (
<div>
<Provider>
<Router>
<Switch>
<Route path="/" component={Header} exact={true} />
<Route path="/info" component={Info} exact={true} />
</Switch>
</Router>
</Provider>
</div>
);
}
}

export default App;

 

Step 4

Now comes the most exciting part of our project. We have to display and modify the state from our child component. Only the elements inside Consumer can access the state value. So, we have to wrap the component inside Consumer. Inside Consumer, we have to define an anonymous function with an argument and the function should return the required jsx code. Now the arguments represent state of the root component.


import React, { Component } from "react";
import { Context } from "../config/Context";
import { Redirect } from 'react-router'
export default class Header extends Component {
state = {
email : "",
password : ""
}
onChange = (e) => {
this.setState({
[e.target.name] : e.target.value
})
}
render() {
return (
<div>
<Context.Consumer>
{(data)=>(
<div>
<main className="pa4 black-80">
<div className="measure center">
<fieldset id="sign_up" className="ba b--transparent ph0 mh0">
<legend className="f4 fw6 ph0 mh0">Log In</legend>
<div className="mt3">
<label className="db fw6 lh-copy f6" htmlFor="email-address">
Email
</label>
<input
className="pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100"
type="email"
name="email"
id="email-address"
onChange={this.onChange}
value={this.state.email}
/>
</div>
<div className="mv3">
<label className="db fw6 lh-copy f6" htmlFor="password">
Password
</label>
<input
className="b pa2 input-reset ba bg-transparent hover-bg-black hover-white w-100"
type="password"
name="password"
id="password"
onChange={this.onChange}
value={this.state.password}
/>
</div>
</fieldset>
<div className="">
<button
className="b ph3 pv2 input-reset ba b--black bg-transparent grow pointer f6 dib"
onClick={()=>{
data.logIn(this.state)
}}
>
Log in
</button>
</div>
{data.isLoggedIn && <Redirect to="/info"/>}
</div>
</main>
</div>
)}
</Context.Consumer>
</div>
);
}
}

 

When we press log in button, login method of the root component is executed and we set ‘userInfo’ in root component and redirect to info page.

Step 5

Now create 2 new files inside components named Info.js and UserAction.js. Now we can display ‘userinfo’ using the same method as above. We can also manage user’s account.

Info.js

import React, { Component } from "react";
import { Context } from "../config/Context";
import UserActions from "./UserAction"
export default class Info extends Component {
render() {
return (
<div className="tc">
<div>
<Context.Consumer>
{data => (
<div>
<h1>User Info </h1>
<h5>Email : {data.userInfo.email && data.userInfo.email}</h5>
<h5>Password : {data.userInfo.password && data.userInfo.password}</h5>
</div>
)}
</Context.Consumer>
<UserActions />
</div>
</div>
);
}
}

UserAction.js

import React, { Component } from "react";
import { Context } from "../config/Context";
import { Redirect } from "react-router";
export default class UserAction extends Component {
render() {
return (
<div>
<Context.Consumer>
{data => (
<div>
<button onClick={data.logout}>Log out</button>
<button onClick={data.delete}>Delete Account</button>
{!data.isLoggedIn && <Redirect to="/" />}
</div>
)}
</Context.Consumer>
</div>
);
}
}

 

You can do anything with the context API and also be able to pull data from external server. You can manage your UI . It is stable enough to be used at production level.