State Management with Context Api, UseState and useContext hooks – React CreateContext.

Global state management in react with useState, useContext hooks and context API (React createContext).

State Management with React createContext

It’s been quite a while since react has announced a stable version of context API in its 16.3.0 release. Before that libraries like Redux, MobX were to be used for state management. In this post I will explain with some examples on how we can manage the state of the components by using context API and Hooks.

Let’s start with a very simple scenario like this:

A simple use case of context API

We Have a Basecomponent with a child named ChildComponentA which further has its own Child named ChildComponentB. Now we want to pass a const from our BaseComponent to ChildComponentB, we can do this easily by passing the const as prop from our BaseComponent to ChildComponentA and then to ChildComponentB it may seem right to you but there is a problem, what if we have 10 child components between BaseComponent and ChildComponentB. Well we must drill down the prop all the way from BaseComponent through all the child components until we reach ChildComponentB. This is obviously not the right way, to avoid this problem we use Context API.

How does Context API work?

The way Context API work is that we create a context using React.createContext() and provide it at the top of our components tree by using a <Context.provider> once we have provided the context at the top of our components tree it is accessible at every level inside that tree. We can then consume its value with <Context.consumer> or useContext hook. It may sound complicated, but you will find it simple as you will see the examples below.

Example 1 (Consume the value of context)

This example is for the above scenario with three components.

import React, { useContext } from 'react'

const FruitContext = React.createContext()

const BaseComponent = () => {
  return (
    <FruitContext.Provider value={'Apple'}>
        <ChildComponentA />
    </FruitContext.Provider>
  )
}

const ChildComponentA = () => {
  return (
    <div>
      <ChildComponentB />
    </div>
  )
}

const ChildComponentB = () => {
  const fruitName = useContext(FruitContext)

  return (
    <h1>
      {fruitName}
    </h1>
  )
}


export { BaseComponent }

Instead of using <Context.consumer> we can also use useContext hook like this.

const ChildComponentB = () => {
  const fruitName = useContext(Context)

  return (
    <h1>{fruitName}</h1>
  )
}

Example 2 (Consuming Multiple Contexts)

This example is like the previous one except for the fact that we are consuming multiple contexts instead of a single context. In our BaseComponent we have provided the value for two contexts name and age and then we have consumed those values in the ChildComponentB like we did previously.

import React, { useContext } from 'react'

const Name = React.createContext()
const Age = React.createContext()

const BaseComponent = () => {
  return (
    <Name.Provider value={'Mateen'}>
      <Age.Provider value={'20'}>
        <ChildComponentA />
      </Age.Provider>
    </Name.Provider>
  )
}

const ChildComponentA = () => {
  return (
    <div>
      <ChildComponentB />
    </div>
  )
}

const ChildComponentB = () => {
  const name = useContext(Name)
  const age = useContext(Age)

  return (
    <h1>
      I am {name} and I am {age} years old.
    </h1>
  )
}

export { BaseComponent }

Example 3 (Context API with useState hook)

You should be thinking we haven’t done any state management, well in this example we will combine Context API with useState hook to manage the state of our App. Now whenever the value of our context is updated every component that uses it will also get re rendered to sync the state of all the components. If you are not familiar with the useState hook, consider this example:

Const [state, setState] = useState(‘initial value’)

State is the variable that is been assigned the value “initial State” and setState is a callback function to update the value of our state variable. To update the value of our state variable we must call setState and whenever the value of state variable changes every component that uses it re renders. This is enough for now to understand this useState hook.

useState() hook
import React, { useContext, useState } from 'react'

const Theme = React.createContext()

const BaseComponent = () => {
  const [theme, setTheme] = useState('Light')

  const toggleTheme = () => {
    setTheme(theme === 'Light' ? 'Dark' : 'Light')
  }

  return (
    <Theme.Provider value={theme}>
      <ChildComponentA />
      <button onClick={toggleTheme}>Change Theme</button>
    </Theme.Provider>
  )
}

const ChildComponentA = () => {
  return (
    <div>
      <ChildComponentB />
    </div>
  )
}

const ChildComponentB = () => {
  const theme = useContext(Theme)

  return (
    <h1>
      current theme is {theme}.
      </h1>
    )
}

export { BaseComponent }

To know more about theming in react see this post.

Example 4 (Global state Management by passing callback functions as the context value)

This example is a little complicated as it falls in the category of global state management. One of the easiest ways to manage the state of a component globally (To update the state of a component from anywhere inside the provider) is to pass the callback function as the context value so we can call it anywhere we want to update our state variables. In this example we have a list of movies with movie name and director Name we can add more movies to the list, get the number of movies in the list and every time we add a movie to the list our component updates the UI.

Movie Provider holds the initial list and as we wrap our components with this provider, we can access and update the list of movies. Header component displays the number of movies in our list. Add Movie component adds the movie to the list using a form. Movies component displays the list of the Movies in the UI.

MovieProvider.js

import React, { useState } from 'react'

export const MovieContext = React.createContext();

export const MovieProvider = (props) => {
  const [movies, setMovies] = useState([
    {
      name: 'Wreck it Ralph',
      director: 'Rich Moore',
      id: 432
    },
    {
      name: 'The Incredibles',
      director: 'Brad Bird',
      id: 234
    },
    {
      name: 'Despicable me',
      director: 'Pierre Coffin',
      id: 542
    }
  ])

  return (
    <MovieContext.Provider value={[movies, setMovies]} >
      {props.children}
    </MovieContext.Provider>
  )

}

AddMovie.js

import React, { useContext, useState } from 'react'
import { MovieContext } from './MovieProvider'

const AddMovie = () => {
  const [movies, setMovies] = useContext(MovieContext)

  const [movieName, setMovieName] = useState('')
  const [directorName, setDirectorName] = useState('')

  const handleSubmit = event => {
    event.preventDefault()
    const rand = Math.random()
    setMovies(movies => [
      ...movies,
      { name: movieName, director: directorName }
    ])
  }

  return (
    <form onSubmit={handleSubmit} style={{ padding: '10px' }}>
      <input
        type='text'
        value={movieName}
        placeholder='Enter Movie Name'
        onChange={e => setMovieName(e.target.value)}
      />
      <input
        type='text'
        value={directorName}
        placeholder='Enter Director Name'
        onChange={e => setDirectorName(e.target.value)}
      />
      <input type='submit' value='Add Movie' />
    </form>
  )
}

export default AddMovie

Header.js

import React, { useContext } from 'react'
import { MovieContext } from './MovieProvider'

const Header = () => {

  const header = {
    paddingLeft: '15px',
    backgroundColor: 'blue',
    color: 'white',
    fontStyle: 'light',
    width: '100%',
    height: '50px',
  }

  const [movies] = useContext(MovieContext)

  return (
    <div style={header}>
      <h1>Number of movies {movies.length}</h1>
    </div>
  )
}

export default Header

Movies.js

import React, { useContext } from 'react'
import { MovieContext } from './MovieProvider'

const Movies = () => {
  const [movies] = useContext(MovieContext)

  return (
    <div>
      {movies.map(movie => (
        <div key={movie.id} style={{padding:'10px'}}>
          Movie Name:{' '}
          <span style={{ color: 'red', fontStyle: 'italic' }}>
            {movie.name}
          </span>{' '}
          | Director Name{' '}
          <span style={{ color: 'red', fontStyle: 'italic' }}>
            {movie.director}
          </span>
        </div>
      ))}
    </div>
  )
}

export default Movies

App.js

import React from 'react'
import {MovieProvider} from './components/MovieProvider'
import Header from './components/Header';
import Movies from './components/Movies'
import AddMovie from './components/AddMovie'

function App() {
  return (
    <MovieProvider>
      <Header />
      <AddMovie />
      <Movies />
    </MovieProvider>
  )
}

export default App

This is How the final output looks like:

Final output

Leave a Reply

Your email address will not be published. Required fields are marked *