DEV Community

Cover image for Demystifying React Hooks: useContext
Milu
Milu

Posted on • Edited on

Demystifying React Hooks: useContext

React Hooks changed the way functional components are used providing them with a simpler way to encapsulate stateful behavior and side effects in a user interface. Since some hooks are easier to understand and use than others, this series of posts will focus on demystifying the hooks that are not as straightforward.

So far, we explored useCallback, useMemo, and useRef in depth. This post will start by exploring the difference in between prop drilling and context, followed by defining a context object, explaining how to use the useContext() hook, and how to optimize its performance.

Prop drilling vs Context

Alt Text

React provides us with a data-flow where a parent component uses props to share data with its children. This way of tracking data works great for small apps, however, as your application grows you might find yourself passing a prop through multiple layers of components. This is called prop drilling.

When passing props through multiple layers, identifying where data is being initialized and when data is actually being used can become very challenging and cumbersome. In addition, refactoring your code could lead to passing unnecessary props or using multiple names for one prop (AKA bugs!).

An alternative to prop drilling is to use Context, a simple and light solution that gives us the capability of accessing data across components even when they don’t have a parent-child relationship.

What is a context object?

A context object is created using the createContext() API and its composed by two elements:

Provider: it provides the value
Consumer: it consumes the value

To create a context object, you can initialize it empty or with a value:

const testContext = createContext();
Enter fullscreen mode Exit fullscreen mode

And you can access its elements by destructuring them in this way:

const { Provider, Consumer } = testContext;
Enter fullscreen mode Exit fullscreen mode

How to use the Provider?

The Provider in your context object needs to wrap around the parent element of a component-tree. This gives every component under that component-tree access to your global data. Take a look at the <Provider> tags below, they are making the name state accessible to all the components being wrapped. Now, the components <NameModifier /> and <NamePrinter /> (and any of their children) have access to the state name even though we are not passing name as a prop.

const App = () => {
  const { Provider } = testContext;
  const [name, setTestName] = useState(Milu);

  return (
    <Provider value={{ name }}>
      <NameModifier />
      <NamePrinter />
    </Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

How to access our global data using useContext()?

The useContext() hook accepts a context object (defined above) and returns the current values made available by the Provider as static variables.

Here we have our <NamePrinter /> component (wrapped by the Provider tag in the previous section of code) and we are accessing the value of name by using our userContext() hook.

export const NamePrinter = () => {
    const { name }  = useContext(testContext);

    return <div>My name is {name}!</div>
};

Enter fullscreen mode Exit fullscreen mode

Did you notice how <NamePrinter /> is not taking any props? The name value is being accessed using the useContext() hook instead.

How do I update my context?

You can also make functions available through your Provider!

In the following example, I’ve created a function called updateName() that allows you to modify the name state. If you take a look at the <NameModifier /> component, I’m accessing the updateName() function using the useContext hook and calling it every time my input changes.

What about performance?

A component that uses useContext() will re-render when the value in the context object is updated. You could run into an instance where one of the values in your context changes very often, which could cause all your components using useContext() to re-render even though the fast-changing-value is only used in a small component-tree.

The recommended solution is to split Context. Therefore if you have Light/Dark Themes and a toggle to choose in between them that most likely won’t change too often in comparison to other values being shared by your context, you want to create a ThemeContext and AppContext as shown below.

const App = ({ user, theme, themeToggle }) => {

  return (
    <ThemeProvider value={{ theme, themeToggle }}>
      <AppContext value={{ user }}>
        <HomePage />
      </AppContext>
    </ThemeProvider>
  );
};
Enter fullscreen mode Exit fullscreen mode

Summary

The use of a context object is a great alternative to prop drilling. It allows you to access global data without passing it as props and it subscribes to it to re-render when it changes.

The context object contains two elements: Provider and Consumer.

The Provider element needs to wrap the component-tree that will have access to the global data.

The useContext() hook allows you to access the global data from any child components in the component-tree under the Provider wrapper.

To avoid unnecessary re-renders, split your context. i.e. using ThemeContext and AppContext.


I hope this explanation of useContext() was helpful and that you will be applying these new concepts in future applications!

I post new content every week. We will be exploring a different React hook next Weekend. Follow me on Twitter and Dev.to to keep up with new posts!

Top comments (10)

Collapse
 
skube profile image
skube

I think you have a typo.

const testContext = createContent();
                               ^
Collapse
 
milu_franz profile image
Milu

thank you! fixed :)

Collapse
 
omkarghate profile image
Omkar Ghate

Great article! Easy to understand, very well explained.

Collapse
 
hiral1987 profile image
Hiral Dharod

Your post on Reack Hooks are easy understand, simplify, good explaination. I am waiting for your next post on react hooks.

Collapse
 
milu_franz profile image
Milu

Thank you for the kind words Hiral! I will have a new post out this weekend.

Collapse
 
lalami profile image
Salah Eddine Lalami

@ IDURAR , we use react context api for all UI parts , and we keep our data layer inside redux .

Here Article about : 🚀 Mastering Advanced Complex React useContext with useReducer ⭐ (Redux like Style) ⭐ : dev.to/idurar/mastering-advanced-c...


Mastering Advanced Complex React useContext with useReducer (Redux like Style)

Collapse
 
amlana24 profile image
amlan

A very good explanation. I was having trouble in managing the prop values in one of my projects where I have multiple layers of components. I will try this approach. Thanks for the expalanation.

Collapse
 
milu_franz profile image
Milu

Glad this post was helpful! and thank you for reading :)

Collapse
 
frankdilo profile image
Francesco Di Lorenzo

Great summary!

Collapse
 
milu_franz profile image
Milu

Thank you Francesco!