Mastering React Context API for Streamlined Data Sharing

Relia Software

Relia Software

Huy Nguyen

Relia Software

featured

The React Context API is a feature provided by React that enables you to pass data through the component tree without having to pass props manually at every level.

Mastering React Context API for Streamlined Data Sharing

Table of Contents

Welcome to the world of React Context API! This handy feature lets you share data across components without the dreaded prop drilling. We'll explore how Context API works, its practical uses, and some best practices to keep your code clean and efficient. So, buckle up and get ready to streamline your React applications!

>> Read more about React:

Understanding React Context API

The React Context API is a feature provided by React that enables you to pass data through the component tree without having to pass props manually at every level. This makes it easier to share data between components, especially for data that needs to be accessible by many components at different nesting levels.

The Context API works by allowing you to create a context object, and this object can be given a default value that all components in the component tree can access if they are a consumer of this context. Here’s a basic example of how it works:

import React, { createContext, useContext } from 'react';

// Create a Context object
const MyContext = createContext(defaultValue);

function MyComponent() {
  // Use the Context object
  const contextValue = useContext(MyContext);
  // Now you can use the value in your component
}

As for state management, while the Context API does help with passing data around, it’s not a complete solution for state management on its own. State management involves more than just passing data around - it also includes how you organize your state, how you update it, and how you handle side effects.

The React Context API provides a mechanism for a React application to create global variables that can be shared and accessed throughout the app. It serves as an alternative to “prop drilling”, which involves passing props from a grandparent to a parent to a child, and so forth. Additionally, Context is often promoted as a simpler, more lightweight solution for state management compared to using Redux.

Installation and Setup

Here’s how you can install and set up a project using the React Context API with npx and yarn:

Step 1: Create A New React Project

  • If you’re using npx, run the following command in your terminal:
npx create-react-app my-app
  • If you’re using yarn, use this command instead:
yarn create react-app my-app

Step 2: Navigate to Your New Project Directory

cd my-app

Step 3: Create A New Context

You can create a new Context using the createContext method from React. Here’s an example of how to do it:

import React from 'react';

const MyContext = React.createContext();

Step 4: Use the Context Provider

Wrap the Context Provider around your component tree and put any value you like on your context provider using the value prop.

Step 5: Use the Context Consumer

You can read that value within any component by using the Context Consumer. Here’s an example of how to do it using the useContext hook:

import { useContext } from 'react';
import { MyContext } from './MyContext';

function MyComponent() {
const { text, setText } = useContext(MyContext);

return (
  <div>
    <h1>{text}</h1>
    <button onClick={() => setText('Hello, world!')}>Click me</button>
  </div>
);
}

That’s it! You’ve now set up a project using the React Context API with npm or yarn. This will allow you to manage state more effectively in your React applications, especially when dealing with nested components.

Basic Usage

This repo explain how to use react-context-api in react, you can find it in here

First, clone the project:

git clone https://github.com/nvkhuy/examples.git

Navigate to react-context-api example:

cd react-context-api

To install:

yarn install

To run:

yarn start

Project structure:

├── README.md
├── package-lock.json
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
└── src
    ├── App.css
    ├── App.js
    ├── Button.js
    ├── index.js
    └── store
        └── ThemeContext.js

Focus on ThemeContext.js file.

To create a context and import necessary files:

import React, { createContext, useState } from 'react'

export const ThemeContext = createContext()

Create custom component that wraps it children:

export const ThemeProvider = ({ children }) => {
    const [isDarkTheme, setIsDarkTheme] = useState(false)

    const toggleTheme = () => setIsDarkTheme(prevTheme => !prevTheme)

    return (
        <ThemeContext.Provider value={{ isDarkTheme, toggleTheme }}>
            {children}
        </ThemeContext.Provider>
    )
}
  • Custom Component: The ThemeProvider is a custom component created by the developer. It accepts a single prop called children, which represents the child components that will be wrapped by the context provider.
  • State Management: Inside the ThemeProvider, there is a state variable called isDarkTheme initialized with false. This state variable represents whether the dark theme is currently active.
  • Toggle Function: The toggleTheme function is defined to toggle the value of isDarkTheme. It uses the setIsDarkTheme function from React’s useState hook. When called, it flips the value of isDarkTheme (from true to false or vice versa).
  • Context Provider: The ThemeProvider component wraps its children with a context provider. Specifically, it uses the ThemeContext.Provider provided by the React Context API. The value prop of this provider is an object containing the isDarkTheme state and the toggleTheme function.
  • Usage: When you use the ThemeProvider in your app, any child component that consumes the ThemeContext will have access to the isDarkTheme state and toggleTheme function. This allows you to manage theme-related logic across different parts of your application.

The ThemeProvider  wraps its children with this context provider, making the context values available to all descendants

In index.js , ThemProvider wrapping App

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { ThemeProvider } from './store/ThemeContext';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <ThemeProvider>
     <App />
    </ThemeProvider>
  </React.StrictMode>
);

In App.js for switching theme:

import {useContext} from 'react';
import './App.css';
import Button from './Button';
import {ThemeContext} from './store/ThemeContext';

function App() {
    const {isDarkTheme} = useContext(ThemeContext)
    return (
        <div className={`App ${isDarkTheme ? 'darkTheme' : 'lightTheme'}`}>
            <h1>Theme Context API</h1>
            <p>Them Context API example</p>
            <Button/>
        </div>
    );
}

export default App;
  • useContext hook is used to access the context value from the ThemeContext.
  • isDarkTheme variable is extracted from the context, which presumably holds information about the current theme (dark or light), controls the theme (dark or light) applied to the app based on the context provided by the ThemeContext.

The Button component is defined in Button.js , inside the component, it uses useContext hook to access the context data provided by ThemeContext.

import React, { useContext } from 'react'
import { ThemeContext } from './store/ThemeContext'
import './App.css'

const Button = () => {
  const { isDarkTheme, toggleTheme } = useContext(ThemeContext)
  return (
    <button onClick={toggleTheme} className={`${isDarkTheme ? 'lightBtn' : 'darkBtn'}`}>
      change Theme
    </button>
  )
}
export default Button
  • The useContext(ThemeContext) hook is used to retrieve the current context value, which includes properties like isDarkTheme and toggleTheme.

The button element is rendered with the following attributes:

  • onClick={toggleTheme}: When the button is clicked, the toggleTheme function will be executed.
  • className={${isDarkTheme ? ‘lightBtn’ : ‘darkBtn’}}: The button’s class name is conditionally set based on the isDarkTheme value. If isDarkTheme is true, it uses the ‘lightBtn’ class; otherwise, it uses the ‘darkBtn’ class.

Light and Dark theme css, defined in App.css

* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}
.App {
  padding: 50px;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  gap: 20px;
  height: 100vh;
}

.darkTheme {
  background-color: black;
  color: aliceblue;
}

.lightTheme {
  background-color: aliceblue;
  color: black;
}

button {
  padding: 10px;
  border-radius: 10px;
  border: 1px solid;
  cursor: pointer;
  font-size: 16px;
}

.darkBtn {
  background-color: black;
  color: aliceblue;
}
.lightBtn {
  background-color: aliceblue;
  color: black;
}

Result would be like this when you click button Change Theme

  • Light Theme

Light Theme

  • Dark Theme

Dark Theme

Clone the repo and run it on your local to understand this example better.

Limitations of React Context API

The React Context API is a powerful tool for managing state and sharing data across components. However, it does have some limitations compared to other React state management libraries. Let’s explore these limitations with code examples:

Performance and Re-renders

Context updates can cause unnecessary re-renders of components that consume the context. Specifically, a state change high up in the context tree may require a component deep in your app to re-render, even if it doesn't use the modified data. It can slow things down. To solve this, techniques like memoization can help optimise re-renders. For example:

// Context.js
import { createContext, useContext, useState } from 'react';

const MyContext = createContext();

export const MyProvider = ({ children }) => {
  const [count, setCount] = useState(0);

  return (
    <MyContext.Provider value={{ count, setCount }}>
      {children}
    </MyContext.Provider>
  );
};

export const useMyContext = () => useContext(MyContext);

Complexity and Nesting

Nested contexts can become complex and harder to manage. The more contexts you stack on top of each other, the harder it becomes to reason about data flow and maintainability. If your application demands a labyrinthine network of contexts, consider a more structured approach. For example:

// NestedContexts.js
import { createContext, useContext } from 'react';

const NestedContext = createContext();

export const NestedProvider = ({ children }) => {
  // ...
};

export const useNestedContext = () => useContext(NestedContext);

Global vs. Local State

Context provides global state, but sometimes local component state is more appropriate. For example:

// LocalState.js
import { useState } from 'react';

const LocalStateComponent = () => {
  const [localCount, setLocalCount] = useState(0);

  return (
    <div>
      <p>Local Count: {localCount}</p>
      <button onClick={() => setLocalCount(localCount + 1)}>
        Increment Local Count
      </button>
    </div>
  );
};

Testing and Mocking

Testing components that rely heavily on context can be challenging. Mocking context values in unit tests becomes an additional step compared to testing components with self-contained state. Don't worry, React Testing Library offers tools and techniques to streamline testing with context providers. For example:

// MyComponent.test.js
import { render } from '@testing-library/react';
import MyComponent from './MyComponent';

test('renders correctly', () => {
  // How to mock context values?
  // ...
});

React Context has its limitations, it’s still a valuable tool for managing state in certain scenarios. For more complex applications, consider using libraries like ReduxMobX, or Zustand.

Best Practices for Using React Context API

Now we'll get down to the nitty-gritty – best practices to ensure you use Context API effectively and avoid any potential pitfalls.

Separate Files for Context Definitions

Imagine working on a massive React project with tons of components. Now, you are trying to find the logic for a specific piece of state scattered throughout your codebase. Not fun, right? Thus, when creating contexts, it's a smart idea to keep them in separate files. This makes your codebase more organized and easier to maintain.

Context for Global State, Not Everything

React Context API shines at managing data that needs to be shared across a significant portion of your component tree. However, remember that Context API isn't a one-size-fits-all solution for state management. Here's when Context API is not ideal:

  • Complex Application State: Context API can struggle to manage complex application state with numerous interdependent data points. Redux is a more structured and scalable state management library for these situations.
  • Overuse and Performance Issues: Context API is helpful, but excessive use might cause unnecessary re-renders, especially with highly nested contexts. If performance is an issue, use Context API judiciously and explore memoization to optimise re-renders.

Conclusion

In this article, we reviewed what the React Context API is, basic usage and hands on how it works. We also cleared up some misconceptions surrounding the React Context API.

Remember, the React Context API is a powerful tool for passing data between components and reducing the need for prop drilling. It simplifies data sharing without prop drilling, works well with hooks and React Suspense, and is great for smaller state operations. However, it’s not designed for global state management or app-wide scenarios, and overusing it can lead to unnecessary re-renders and performance issues.

>>> Follow and Contact Relia Software for more information!

  • coding
  • development