Simplifying Data Sharing in React with Context API: A Localisation example - part 21


A Complete Guide to Context API: Building a Localisation Project with React


We usually deal with the state and layer of components in typical React applications. Components are independent and reusable and accept props to communicate with each other. However, passing complex data between components can lead to confusing code and loss if you have to forward data through many components. 

React introduce context API to deal with props drilling and global state management. React context helps to access data through react application without any complexity even when we need that information by very deep nested components.

React context is also used to pass data to the entire subtree so components will change their appearance and adjust to the surrounding, Theming is a perfect example of this. 

In this article, we’ll learn how to create context in react project and consume it with useContext hook.

What is useContext hook in React?

The useContext is another essential hook that offers managing global state and addresses the complexities of passing repetitive props. It allows components to read state and functions from context.

By using useContext hook, we can manage state, pass function, and object array behind the scene and components can directly access that information at any position in React app without building a props chain.

Benefits of using the Context API over prop drilling in React

In React, data passes in a single flow, and props pass state or data in that layer of components without skipping any components to the desired place like a chain. Let's consider a localization functionality in a React project with multiple components, such as Dashboard, HeroSection, Header, and Greetings. 


A chart of showing props drilling problem

A chart showing props drilling problem within nested components

We'll explore how we can expose a hell of props drilling in this scenario and how context API is a powerful tool that Simplifying Data Sharing in React Components.

1. Data is stored in app components, and Greetings components need to access that localization data which is deeply nested inside Dashboard components.

Here in the traditional method, we'll pass props through multiple layers of components. However, by using the useContext hook, we can directly access the localization data from context, avoiding props drilling.  

2. Suppose Header component has its own state that needs to be accessed by the Dashboard component, which is the parent component. 

Instead of lifting state up and passing the state down through props, the useContext hook allows the Dashboard component to access the Header component's state directly from the shared context.

3. There are scenarios where you have to pass the localization data object throughout in React application to all components subtree.

Instead of passing props at each level, we can define a context that holds the shared data or functions and consume them in any component within the subtree. 

A chart showing how you can solve props drilling with context API in react

 A chart of showing simplifying shared data with context API

How to use the useContext hook for state management in React?

Using context API in react project it requires three steps. Let's see how context works step by step.

1. Creating context

First, you can create a separate file to create context and import the createContext function from react library, createContext returns a new context object.

import { createContext } from "react";
const ReactContext = createContext(defualtValue)

export default ReactContext 

Here, ReactContext is an object. The context doesn't hold real information instead it defines what kinds of data components can access or provide. You can specify a default value to createContext object it can be any type but most of the time it's an object. if you don't have any value you can specify it as null.

2. Providing context

Provider lets access the context value to any child components. Simply you'll wrap the components into a Provider to specify the value inside.

To specify the context value use value props on ReactContext.Provider, which holds the shared data, and any child components that are wrapped inside Provider can access that state or data and update it accordingly.

Here is an example of how we can wrap it around child components 

import ReactContext from './context/ReactContext'

function Dashboard = () => {
  const [language, setLanguage] = useState('Hello')
  <ReactContext.Provider value={{language, setLanguage}}>
    <MessageComponent />
  <ReactContext.Provider />
} 


If you want to have access to shared data to all child components in React application make sure to wrap Provider around top-root components.

3. Use useContext to consume the context

To read the context value we use useContext hook in components, it allows us to use context and listen to it, call useContext in the component function body, and pass context pointer inside useContext.

import ReactContext from './context/ReactContext'
import {useContext } from 'react'

const Dashboard = () => {
  const {language, setLanguage} = useContext(ReactContext)

  return (
    <h1>{language}</h1>
    <button onClick={() => setLanguage('Hola')}></button>
  )
}

We've accessed the state language and setLanguage variables that were defined in the Provider component.

Localization in React

I'll demonstrate Localization in a React project to make it available in multiple languages. You will learn how to utilize the context API or the useContext hook to simplify data sharing, build a global state, and access it from deeply nested components.

I've stored all translation data in the top-level App component.

const App = () => {
  const localLanguage = {
    en: {
      'Greetings': 'Hello!',
      'text': 'Welcome to our website'
    },
    it: {
      'Greetings': 'Ciao!',
      'text': 'Benvenuti sul nostro sito web'
    },
    fr: {
      'Greetings': 'Bonjour!',
      'text': 'Bienvenue sur notre site web'
    }
  }
  return (
     <div className="App"> 
       <Dashboard />
     </div>
  )
}

export default App 

Let's say I've multiple components like Dashboard, HereSection, Header, and Greetings Components and I need to access that translation data in deeply nested Greetings components.

const Dashboard = () => {
  return (
    <div>
      <HeroSection />
    </div>
  )
}

const HeroSection = () => {
  return (
    <div>
      <Header /<
    </div>
  )
}

const Header = () => {
  return (
    <div>
      <Greetings />
    </div>
  )
}

const Greetings = () => {
  return (
    <>
      <div>
	<button>English</button>
	<button>Italy</button>
	<button>French</button>
      </div>
      <h1>Greetings</h1>
      <p>text</p>
    </>
  )
}

Within the src folder create a context folder and inside this folder create the LocalizationContext.js file. Now you can create a new context using createContext function.

import { createContext } from "react";

// create a new context using createContext function
const LocalizationContext = createContext()
export default LocalizationContext 

In the App component wrap all child components with Localization.Provider and in App function body declare state and HandlerChangeLanguage function to select and update language.

function App() {
  const localLanguage = {
    en: {
      'Greetings': 'Hello!',
      'text': 'Welcome to our website'
    },
    it: {
      'Greetings': 'Ciao!',
      'text': 'Benvenuti sul nostro sito web'
    },
    fr: {
      'Greetings': 'Bonjour!',
      'text': 'Bienvenue sur notre site web'
    }
  }

  const [language, setLanguage] = useState('en')

  const handleChangeLanguage = (lang) => {
    setLanguage(lang)
  }
  return (
       <div className="App"> 
          <LocalizationContext.Provider value={{language, onChooseLang: handleChangeLanguage, localLanguage}}>
            <Dashboard />
          </LocalizationContext.Provider>
	   </div>
  )
} 

Here we set the initial state to english. Provider store language state, point onChooseLang to handleChangeLanguage function and localLanguage so it can provide to consuming components.

To access localization data in Greetings components I'll use useContext hook to consume context from LocalizationContext. Remember to import LocalizationContext inside the Greetings component at the top level.

import LocalizationContext from "../context/LocalizationContext"
import { useContext } from "react"
const Greetings = () => {
  const {language, localLanguage, onChooseLang} = useContext(LocalizationContext)
  
  return (
    <>
      <div className="lang-btn">
        <button onClick={() => onChooseLang('en')}>English</button>
        <button onClick={() => onChooseLang('it')}>Italy</button>
        <button onClick={() => onChooseLang('fr')}>French</button>
      </div>
      <h1>{localLanguage[language].Greetings}</h1>
      <p>{localLanguage[language].text}</p>
    </>
  )
}

export default Greetings

In the above code, we pulled out language, localLanugate, and onChooseLang from context.

This simple localization system shows how you can save the data in the App component and render it accordingly based on the selected language button to demonstrate the concept of props drilling.

Create a Provider component to manage Global State

We may create Provider components to manage a global state when we need to make sure that every component in a subtree of our React application has access to data or state. This is a best practice to handle global state.

In the same LocalizationContext file that we created earlier, create a component called LocalizationProvider pass children as an argument, this component will serve as a provider for localization context.

const LoclizationProvider = ({children}) => {}

Return the LocalizationContext.Provider and pass children between provider components so that shared data or state is accessible to all components in the subtree. We'll now export this as named export.

export const LocalizationProvider = ({children}) => {
  return <LocalizationContext.Provider>
    { children }
  </LocalizationContext.Provider>
 )
} 

Now we can manage state, handler function, and translation data object in this exact file instead of App component, set the value props, and make sure to pass all data or state you want to share.

LocalizationContext.js

import { createContext, useState } from "react";

const LocalizationContext = createContext(null)

export const LocalizationProvider = ({children}) => {
  const [language, setLanguage] = useState('en')

  const handleChooseLanguage = (lang) => {
    setLanguage(lang)
  }
  const localLanguage = {
    en: {
      'Greetings': 'Hello!',
      'text': 'Welcome to our website'
    },
    it: {
      'Greetings': 'Ciao!',
      'text': 'Benvenuti sul nostro sito web'
    },
    fr: {
      'Greetings': 'Bonjour!',
      'text': 'Bienvenue sur notre site web'
    }
  }
  return <LocalizationContext.Provider value={{
    language,
    localLanguage,
    onChooseLang : handleChooseLanguage
  }}>
    {children}
  </LocalizationContext.Provider>
}

export default LocalizationContext 

We now have a whole dedicated file that controls localization in the LocalizationProvider component and contains context data.

Console

React introduced the context API to simplify sharing information and state management. It eliminates the traditional method of manually sending props to each component, even when parent components do not require it and are just passing to one another.

In this article, we looked at creating contexts that store shared data and let components access that data even if they are at the bottom of the component tree. We also looked at using useContext to consume contexts and update them in components.

I hope you found this content helpful. It's okay if you don't grasp an idea in one flow; it will come with practice. I recommend that you practice Context API with real-world examples such as authentication or theming and share what you learned with me in the box below.

0 Comments:

Post a Comment