Blogs

calendar img
vijesh_profile
Vijesh ChoudhariSoftware Engineerauthor linkedin

React Context with TypeScript

img

What is the React contextApi?

The React Context API is a feature that simplifies state management in React applications by providing a way to share data across components without the need for prop drilling. It consists of a createContext function to establish a context, a Provider component to wrap the part of the component tree where data is shared, and a Consumer component or the useContext hook to access and consume the shared data in child components. This allows for more efficient and cleaner management of global state, making it especially beneficial for applications with deeply nested component hierarchies.

Let's dive into this react world to explore and understand about contextApi along with a small project.

Create a React Project (TypeScript)

To get a better overview of React contextApi we will create a notification system with React TypeScript.

A notification system where components can trigger notifications to be displayed. Use React Context to manage the notifications globally, allowing any part of the application to trigger or display notifications without passing data through props.

  • npx create-react-app article-react-context-notification --template typescript

To create a TypeScript project in React, you have to use flag --template typescript along with CRA.

Folder Structure

Following a proper folder structure and proper naming convention is a good practice of maintainability and scalability of a project. To learn more about the design patterns and best practices of React, Click Here.

1	src/
2 |-- components/
3 |   |-- NotificationProvider/
4 |   |   |-- NotificationProvider.tsx
5 |   |
6 |   |-- Notification/
7 |       |-- Notification.tsx
8 |
9 |-- hooks/
10 |   |-- useNotification.ts
11 |
12 |-- styles/
13 |   |-- Notification.css
14 |
15 |-- App.tsx
16 |-- index.tsx


Context Creation

  • Initial state

Define the initial state of your context. In this case of a notification system, this might include an array of notifications.

1// src/components/NotificationProvider/NotificationProvider.tsx
2
3import React, { createContext, useContext, useState, ReactNode } from "react";
4
5interface NotificationContextProps {
6  showNotification: (message: string) => void;
7  removeNotification: (index: number) => void;
8}
9
10const NotificationContext = createContext<NotificationContextProps | undefined>(
11  undefined
12);
13
14interface NotificationProviderProps {
15  children: ReactNode;
16}
17
18export const NotificationProvider: React.FC<NotificationProviderProps> = ({
19  children,
20}) => {
21  const [notifications, setNotifications] = useState<string[]>([]);
22
23  const showNotification = (message: string) => {
24    setNotifications((prevNotifications) => [...prevNotifications, message]);
25  };
26
27  const removeNotification = (index: number) => {
28    setNotifications((prevNotifications) =>
29      prevNotifications.filter((_, i) => i !== index)
30    );
31  };
32
33  return (
34    <NotificationContext.Provider
35      value={{ showNotification, removeNotification }}
36    >
37      {children}
38      <div className="notification-container">
39        {notifications.map((message, index) => (
40          <div
41            key={index}
42            className="notification"
43            onClick={() => removeNotification(index)}
44          >
45            {message}
46          </div>
47        ))}
48      </div>
49    </NotificationContext.Provider>
50  );
51};
52
53export const useNotification = () => {
54  const context = useContext(NotificationContext);
55  if (!context) {
56    throw new Error(
57      "useNotification must be used within a NotificationProvider"
58    );
59  }
60  return context;
61};


In this example, the NotificationProvider initializes a state for storing notifications. The showNotification function adds a new notification, and removeNotification removes a notification by its index.

It will throw error when the context is null or undefined which is likely to be happen when you useNotification() outside NotificationProvider component.


  • Component

Create a Notification component which renders the notification message.

1// src/components/Notification/Notification.tsx
2
3import React from "react";
4
5interface NotificationProps {
6  message: string;
7}
8
9const Notification: React.FC<NotificationProps> = ({ message }) => {
10  return <div className="notification">{message}</div>;
11};
12
13export default Notification;



  • Context Provider

Wrap the entire component tree with the context provider. This ensures that all child components can access the context.


1// src/index.tsx
2
3import React from "react";
4import ReactDOM from "react-dom";
5import App from "./App";
6import { NotificationProvider } from "./components/NotificationProvider/NotificationProvider";
7
8ReactDOM.render(
9  <React.StrictMode>
10    <NotificationProvider>
11      <App />
12    </NotificationProvider>
13  </React.StrictMode>,
14  document.getElementById("root")
15);

Utilizing the Context

  • Create a hook of created context:

Once the context is created, you can create the hook component, which returns the hook as a function.

1// src/hooks/useNotification.ts
2
3import { useNotification as useNotificationContext } from "../components/NotificationProvider/NotificationProvider";
4
5const useNotification = () => {
6  const { showNotification } = useNotificationContext();
7  return { showNotification };
8};
9
10export default useNotification;


Here we return the hooks, when we create a hook it name should start with use . It can be used to passed the value to all the components.


  • Using created hook in the component:


1// src/App.tsx
2
3import React, { useState } from "react";
4import Notification from "./components/Notification/Notification";
5import useNotification from "./hooks/useNotification";
6import "./styles/Notification.css";
7
8const App: React.FC = () => {
9  const [message, setMessage] = useState("");
10
11  const { showNotification } = useNotification();
12  const HandleButtonClick = (message: string) => {
13    showNotification(message);
14  };
15
16  return (
17    <div>
18      <h1>React Context API Notification System</h1>
19      <input
20        className="messageInput"
21        type="text"
22        placeholder="Type a notification message"
23        onChange={(e) => setMessage(e.target.value.trim())}
24      />
25      <button
26        className="notificationButton"
27        onClick={() => HandleButtonClick(message)}
28      >
29        Show Notification
30      </button>
31      <Notification message="Hello from App component!" />
32    </div>
33  );
34};
35
36export default App;


It includes an input field to enter a notification message, a button to trigger the notification, and a placeholder notification rendered by default. The state hook (useState) manages the input message. The useNotification hook is used to access the global notification context, and the HandleButtonClick function shows a notification when the button is clicked.


Here we all set, now run the React project.

yarn start

or

npm start


After compilation the project will run on localhost at port 3000 (Default port for react application).

http://localhost:3000/


Output:
React useContext Output

Why using React contextApi ?


React with Context

  • Dynamic Value Changes:

Context allows for dynamic changes to the shared values. When the context value changes, all components that consume that context will be re-rendered.


  • Provider-Consumer Relationship:

The Provider component defines the scope of the context, and only components within its subtree can consume the provided context. This establishes a clear provider-consumer relationship.


  • Default Values:

Context providers can have default values, providing a fallback in case a component does not find a matching provider in its ancestry.


  • Single Source of Truth:

Context can act as a single source of truth for shared state, reducing the risk of inconsistencies that might arise from managing state in multiple places.


  • Complex State Management:

Ideal for complex state management scenarios where multiple components at different levels of the component tree need access to the same state or functions.

React without Context

  • Explicit Prop Passing:

State and functions need to be explicitly passed down the component tree via props. This can lead to a more predictable flow of data, but it might become cumbersome in deeply nested structures.


  • Simplicity for Small Applications:

In simpler applications, especially those with a shallow component tree, prop drilling might be straightforward and less of a concern. In such cases, using context could introduce unnecessary complexity.


  • Clear Data Flow:

Prop drilling provides a clear and explicit data flow, making it easier to trace the flow of data through the application.


  • Easier Testing:

Components are often more self-contained and can be easier to test since their behavior is directly tied to the props they receive.


  • Performance Considerations:

For very large applications, prop drilling might lead to performance issues due to the sheer number of props being passed down the component tree. In such cases, context might offer a more optimized solution.

React Context vs Redux


Key points

React Context

Redux

Scope

Context is scoped to a React component tree. It provides a way to share values (such as state or functions) within the components that are part of the same subtree.

Redux is a state management library that is not tied to React. It can be used with any JavaScript framework or library and is not limited to a specific component tree.

API

Context is part of the React core library and is designed to solve the problem of prop drilling by allowing data to be passed down the component tree without explicitly passing props at each level.

Redux has a more extensive API compared to Context. It involves actions, reducers, and a store to manage the state globally. Actions are dispatched to trigger state changes, and reducers handle these changes.

Use Case

Context is well-suited for managing state in scenarios where a few components (or a subtree) need access to shared data. It's often used for simpler state management within a React application.

Redux is well-suited for managing complex state across an entire application, especially when the state needs to be accessed by many components that are not necessarily in the same component tree.

Updates

When the context value changes, only the components within the subtree that consume that context are re-rendered.

Redux follows a unidirectional data flow. When the Redux store is updated, it triggers a re-render of components connected to the store. This allows for a predictable and centralized way of managing application state.


Conclusion

  • In conclusion, the React Context API provides a powerful and convenient solution for managing state and sharing data across components in a React application. It addresses the challenges of prop drilling by allowing the creation of context objects, which can be consumed by components at different levels of the component tree.
  • The Context API is particularly valuable in scenarios where state needs to be shared between components that are not directly connected or when prop drilling becomes impractical. It simplifies the process of passing data through the component tree, leading to cleaner and more maintainable code.
  • While the Context API is a versatile tool, it's essential to use it judiciously. For simpler applications or when dealing with local component state, using context might be overkill. It's crucial to strike a balance between using context for efficient state management and not introducing unnecessary complexity.
  • Understanding the nuances of creating, providing, and consuming context values, as well as knowing when to use context versus other state management solutions, empowers developers to make informed decisions about the best approach for their specific use cases.
  • In summary, the React Context API is a valuable addition to the toolkit of React developers, providing a clean and efficient way to manage state and share data between components. When used thoughtfully, it contributes to the creation of more modular, scalable, and maintainable React applications.

FAQs

  1. When should I use the React Context API ?
  • You want to avoid prop drilling:

When passing data through many components gets messy, and you want an easier way to share state without threading it through every level.

  • Managing global state:

If you have state that multiple parts of your app need to access or modify, and you want a central place to handle it.

  • Dealing with UI-related state:

When you have UI state, like modals or theme settings, that needs to be shared among different components.

  • Simplifying form state:

If you're dealing with forms spread across various components and want an organized way to manage and update form data.

  • Handling user authentication:

When you need different parts of your app to respond to changes in user authentication status without passing the info through props.

  • Dealing with dynamic theming or configuration:

If your app involves dynamic theming or configuration changes that components need to adapt to.

  • Localization or internationalization:

When you want to share the current language or localization settings across different components.


  1. Can I use multiple contexts in a React application?

Yes, you can use multiple contexts in a React application. Each context operates independently, and components can subscribe to multiple contexts.


  1. How does React Context handle performance optimization?

React Context uses a form of "render prop" optimization. It only re-renders components that consume the context when the context value changes, preventing unnecessary renders.


  1. Can I use React Context with class components?

Yes, React Context can be used with both functional and class components. However, using it with functional components and hooks like useContext is more common in modern React development.


  1. What happens if a component does not have a matching context provider?

If a component tries to consume a context without a matching provider in its ancestry, it will use the default value provided during the context creation.