I built a small internal company app (Stand Up Bot) that we note down our what's new, if there was anyone that needs any help and our pairing configs (we practice pair programming) for the day. In this app, I wanted to pass the notes data from the input component to the publish module that sends to our Discord channel as a post (thus the name Stand Up Bot).
The usual practice is to use a state container such as redux to manage the passing of data between components, but using Redux requires a deep understanding of reducers
and actions
that is not really necessarily if your small app simply wants to pass data without mutating it.
React JS provides us an api called createContext
which we can easily call any data from any component of your app. Usually when a value is needed to be used in a child component from a parent component, we would usually pass the data down as a prop
. Sometimes a prop
is passed down to a child component of another child component of another child component of a parent! This is what we call prop drilling.
In this post, I will share what I've learned and how i tackled my problem by using the useContext Hooks. I enjoyed using it and hope you will too!
React Context
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
I have 3 sets of data that I want to pass to the input component and store it globally so that it is easily accessible.
const teamMembersNames = ['John', 'Mary', 'Jason', 'David']
const [sharing, setSharing] = React.useState([])
const [help, setHelp] = React.useState([])
const [pairing, setPairing] = React.useState(teamMembersNames)
As per the React Official Context docs, i will need to use createContext
and nest my main component with the Context.Provider
.
<StoreContext.Provider value={store}>
<App />
</StoreContext.Provider>
Then at the component, we nest the component again with a Context.Consumer
tag.
<StoreContext.Consumer>
{store => <InputComponent store={store} />}
</StoreContext.Consumer>
React useContext Hooks
React useContext
hooks provides us an elegant way to call our data without nesting. Let's try it out!
We'll move our Context provide to its own file.
// ./src/utils/store.js
import React from 'react'
export const StoreContext = React.createContext(null)
In the same context file we will define a default function that the data are initialized and it's children will have data provided.
// ./utils/store.js
import React from 'react'
export const StoreContext = React.createContext(null)
export default ({ children }) => {
const teamMembersNames = ['John', 'Mary', 'Jason', 'David']
const [sharing, setSharing] = React.useState([])
const [help, setHelp] = React.useState([])
const [pairing, setPairing] = React.useState(teamMembersNames)
const store = {
sharing: [sharing, setSharing],
help: [help, setHelp],
pairing: [pairing, setPairing],
}
return <StoreContext.Provider value={store}>{children}</StoreContext.Provider>
}
Now our Context has setup, we can wrap our context to the main app. In the index.js
file, i will wrap the app with the Context.
// ./index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import StoreProvider from './utils/store'
ReactDOM.render(
<StoreProvider>
<App />
</StoreProvider>,
document.getElementById('root')
)
In any component, to fetch the data, we will use useContext.
import React from 'react'
import { StoreContext } from '../utils/store'
const SomeComponent = () => {
// to fetch the sharing data
const { sharing } = React.useContext(StoreContext)
}
Now our components in the app will be provided with the store data. But to fetch the data, lets use the useContext
hooks instead of Context.Consumer syntax.
I have created an input component that will get user input and set the state according to the type (sharing, help or pairing)
// ./components/input-section.js
import React from 'react'
import { StoreContext } from '../utils/store'
export default ({ type, description }) => {
const [input, setInput] = React.useState('')
const {
[type]: [data, setData],
} = React.useContext(StoreContext)
/*
.
. some other handlers
.
*/
return (
<div>
<ul>
{data.map(d => (
<li>{d}</li>
))}
</ul>
<input
placeholder={description}
type="text"
value={input}
onChange={e => setData([e, ...data])}
/>
</div>
)
}
I've shortened the component so we can see how the data were fetched. We simply call the React.useContext(StoreContext)
and the value that was passed to the provider in the store.js
are fetched exactly as it was passed. No props were passed to this component from the parents component!
To further clarify, in the parent component, I pass the type (sharing, help, pairing) that was the key to store datas.
// ./app.js
import React from 'react'
import InputSection from './components/input-section'
const App = () => {
/*
.
. some stuffs
.
*/
return (
<InputSection type="sharing" description="What are your thoughts?..." />
)
}
As you can see, I did not pass any states or data props to the child component!
Hope this helps you understand better and display how elegant using useContext
hook is! For the full app, check out my repo.
Happy Coding!
Top comments (10)
I was looking for this. How to share multiple states in context using useContext.
I thought about passing value as
{[firstState, setFirstState, secondState, setSecondState]}
This works but it was very ugly when I deconstructed it inside a child component. Your way is way more elegant, thank you!
@nazmifeeroz nice approach on how to decouple multiple stores with useState, I had a similar approach but instead used reducers in a similar way as Redux.
As you metioned @pazsea , with useState get complicated quickly.
As I was solving similar issues in the past, recently I wrote a tutorial on how to use Context and that code is decoupled with similar structure as Redux you can have a look here: frontendbyte.com/how-to-use-react-...
Of course I would be happy to know if it was helpful too.
Cheers,
Andrea
Hello, maybe something that is not very clear for newbies is the use of
value
.It will not work if you do:
return <StoreContext.Provider storeValue={store} ..
The prop should be always called
value
.Thanks for the tutorial, you made my night!
I was wondering if this store can be saved easily in local storage? isn't it going to mess up when the setMethods instances of the sates of the store will be retreived from the local storage ?
This has been so helpful. Thank you!
This design reduce a lot of boilerplate code and very intuitive. Could you point me the direction of how to do testing in this pattern?
Really interesting way of using context!
Thank you Nazmi!
I am a bit confused where is StoreProvider exported?
Really amazed by the simplicity
Does it scale well with big store?
Is this faster than redux approach? Thanks!