Using useContext and useState hooks as a global store

06.07.20194 Min Read — In React JS

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 at https://github.com/nazmifeeroz/stand-up-bot.

Happy Coding!