DEV Community

Cover image for A Simple Guide to React Context with Hooks
Keke Arif
Keke Arif

Posted on

A Simple Guide to React Context with Hooks

It took me a while to get my head around using React Context with hooks, all the tutorials I read failed to explain it in a simple manner or seemed focused on class syntax. The class syntax is due to die soon so let me give it a try.

Why use Context?

Passing props down multiple child components can become unwieldy.

const [someState, setSomeState] = useState('cool value');

return <SomeBigDaddyComponent someState={someState} />

Now the implementation of SomeBigDaddyComponent.

const SomeBigDaddyComponent = ({ someState }) = {
  return <SomeMediumDaddyComponent someSate={someState} />
}

Now the implementation of SomeMediumDaddyComponent.

const SomeMediumDaddyComponent = ({ someState }) = {
  return <SomeSmallDaddyComponent someSate={someState} />
}

Now the implementation of SomeSmallDaddyComponent.........
It's becoming a mess right? This is about the right time to use context.

Creating and Broadcasting a Context

Context is essentially a way to broadcast data down a chain of child components without passing props. It's easier to explain by just doing it. Let's create a very simple context.

import React from 'react';

const CoolMessageContext = React.createContext('hello hello hello');

export default CoolMessageContext;

I initialized the context above with a simple string, however this can be any value and usually an object would be used. Now lets see how the context works. Consider App.js.

import React from 'react';
import CoolMessageContext from './CoolMessageContext';

const App = () => {
  return (
    <CoolMessageContext.Provider value={'bye bye bye'} />
      <SomeBigDaddyComponent />
    </CoolMessageContext.Provider>
  );
}

The components that will have access to the context's value are wrapped in Provider. This is essentially saying "Hey I'm the provider of the value, anything below me will get that value". In the example above SomeBigDaddyComponent can get the value and also its children, SomeMediumDaddyComponent and SomeSmallDaddy. Anything above the provider can not access the value.

const App = () => {
  return (
    <div>
      <SomeOtherComponent />
      <CoolMessageContext.Provider value={'bye bye bye'} />
        <SomeBigDaddyComponent />
      </CoolMessageContext.Provider>
    </div>
  );
}

SomeOtherComponent does not have access to the value because it is sitting above the provider. Note that an initial value must be provided, here I provide 'bye bye bye'. Although an initial value was set with createContext this is really just a fail-safe value in case a value isn't provided when setting the provider.

Getting the Context's Value

Now the value is being broadcast we can go ahead and access it from any of the children. To get the value we will use the hook useContext.

import React, { useContext } from 'react';
import CoolMessageContext from './CoolMessageContext';

const SomeSmallDaddyComponent = () => {
  const coolMessage = useContext(CoolMessageContext);
  return <h1>{coolMessage}</h1>
};

export default SomeSmallDaddyComponent;

The useContext hook is initialized with the context object and so that the value can be accessed without messy prop passing. You can think of useContext as providing a teleport for the value so that it can teleport down into the component that needs it.

Setting a Context's Value

A context's value can also be changed from anywhere in a similar manner if state is used.

import React from 'react';

const ObjectContext = React.createContext({
  object: {
    number: 0,
    word: 'hello'
  },
  setObject: () => {}
});

export default ObjectContext;

The above context is similar to what we used before but also has a property to store the set function for setState.

import React, { useState } from 'react';
import ObjectContext from './ObjectContext';

const App = () => {
  const [object, setObject] = useState({
    number: 1,
    word: 'bye'
  });

  return (
    // ES6 Object Property Value Shorthand 
    <ObjectContext value={{ object, setObject }} />
      <SomeBigDaddyComponent />
    </ObjectContext >
  );
};

The value above is set with the state object and also the state set function. Now setting the state from anywhere is the same as accessing the value.

import React, { useContext } from 'react';
import ObjectContext from './ObjectContext';

const SomeSmallDaddyComponent = () => {
  const { object, setObject } = useContext(ObjectContext);
  const clickHandler = () => {
    const objectCopy = {...object};
    objectCopy.title = 'wow new title!';
    setObject(objectCopy);
  }
  return <button onClick={clickHandler}>{object.title}</button>
};

export default SomeSmallDaddyComponent;

That's the basic rundown of contexts! Let me know if I missed anything or you have any questions. If you like the post give me a follow on Twitter @keke_arif . Cheers.

keke

Top comments (4)

Collapse
 
mrcrrs profile image
mrcrrs

Nice article but a mistake tripped me up; when you're creating the context wrapper with setting ability you have:

<ObjectContext value={{ object, setObject }} />

I think this should be:

<ObjectContext.Provider value={{ object, setObject }} />

Otherwise I get the "Objects are not valid as a React child context" error.

Collapse
 
jochri3 profile image
Christian Lisangola • Edited

I've defined a default value each time you use createContext but you've not used it even once because anytime your wrap your component with the Provider, you pass it a props value that override whatever value you passed as argument to the createContext function.

Here : const CoolMessageContext = React.createContext('hello hello hello');

The default value is "'hello hello hello'"

And here when you define the Provider <CoolMessageContext.Provider value={'bye bye bye'} /> it's overriden and will never be used.

That's what you've also done when combining context with useState.

You've create the context linke this :

const ObjectContext = React.createContext({
object: {
number: 0,
word: 'hello'
},
setObject: () => {}
});

But the defaultValue is never used anywhere.

It would be good to explain the practical use case of that default value.

In fact it's can be useful when you want to test a component in isolation without wrapping it inside the Provider.It's a very important point to notice because if not, people will create the context everytime with a default even when it's not needed becaus it's not mandatory as said in the official doc : reactjs.org/docs/context.html#reac...

Sorry for my English, it's not my first languageπŸ€—

Collapse
 
jimthedev profile image
Jim Cummins

Hi, thanks for this. Looks like there is a typo in one of your spreads where it goes something like: {..object} when it should be {...object}

Collapse
 
keke_arif profile image
Keke Arif

Whoopsie! Fixed. Thanks!