November 1, 2022

React Context API With Examples

In this tutorial you will learn how to use React's Context API which is designed to share global states for a tree of Components while avoiding prop-drilling in your application.

Why React Context API

First let's go into some background of why Context API is required and what exactly is this "prop-drilling".

As we all know one of the design principles in React is to break your application into several small components which helps in better management of your application and also helps in creating reusable components.

Once you have these smaller components it is natural that you may have to move data around from one component to another. That's where props come handy to pass data from parent component to child component. At the same time this may also make things messy if you have a huge hierarchy and you have to pass data deep down, this will require keep using props to pass data down and that's what the term "prop-drilling" refers to.

Let's take a simple example where we have 3 components ComponentA, ComponentB and ComponentC and we have to pass some data from parent component (ComponentA) to child component.

ComponentA.js

From ComponentA, value for theme is passed to the child component ComponentB

import ComponentB from "./ComponentB";
const ComponentA = () => {
    return(
        <ComponentB theme="dark"></ComponentB>
    );
}    
export default ComponentA;

ComponentB.js

The same theme value has to be passed to the ComponentC so you'll again use props to pass it further down.

import ComponentC from "./ComponentC";
const ComponentB = (props) => {
    return(
     <ComponentC theme={props.theme}></ComponentC>
    );
}    
export default ComponentB;

ComponentC.js

const ComponentC = (props) => {
    return (
        <p>Current theme is- {props.theme}</p>
    );
}    
export default ComponentC;

Though it's a simple example but I hope you got the point how you have to keep moving data through props and that may become unmanageable with a huge hierarchy of Components. Here itself just think of having components let's say- ComponentA till ComponentE and you have to pass some data from ComponentA till ComponentE.

That brings us to the point of discussion how React Context API can help in this scenario.

Using React Context

Context in React helps the component to pass data deep down without explicitly passing props.

Listed below are the steps that you need to follow to use Context.

  1. Create context using React.createContext.
  2. Enclose the component (it will be the parent component where you want to pass some value to it’s descendants) with the tag <CreatedContext.Provider>. Using Provider lets you provide a context value.
    <CreatedContext.Provider value="ctxValue">
        <SomeComponent />
    </CreatedContext.Provider>
    

    React Context guarantees that this SomeComponent component and any components inside it, no matter how deep, will be able to access the passed context values.

    If the passed context values change, React will re-render the components reading the context as well.

  3. Read the context value in the Component where you need it using <CreatedContext.Consumer> (not the preferred way anymore) or by using useContext() hook.

Context API React example

1. First let's take the same example of 3 components ComponentA, ComponentB and ComponentC and we have to pass some data from parent component (ComponentA) to child component. Now we'll do this example using Context rather than using props to pass data.

1. Creating Context

First step is to create Context. Convention is to create contexts in a separate file. Since many components in different files will need access to the same context so it's common to declare contexts in a separate file.

Contexts.js

import { createContext } from 'react';

export const ThemeContext = createContext('light');

You can pass default value while creating context. If matching context provider is not present default value acts as a fallback value. If you don't have any meaningful default value, pass null while creating context.

Note that Context value can be anything an object, function handle, string. Here we are using a string value.

2. Providing context

createContext() returns a context object which in itself doesn’t hold any information. Typically, you will enclose your higher component with the <Context.Provider> specifying the context value.

App.js

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <ComponentA></ComponentA>
    </ThemeContext.Provider>
  );
};

export default App;

Now the ComponentA component and any components inside it, no matter how deep, will be able to access the passed context values.

CompoentA.js

import ComponentB from "./ComponentB";

const ComponentA = () => {
    return(
        <ComponentB />
    );
}    
export default ComponentA;

As you can see now there is no need to pass theme value using props.

ComponentB.js

import ComponentC from "./ComponentC";
const ComponentB = () => {
    return(
     <ComponentC />
    );
}    
export default ComponentB;

3. Accessing Context value.

Read the context value in the Component where you need it using <Context.Consumer> (not the preferred way anymore) or by using useContext() hook.

ComponentC.js

import { useContext } from "react";
import { ThemeContext } from "./Contexts";

const ComponentC = () => {
    const themeCtx = useContext(ThemeContext);
    return (
        <p>Current theme is- {themeCtx}</p>
    );
}    
export default ComponentC;

useContext is a React Hook that lets you read and subscribe to context from your component. useContext returns the context value for the context you passed.

Same thing if written with Context.Consumer

const ComponentC = () => {
    return (
        <ThemeContext.Consumer>
            {themeCtx => (
                 <p>Current theme is- {themeCtx}</p>
                )
            }
        </ThemeContext.Consumer>
    );
}    
export default ComponentC;

2. Here is another example where Authenticated State of a user is passed using Context. Generally you will use Context along with useState() to update context. In this example you will use useState() to change the loggedIn status of the user.

The login example used here uses the same code as shown in this post- React useEffect Hook With Examples

There is a login page and a landing page which is displayed when the user logs in. There is also a Navbar which displays a menu item and a logout link only when the user logs in. For that you need to maintain state whether user is authenticated or not and that state has to be passed as a context value. When user clicks login button or logout link there are functions that needs to be called, you also want to pass handle to those functions using context rather than using props.

Note that in this example react-bootstrap is used for styling.

First step is to create a Context.

Contexts.js

import { createContext } from 'react';
export const AuthContext = createContext({
    isUserAuthenticated: false,
    logoutAction: () => {},
    loginAction: () => {}
});

export const ThemeContext = createContext('light');

While creating AuthContext as default value an object is passed with one boolean value isUserAuthenticated (initially false) and two handles to functions logoutAction and loginAction (initially just empty functions).

Login.js

Login component where AuthContext is used by calling useContext hook. When the login form is submitted submitHandler() function is called that's where you can see this call- authCtx.loginAction(user);

Which method should actually be called when authCtx.loginAction() is invoked will be given in value with AuthContext.Provider

import Form from 'react-bootstrap/Form';
import Button from 'react-bootstrap/Button';
import { Card } from 'react-bootstrap';
import { useContext, useState } from 'react';
import { AuthContext } from './Contexts';

const Login = () => {
  const authCtx = useContext(AuthContext);
  const [user, setUser] = useState('');
  const userChangeHandler = (event) => {
    setUser(event.target.value);
  }
  const submitHandler =(event) => {
    event.preventDefault();
    authCtx.loginAction(user);
  }
  return (
    <Card className="mt-3 mx-auto" style={{ width: '25rem' }}>
      <Form onSubmit={submitHandler}  className="mx-4">
        <Form.Group className="mb-3">
        <Form.Label>User Name</Form.Label>
        <Form.Control type="text" name="name" 
            placeholder="Enter user name"  value={user}
            onChange={userChangeHandler} />
        </Form.Group>
        <Form.Group className="mb-3">
        <Form.Label>Password</Form.Label>
        <Form.Control type="password" name="password" 
            placeholder="Enter password"  />
        </Form.Group>
        <div className="text-center mb-3">
            <Button variant="primary" type='submit'>Login</Button>
        </div>
      </Form>
    </Card>
  );
}

export default Login;

TopHeader.js

Navbar component where authCtx.isUserAuthenticated is used to conditionally render menu item and logout link.

import { useContext } from "react";
import { Container, Nav, Navbar } from "react-bootstrap"
import { AuthContext } from "./Contexts";

const TopHeader = () => {
  const authCtx = useContext(AuthContext);
  return (
    <Navbar bg="dark" variant="dark" expand="lg">
      <Container>
        <Navbar.Brand href="#home">Login Page</Navbar.Brand>
        <Navbar.Toggle aria-controls="basic-navbar-nav" />
        <Navbar.Collapse id="basic-navbar-nav">
          {authCtx.isUserAuthenticated &&(
          <Nav className="me-auto">
            <Nav.Link href="#admin">Admin</Nav.Link>
          </Nav>
          )}
          {authCtx.isUserAuthenticated && (
          <Nav className="justify-content-end">
            <Nav.Item>
              <Nav.Link onClick={authCtx.logoutAction}>Logout</Nav.Link>
            </Nav.Item>
          </Nav>
          )}
        </Navbar.Collapse>
      </Container>
    </Navbar>
  );
}

export default TopHeader;

LandingPage.js

const LandingPage = (props) => {
    const user = localStorage.getItem('userName');
    return(
    <p>{user} logged in, welcome to our site</p>
    );
}
export default LandingPage;

App.js

function App() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const loginActionHandler = (user) => {
    localStorage.setItem('userLoggedIn', true);
    localStorage.setItem('userName', user);
    setIsLoggedIn(true);
  }

  const logoutActionHandler = () => {
    localStorage.removeItem('userLoggedIn');
    setIsLoggedIn(false);
  }
  useEffect(()=>{
    const loggedIn = localStorage.getItem('userLoggedIn');
    if(loggedIn){
      setIsLoggedIn(true);
    }
  }, []);
 
  return (
    <AuthContext.Provider value={{
          isUserAuthenticated: isLoggedIn, 
          logoutAction: logoutActionHandler, 
          loginAction: loginActionHandler
    }}>

      <TopHeader />
      {!isLoggedIn && <Login />}
      {isLoggedIn && <LandingPage />}
    </AuthContext.Provider>
  );
};

export default App;

isLoggedIn is used to manage login state which is updated in the functions loginActionHandler and logoutActionHandler.

const [isLoggedIn, setIsLoggedIn] = useState(false);

Components where data is needed are wrapped into a context provider to specify the value of this context for all components.

<AuthContext.Provider value={{
    isUserAuthenticated: isLoggedIn, 
    logoutAction: logoutActionHandler, 
    loginAction: loginActionHandler
}}>

As you can see isUserAuthenticated is mapped to isLoggedIn state. If the passed context values change, React will re-render the components reading the context as well.

When isLoggedIn state changes React will re-render the components because of the change in passed context value.

That's all for the topic React Context API With Examples. If something is missing or you have something to share about the topic please write a comment.


You may also like

No comments:

Post a Comment