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:
- Mastering React Higher-Order Components (HOCs)
- Top 6 Best React Component Libraries for Your Projects
- The Best React Design Patterns with Code Examples
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 calledchildren
, which represents the child components that will be wrapped by the context provider. - State Management: Inside the
ThemeProvider
, there is a state variable calledisDarkTheme
initialized withfalse
. This state variable represents whether the dark theme is currently active. - Toggle Function: The
toggleTheme
function is defined to toggle the value ofisDarkTheme
. It uses thesetIsDarkTheme
function from React’suseState
hook. When called, it flips the value ofisDarkTheme
(fromtrue
tofalse
or vice versa). - Context Provider: The
ThemeProvider
component wraps its children with a context provider. Specifically, it uses theThemeContext.Provider
provided by the React Context API. Thevalue
prop of this provider is an object containing theisDarkTheme
state and thetoggleTheme
function. - Usage: When you use the
ThemeProvider
in your app, any child component that consumes theThemeContext
will have access to theisDarkTheme
state andtoggleTheme
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 theThemeContext
.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 theThemeContext
.
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 likeisDarkTheme
andtoggleTheme
.
The button element is rendered with the following attributes:
onClick={toggleTheme}
: When the button is clicked, thetoggleTheme
function will be executed.className={
${isDarkTheme ? ‘lightBtn’ : ‘darkBtn’}}
: The button’s class name is conditionally set based on theisDarkTheme
value. IfisDarkTheme
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
- 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 Redux, MobX, 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