Exploring React Context API with React Hooks
By: Janitha Tennakoon
Tags: Reactjs, React Hook, Redux, React

React has come a long way since it has been initially released on 29, May 2013 and now it has been one of the top javascript libraries for front end development. Today I am going to talk about React Context API and how to use React Hooks with it. Before starting to code let us take look back at the history and talk about how state management is done in React.

What is State Management

State management refers to the management of the state of an application. This state can be changed in every component of an application. The state can be needed only by a single component or multiple components might be needed the same state.

How React Handled State Management in the Past

In the beginning, React handled this state management by passing each state from parent to its children component. Hence if a sibling component also needs the same state, the state will be moved to a common parent high up in the order.

This immediately brings up an issue where there will be unnecessary state transfer from parents to the child even though that state is not required by that child. This issue is called prop drilling where you have to go through to get data to parts of the React Component tree.

How Redux Solved Prop Drilling

Redux is a predictable state container for JavaScript apps. Redux makes it easy to manage the state of your application. Redux uses a global store to store the state of the application in one place. By using this store any component can use this store and do modifications to the state. This solves the issue with prop drilling where we do not need to pass state from parents to child. A component can access the state in the global store by using actions and reducers in the Redux library.

What is React Context API

The React Context API is a component structure provided by the React framework, which enables us to share specific forms of data across all levels of the application. This aims to solve the same problem where Redux is used to solve, prop drilling. It is available since React version 16.3. Basically Context API is React’s way of managing state in multiple components that are not directly connected.

Redux Vs Context API

This is one of the hot topics in the developer community these days, what are the pros and cons with Redux and Context API and whether Context API will replace Redux in the future. Time will let us know whether Context API will replace Redux in future React applications. But below are some advantages over Redux when using Context API.

It’s built into React and you, therefore, need no extra third-party dependencies.

It has a relatively easy learning curve compared to Redux.

Less extra code needed compared to Redux.

That’s it for the history of the React and what evolutions have happened for state management. Now let us talk about the main topic of this post, React Context API.

React Context API

As mentioned above React Context API is a component structure provided by the React framework, which enables us to share specific forms of data across all levels of the application. There are three main things related to Context API

React.createContext : This creates a context object.

: This creates a context object. Context.Provider : This is what makes the state available to all child components

: This is what makes the state available to all child components Context.Consumer: This consumes the data from the Provider without any need for prop drilling

The above mentioned three items are the main components we will be using when we implement React Context API. We will try to integrate these components with React Hooks to try achieve full functional capability of Context API.

What are React Hooks

React Hooks are a type of function that enables the execution of custom code in a base code. In React, Hooks are special functions that allow us to “hook into” its core features. React Hooks provide an alternative to writing class-based components by allowing us to easily handle state management from functional components.

Enough of talking chit chat on theory, now let’s start doing some coding with React Context API using React Hooks. Our feature will be to develop a simple application with a login form with a simple header where header values will be changed depending on a user logged in or not.

We can create a simple React application using the below command in a terminal.

npx create-react-app context-example-app

npm install react-bootstrap bootstrap

npm install react-router-bootstrap

The first command will install react modules and create project structure while the second command will install react-bootstrap styles which we are going to use as our style components. The third command installs the react-router for bootstrap. To add react-bootstrap in our application make sure to add following line to index.js file as well.

<link

rel="stylesheet"

href="https://maxcdn.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"

integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"

crossorigin="anonymous"

/>

After successfully running the above commands create two folders inside src folder named Header and Login. These two folders will contain the functions required in our header and login page respectively.

Let’s first create our login page. Add the below code to Login.js inside Login folder.

import React, { useState } from 'react'

import {FormControl, FormGroup, FormLabel, Button, } from 'react-bootstrap' import './Login.css' const Login = () => {

const [userName, setUserName] = useState('')

const [password, setPassword] = useState(''); const handleSubmit = () => {



} return (

<div className="Login">

<form onSubmit={handleSubmit}>

<FormGroup controlId="email" bsSize="large">

<FormLabel>User Name</FormLabel>

<FormControl

autoFocus

type="text"

value={userName}

onChange={e => setUserName(e.target.value)}

/>

</FormGroup>

<FormGroup controlId="password" bsSize="large">

<FormLabel>Password</FormLabel>

<FormControl

value={password}

onChange={e => setPassword(e.target.value)}

type="password"

/>

</FormGroup>

<Button block bsSize="large"

type="submit">Login</Button>

</form>

</div>

)} export default Login;

The below code describes styles for Login page. Add a new file named Login.css and add the below code.

@media all and (min-width: 480px) {

.Login {

padding: 60px 0;

} .Login form {

margin: 0 auto;

max-width: 320px;

}

}

In Login.js we use two state values for the component, user name and password and by using useState we provide methods to change these states. handleSubmit() method is currently empty because this is where we are going to use Context API later. Now let's focus on the Header component.

import React, { Fragment, useContext } from 'react'

import { LinkContainer } from 'react-router-bootstrap'

import { Navbar, Nav, NavItem } from 'react-bootstrap'; const Header = () =>{

return (

<Navbar bg="light" expand="lg">

<Navbar.Brand href="#home">React-Bootstrap</Navbar.Brand>

<Navbar.Toggle aria-controls="basic-navbar-nav" />

<Navbar.Collapse id="basic-navbar-nav">

<Nav className="mr-auto">

<Nav.Link href="#home">Home</Nav.Link>

<Nav.Link href="#link">Link</Nav.Link>

</Nav>

<Nav pullRight>

<LinkContainer to="/login">

<NavItem>Login</NavItem>

</LinkContainer>

</Nav>

</Navbar.Collapse>

</Navbar>

)

} export default Header;

Add above code to Header.js file inside Header folder. Now lets quickly add the route of login page and Header component in the app.js file.

import React from 'react';

import './App.css';

import { BrowserRouter as Router, Route } from 'react-router-dom' import Header from './Header/Header'

import Login from './Login/Login' function App() {

return (

<div >

<Router>

<Header />

<Route path='/login' component={Login} />

</Router>

</div>

);

} export default App;

Now if you go to http://localhost:3000/ you will see the below page.

Main Page

Click on Login button and you will be directed to the login page as below.

Login page

Now, let us start creating context and using it. Contexts are created using createContext function where it accepts state as an argument.

const newContext = React.createContext({ name: 'Janitha' });

This state provided will be the initial state for this context. This newContext will return an object with a Provider and a Consumer component.

const { Provider, Consumer } = newContext;

Provider will make this state available for all the components in the application while Consumer is the object which will consume this state.

<Provider value={name: 'Janitha'}>

{children}

</Provider> <Consumer>

{value => <span>{value}</span>}}

</Consumer>

This still does not be the replacement solution for Redux as we need to provide Provider and Consumer at each component level. For that, we use React Hooks, specifically useReducer hook.

The useReducer Hook

The useReducer hook allows functional components in React access to reducer functions from your state management. It is just like the reduce() method in JavaScript. The useReducer hook receives two values as its argument —the first argument is the current state and the second, an action — and then it returns a new state.

const [state, dispatch] = useReducer((state, action) => {

switch(action.action) {

case 'ACTION_TYPE':

const newState = // do state modifications

return newState;

default:

return state;

}

}, []);

In the above code block, we’ve defined our state and a corresponding method to handle that state, dispatch. When we call the dispatch method, the useReducer() Hook will perform an action based on the action.type that our method receives in its action argument.

Now as we know useReducer hook let’s creating our store and do state management using Context API. Create a folder named Store and add below code to a file named Store.js

import React, {createContext, useReducer} from 'react'; const initialState = {};

const store = createContext(initialState); const { Provider } = store; const StateProvider = ( { children } ) => {

const [state, dispatch] = useReducer((state, action) => {

switch(action.type) {

case 'ADD_USER':

return { ...state, userName : action.payload.userName}

default:

return state;

};

}, initialState);

return <Provider value={{ state, dispatch }}>{children}</Provider>;

}; export { store, StateProvider }

In our store.js file, we first create the context using createContext() method giving an initial state as {}. Then using useReducer() hook we create StateProvider where we define our actions and what will happen to our state based on that action. We have added one action named ‘ADD_USER’ which we will call when a user logged in through our login page. Next, we will export our Provider which is StateProvider to import to our application and store to use as a consumer in our other components.

Before accessing our store in other components we need to mention that our Provider is the StateProvider. We do that by wrapping around App in index.js file.

import React from 'react';

import ReactDOM from 'react-dom';

import './index.css';

import App from './App'; import * as serviceWorker from './serviceWorker';

import 'bootstrap/dist/css/bootstrap.min.css'; import {StateProvider} from './Store/Store' ReactDOM.render(<StateProvider> <App /></StateProvider>, document.getElementById('root')); serviceWorker.unregister();

Now our context which is store can be accessed from any component inside the application. Let’s first access this store in our Header component to identify whether a user logged in or not.

import { store } from '../Store/Store' const { state } = useContext(store);

First we need to import our store. Then we can access the state via using useContext which will provide us a consumer. By using that consumer we can access the state. Below is the full code for Header.js

import React, { Fragment, useContext } from 'react'

import { LinkContainer } from 'react-router-bootstrap'

import { Navbar, Nav, NavItem } from 'react-bootstrap'; import { store } from '../Store/Store' const Header = () => {

const { state } = useContext(store); const RightHeader = () => {

let userSection = (state.userName) ?

<LinkContainer to="/login"><NavItem>{state.userName}</NavItem

</LinkContainer>:

<Fragment>

<LinkContainer to="/login"><NavItem>Login</NavItem>

</LinkContainer></Fragment> let rightHeader = <Nav pullRight>{userSection} </Nav>

return (rightHeader)

} return (

<Navbar bg="light" expand="lg">

<Navbar.Brand href="#home">React-Bootstrap</Navbar.Brand

<Navbar.Toggle aria-controls="basic-navbar-nav" />

<Navbar.Collapse id="basic-navbar-nav">

<Nav className="mr-auto">

<Nav.Link href="#home">Home</Nav.Link>

<Nav.Link href="#link">Link</Nav.Link>

</Nav>

<Nav pullRight>

<LinkContainer to="/login">

<NavItem>Login</NavItem>

</LinkContainer>

</Nav>

</Navbar.Collapse>

</Navbar>

)

} export default Header;

First, we access our store using the consumer and access the state. Then depending on whether in our state we have a value for userName or not we show either login button or the user name in the header. Now next task is to assign this userName to the store on our login page. Below is the full code for our Login.js

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

import {FormControl, FormGroup, FormLabel, Button, } from 'react-bootstrap'

import './Login.css' import {store } from '../../Store/Store' const Login = () => {

const globalState = useContext(store);

const { dispatch } = globalState;



const [userName, setUserName] = useState('')

const [password, setPassword] = useState(''); const handleSubmit = (e) => {

e.preventDefault();

dispatch({type : 'ADD_USER', payload : {userName : userName}})

} return (

<div className="Login">

<form onSubmit={handleSubmit}>

<FormGroup controlId="email" bsSize="large">

<FormLabel>User Name</FormLabel>

<FormControl

autoFocus

type="text"

value={userName}

onChange={e => setUserName(e.target.value)}

/>

</FormGroup>

<FormGroup controlId="password" bsSize="large">

<FormLabel>Password</FormLabel>

<FormControl

value={password}

onChange={e => setPassword(e.target.value)}

type="password"

/>

</FormGroup>

<Button block bsSize="large"

disabled={!validateForm()}

type="submit">Login</Button>

</form>

</div>

)} export default Login;

Here inside handleSubmit() method, we call our dispatch with the action as ‘ADD_USER’ where our reducer will add userName to our global state. That’s it, now when a user sign in our header will automatically change the SignIn button to the user name.

That’s it for this post, obviously, this is not the best way to handle the login/header situation, but the main purpose of this post was to show how we can use React Context API with the use of React Hooks. I think this can be a starting point to explore more on this Context API and check whether this may replace Redux future in React applications. Thank you and see you in another post :)