DEV Community

Cover image for useContext
gisellec60
gisellec60

Posted on

useContext

What is useContext and how do we use it.

I'll have to admit, I have a selfish reason for writing this blog on useContext...it's so I can understand how to use it because I have a feeling I'll need it for my next project.

useContext is another popular hook in react that allows data to be managed globally. It provides the ability to share data through a tree of nested components without passing it manually... a process known as prop drilling.

Let me explain...
Let's say we have a parent and 3 child components and state is set in the parent component. Inorder for child #3 to have access to a value in state set by its parent it would have to be passed down through props to child #1, and child #2 to get to child #3.

import {useState } from "react"

export default function Parent() {

const [fruit, setFruit] = useState("apple")   

return (
  <div>
  <p className="para"> 
     The Parent would love to have an {fruit}
     and give one to Child3. So the Parent will have to pass the    
     fruit to Child1fruit to Child1 who will pass it to Child2 
     who will pass it to Child3 so that Child3 can enjoy the 
     fruit.
  </p> 
     <Child1 fruit={fruit} />
 </div>    

  );
}                                                                          

function Child1 ({fruit}) {
    return (
      <div> 
        <p className="para">fruit passed to Child2  </p> 
        <Child2 fruit={fruit} />
     </div> 
   )
}

function Child2 ({fruit}) {
  return(
    <div>
       <p className="para">fruit passed to Child3  </p> 
       <Child3 fruit={fruit} />
    </div>
  ) 
}

function Child3 ({fruit}) {
  return 
   <p className="para"> Child3 can now enjoy the {fruit}! </p>
}  
Enter fullscreen mode Exit fullscreen mode

Output:
Output

Notice the Parent component passes fruit down to Child1 which inturn passes fruit down to Child2 which passes fruit down to Child3, without ever using it. This is the only way the Parent component can get fruit to the Child3 component. This process of passing data down through an heirachy is called prop drilling

This may not look like much of a problem right now, but imagine you have an application with a heirarchy of nested compoenents that runs deep and is complex. Managing such an application can become confusing and hard to maintain. React's answer to this problem is to create context.

Let's redo our example above using the useContext hook:

import {useState, useContext, createContext} from "react"

const FruitContext = createContext("")

export default function Parent() {

  const [fruit, setFruit] = useState("apple")   

  return (
     <div>
        <p className="para"> 
        The Parent would love to have an {fruit} and give one to Child3 without
        going through Child1 and Child2. 
        </p> 
        <FruitContext.Provider value={fruit}> 
           <Child1  /> 
        </FruitContext.Provider>
     </div>    

  );
}
 function Child1 () {
    return (
        <div> 
            <p className="para">Skip Child1  </p> 
            <Child2  />

        </div> 
      )
}
function Child2 () {
  return(
    <div>
       <p className="para">Skip Childl2  </p> 
       <Child3 />

    </div>
  ) 
}

function Child3 () {
  const fruit = useContext(FruitContext);
  return <p className="para"> Child3 can now enjoy the {fruit}! </p>
}  
Enter fullscreen mode Exit fullscreen mode

Output
Output

As you can see Child3 has access to fruit without it ever being passed down through the heirarchy of components. Instead of passing fruit down as a prop through the tree, Child3 was able to access fruit by the use of useContext,

A closer look...

Step1 - We create the context object. In the case above the context object is FruitContext.

const FruitContext = createContext("")

Step2 - The context object had a property called the Provider and the provider takes a value which in this case is the state variable fruit. The provider is wrapped around the component at the top of the tree so that every component in the tree from that point on has access to context object.

 export default function Parent() {
     const [fruit, setFruit] = useState("apple")
     return (
     <div>
        <p className="para"> 
         The Parent would love to have an {fruit} and give one to 
         Child3 withoutgoing through Child1 and Child2. 
        </p> 
        <FruitContext.Provider value={fruit}> 
           <Child1  /> 
        </FruitContext.Provider>
     </div>    

  );
 }
Enter fullscreen mode Exit fullscreen mode

I made the statement, The provider is wrapped around the component at the top of the tree so that every component in the tree **from that point on has access to context object.** That's because any component not in the wrapper does not have access to context provider.

Example:

export default function Parent() {
     const [fruit, setFruit] = useState("apple")
     return (
         <ChildA fruit={fruit}  />
         <FruitContext.Provider value={fruit}> 
            <p> The Parent would love to have an {fruit} </p> 
            <Child1 />
         </FruitContext.Provider>
    )
 }
Enter fullscreen mode Exit fullscreen mode

In the example above the ChildA component does not have access to FruitContext. So for ChildA and its children to have access to the state variable fruit it will have to be manually passed down through props.

Ok, moving along...

Step3 - We then use the useContext() hook to gain access to the state variable:

function Child3() {
  const fruit = useContext(FruitContext);
  return <p> fruit prop not passed to Child3 but Child3 has the {fruit} now! </p>
}
Enter fullscreen mode Exit fullscreen mode

Imagine...
You have a component tree that runs 26 components deep(ChildA-ChildZ) and during development you decide that the Parent and ChildZ need to share a state varaible. Without creating context you would have to manually pass the state variable through each component starting at ChildA down through ChildZ. That's 26 levels deep for those of us counting.

export default function Parent() {
     const [fruit, setFruit] = useState("apple")
      return (
         <p> The Parent would love to have an {fruit} </p> 
         <ChildA fruit={fruit}/>
     )  
 }

// Components ChildB({fruit})-ChildY({fruit}) 

function ChildZ({fruit}) {
   return <p>ChildZ has the {fruit} now! </p>
}
Enter fullscreen mode Exit fullscreen mode

However, by creating context you would:

  1. import the useContext and createContext hook
  2. create the context object
  3. wrap CHildA in the context provider
  4. Skip to ChildZ and give it access to fruit via useContext(ContextProvider)
import ({useContext, createContext, useState}) from "react"
const FruitContext = createContext("")
export default function Parent() {
     const [fruit, setFruit] = useState("apple")
     return (
          <FruitContext.Provider value=(fruit)>
            <ChildA/>
          <FruitContext.Provider>  
    )  
 }
// Skip Components ChildB-ChildY 
...
function ChildZ() {
  const fruit = useContext(FruitContext);
  return <p>ChildZ has the {fruit} now! </p>
}
Enter fullscreen mode Exit fullscreen mode

In the case above we can easily see how ineffficient prop drilling would be in passing fruit down a deeply nested hierarchy of components.

NOTE: Something to make note of is when a context value changes the calling component will re-render. If rendering becomes expensive there is a possible solution called memo. A component re-renders whenever it parent re-renders even if its props have not changed. Memo allows you to create a component that will not re-render when the parent re-renders as long as its props have not changed. The component in the case is said to have been "memoized". This process will help with optimization by cutting down on unneccessary rendering. Click here for more information.

Conclusion:

  • The useContext Hook solves the problem of prop drilling down a deeply nested tree of components
  • To use the useContext hook you have to first import both useContext and createContext from react.
  • createContext creates a context "Provider" that takes a value.
  • The context provider is wrapped around the top level component and every component down the tree has access to the value provided by the context provider.
  • useContext is how a child component gets access to the actual data in the context provider.
  • If rendering becomes expensive memo may be a solution.

Top comments (0)