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 ๐