DEV Community

Joseph Mawa
Joseph Mawa

Posted on

useContext hook and context API. What are they and how do you use them?

Content Outline

  • useContext hook
  • References
  • What is a hook?

    A hook is a special function which lets you use functional components without writing ES6 class components. To appreciate the usefulness of useContext hook, it is important to understand the problem it is trying to solve. In the sections below, we discuss how context API is used in both class and functional components.

    Prop drilling

    In React, data can be passed from a component to its descendants primarily via props. It is not a big deal to pass props from a parent component to its descendants one or two levels deep, but it becomes a mess if components are deeply nested. This problem is illustrated in the code below.

    class App extends React.Component {
      constructor(props) {
        super(props);
      }
      render() {
        return <ChildComponent name={"Joe"} />;
      }
    }
    
    class ChildComponent extends React.Component {
      constructor(props) {
        super(props);
      }
      render() {
        return <GrandChildComponent name={this.props.name} />;
      }
    }
    
    class GrandChildComponent extends React.Component {
      constructor(props) {
        super(props);
      }
      render() {
        return <GreatGrandChildComponent name={this.props.name} />;
      }
    }
    
    class GreatGrandChildComponent extends React.Component {
      constructor(props) {
        super(props);
      }
      render() {
        return <h1> Hello {this.props.name} </h1>;
      }
    }
    
    Enter fullscreen mode Exit fullscreen mode

    You can see how props have to be passed through all intermediate components from App in order to get to GreatGrandChildComponent. This, according to Kent C. Dodds, is called prop drilling. Prop drilling is undesirable if the intermediate components have no use for the data. Your goal is to have the data sent to a specific component within the component tree. context API was developed to address this problem. It helps in passing data from parent component to child components without drilling through successive descendants via props.

    How to use context API in class components

    Inorder to use context API in class components, you go through the following steps

    1. Create an instance of context using React.createContext
    2. Wrap children components of provider (component which dispatches data ) inside context.Provider
    3. Pass data via value attribute of context.Provider component
    4. Access data passed from context.Provider from any descendant of parent component via context.Consumer

    Create an instance of context

    In class components, you can use createContext to create an instance of context. createContext takes context as an argument and returns an instance of the context. Context is an object whose value property holds the data you want to pass. You can also create an instance of context without passing context(data) to createContext. In that case you will pass data via value attribute of context.Provider as explained in the subsequent sections.

    import React from "react";
    const myContext = React.createContext();
    
    Enter fullscreen mode Exit fullscreen mode

    Alternatively, you can also import createContext instead of using React.createContext() like:

    import React, { createContext } from "react";
    const myContext = createContext();
    
    Enter fullscreen mode Exit fullscreen mode

    A call to createContext returns an object which in the case above is assigned to the variable myContext. You can give a meaningful name to instance of createContext instead of calling it myContext One of the properties of myContext object is Provider whose usage is explained below.

    Wrap children of parent component (provider of data) inside context.Provider

    myContext has a Provider property which can be used inside component which provides data by wrapping siblings of the parent component in it. To illustrate this, let us go back to the previous example. If you want to pass data from App component down the component tree to any of the descendant components, then App is the provider of the data and is a parent to the components below it in the component tree. Simply wrap children of App inside context.Provider. This is illustrated in the code below.

    import React from "react";
    const myContext = React.createContext();
    class App extends React.Component {
      constructor(props) {
        super(props);
        this.state = { name: "John Doe" };
      }
      render() {
        return (
          <myContext.Provider value={{ ...this.state }}>
            <ChildComponent />
          </myContext.Provider>
        );
      }
    }
    
    Enter fullscreen mode Exit fullscreen mode

    Pass data via value attribute of context.Provider component

    myContext.Provider has an attribute called value which is used for passing data.
    In the code above, notice how React.createContext has been invoked. Its invocation returns a context object which is stored in myContext. In the render method of the component (in this case App) which provides the data to be passed down the component tree , you wrap the child component in myContext.Provider and pass the data via value attribute of myContext.Provider. This way, any component which is a descendant of App can access the data as explained below.

    Access data passed from context.Provider from any descendant of parent component (App) via context.Consumer

    In the sections above, we looked at how to create context and pass data down the component tree via context.Provider. Let us look at how descendants of App in the component tree can access the data passed. myContext, context object returned by createContext has a property called Consumer which you can use to access the data. In the code below we assume GrandChildComponent wants to use data which we passed from App.

    class GrandChildComponent extends React.Component {
      constructor(props) {
        super(props);
      }
      render() {
        return (
          <myContext.Consumer>
            {context => <h1> {context.name} </h1>}
          </myContext.Consumer>
        );
      }
    }
    
    Enter fullscreen mode Exit fullscreen mode

    Notice the child of myContext.Consumer is a function. The function takes the context (in this case myContext) as argument and returns JSX which renders the data passed. This can be done in any component which is a descendant of component from which data was passed. This helps to avoid prop drilling. Though context API solves problem of drilling components to pass data around, it introduces complexity in myContext.Consumer because a function has to be passed as its child. If you want to avoid this complexity, you can use useContext. It should be noted that useContext is a hook, therefore its use is restricted to functional components.

    useContext hook

    In the sections above, we looked at how context API is used in class components. Dispatching context in both class and functional components is the same. The difference is in the way context is consumed. To use data passed from a parent component via context, you use context.Consumer component in ES6 classes. A function is passed as a child of context.Consumer. The function takes context as an argument and returns JSX which renders the data passed via context.

    In functional components on the other hand, useContext hook takes a context object (object returned by React.CreateContext) as an argument and returns the object passed via value prop of Context.Provider. The current context value is determined by the value prop of the nearest Context.Provider in the component tree in case a component has multiple ancestors dispatching data via Context.Provider.

    It should be noted that a component which renders data passed via context API will always re-render when the context value changes. The code below is the functional component equivalent of context.Consumer.

       const myContext = React.useContext(context);
    
    
    Enter fullscreen mode Exit fullscreen mode

    You can now access the data passed as value attribute of context.Provider using myContext.


    Thanks for reading this article till the end. If you find it informative, you can share it on Twitter. Others might find it useful too. In case you notice anything technically inaccurate, leave a comment below.

    References

    Top comments (1)

    Collapse
     
    vikramvi profile image
    Vikram Ingleshwar

    Can you please do correction in " Pt. 4 Access data passed from context.Provider from any descendant of parent component via context.Consumer " ? context.Consumer in Class component and useContext in functional component