DEV Community

Nazmi
Nazmi

Posted on

Using useContext and useState hooks as a store

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)

Collapse
 
pazsea profile image
Patrick Sjöberg • Edited

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!

Collapse
 
ascotto profile image
Andrea Scotto Di Minico • Edited

@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

Collapse
 
tanuk5 profile image
Tanya G. • Edited

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.

Collapse
 
sgarcia710 profile image
Seb

Thanks for the tutorial, you made my night!

Collapse
 
vincent38wargnier profile image
vincent38wargnier

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 ?

Collapse
 
cameronapak profile image
cameronapak

This has been so helpful. Thank you!

Collapse
 
zhuyouwei profile image
Zhu Youwei

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?

Collapse
 
areskyb profile image
Aresky Berkane

Really interesting way of using context!
Thank you Nazmi!

Collapse
 
alexkamenev profile image
Alex-Kamenev

I am a bit confused where is StoreProvider exported?

Collapse
 
foufrix profile image
foufrix

Really amazed by the simplicity
Does it scale well with big store?

Is this faster than redux approach? Thanks!