React Breakdown: useContext

React Breakdown: useContext

ยท

4 min read

Are you tired of dealing with messy and hard-to-track states in React? Well, hold onto your seats because I've got the solution for you!

Let's goooo!!!! ๐Ÿš€

Some Backstory

Let's say we have a React Application that has 3 components:

  • App.js - The main file

  • ComponentA.js - A component that displays my name.

  • ComponentB.js - A component that changes my name

First off, we'd need to store my name "Prince"

In React, every mutable data is stored as states which, can be used within component logic. For example:

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

export default function App() {
  const [name, setName] = useState('Prince');
  return (
    <div>
        <ComponentA />
        <ComponentB />
    </div>
  );
}

Problem #1

What if you need to use that state in multiple components?

No problem!

Solution

Just raise the state to a higher component and pass it down as props. This is called prop drilling.

// In the App.js

<ComponentA name={name}/>
<ComponentB name={name} setName={setName}/>

Then we can access the values as props (like function arguments).

// ./ComponentA.js
import React, {useContext} from 'react';
import NameContext from './NameContext';

export default function ComponentA({name}) {
  return (
    <div style={{ border: '1px solid red', margin: '1em' }}>
      <h1>Component A</h1>
      <p>Name: {name}</p>
    </div>
  );
}

The same thing for ComponentB:

// ./ComponentB.js
import React, {useContext} from 'react';
import NameContext from './NameContext';

export default function ComponentB({name, setName}) {
  return (
    <div style={{ border: '1px solid green', margin: '1em' }}>
      <h1>Component B</h1>
      <p>Change Name</p>
      <input type="text" defaultValue={name} onInput={e => setName(e.target.value)}/>
    </div>
  );
}

We set the default value of the input element to the state "name" and use an anonymous function to update the name to the input value.

Looks Good!

Problem #2

What happens when you need to pass the state to a large number of components or even a great-grandchild of another component?

That's where things can get messy, right?

Solution

Enter useContext, the game-changer!

With useContext, you can share a piece of state between multiple elements without the headache of prop drilling. No more passing the state down component by component. The state can be accessed directly, just as if it was defined there. Say goodbye to messy and hard-to-track state and hello to streamlined and efficient state management.

Let's Code ๐Ÿš€

Let's set up a Context in the React application. First, we create a file name NameContext.js

// ./NameContext.js
import React, { createContext } from 'react';

const NameContext = createContext();

export default NameContext;

Then we use it in the App.js this way and, set the value to the state.

// ./App.js
import React, { useState } from 'react';
import ComponentA from './ComponentA';
import ComponentB from './ComponentB';
import NameContext from './NameContext';

export default function App() {
  const [name, setName] = useState('Prince');
  return (
    <div>
      <NameContext.Provider value={{ name, setName }}>
        <ComponentA />
        <ComponentB />
      </NameContext.Provider>
    </div>
  );
}

Awesome!! We that we have set up the Context.

To use it, we need the useContext hook.

// ./ComponentA.js
import React, {useContext} from 'react';
import NameContext from './NameContext';

export default function ComponentA() {
  const { name } = useContext(NameContext)
  return (
    <div style={{ border: '1px solid red', margin: '1em' }}>
      <h1>Component A</h1>
      <p>Name: {name}</p>
    </div>
  );
}

The same thing for ComponentB.

// ./ComponentB.js
import React, {useContext} from 'react';
import NameContext from './NameContext';

export default function ComponentB() {

  const {name, setName} = useContext(NameContext)
  return (
    <div style={{ border: '1px solid green', margin: '1em' }}>
      <h1>Component B</h1>
      <p>Change Name</p>
      <input type="text" defaultValue={name} onInput={e => setName(e.target.value)}/>
    </div>
  );
}

Output ๐ŸŽ‰

Bonus Trick

Here's a trick to keep your context and state together.

We create a wrapper for the Context Provider and we can add all our states and data there.

// ./NameContext.js
import React, { useState, createContext } from 'react';

const NameContext = createContext();

const ProvideName = ({ children }) => {
  const [name, setName] = useState('Prince');
  return (
    <NameContext.Provider value={{ name, setName }}>
      {children}
    </NameContext.Provider>
  );
};

export { ProvideName };
export default NameContext;

Then we use it in the App.js this way instead.

// ./App.js
import React, { useState } from 'react';
import ComponentA from './ComponentA';
import ComponentB from './ComponentB';
import { ProvideName } from './NameContext';

export default function App() {
  return (
    <div>
      <ProvideName>
        <ComponentA />
        <ComponentB />
      </ProvideName>
    </div>
  );
}

Conclusion

That's all for today guys. We learnt about the useContext hook and the Context API. Hope you liked it. Until next time ๐Ÿ™Œ