context.api
React.js
When working with React, sharing data between multiple components can become complicated. Initially, we use useState
to handle local states, but as our application grows, we encounter problems such as:
Excessive prop drilling: Passing data from a parent component to a distant child component can be frustrating and inefficient.
Scattered states: Each component manages its own state, making it difficult to coordinate global changes.
Lack of centralized state management: Without a proper structure, updating the state globally can become messy.
A global state that can be accessed from anywhere in the application without the need for props.
A centralized mechanism to modify the state through well-defined actions.
More organized and maintainable code.
Every time the state changes, the components that consume it are automatically updated. It's similar to a television signal: a channel broadcasts the signal, and all tuned-in TVs receive it.
A reducer is a function that receives the current state and an action, and returns a new state based on that action.
1// store.js - Define the initial state and reducer functions 2 3export const initialStore = () => ({ 4 todos: ["Make the bed", "Take out the trash"] 5}); 6 7export default function storeReducer(state, action) { 8 switch (action.type) { 9 case "ADD_TODO": 10 return { ...state, todos: [...state.todos, action.payload] }; 11 default: 12 return state; 13 } 14}
Here we create a Context and a Provider that will wrap our application to share the global state.
1import { useContext, useReducer, createContext } from "react"; 2import storeReducer, { initialStore } from "../store"; 3 4const StoreContext = createContext(); 5 6export function StoreProvider({ children }) { 7 const [store, dispatch] = useReducer(storeReducer, initialStore()); 8 return ( 9 <StoreContext.Provider value={{ store, dispatch }}> 10 {children} 11 </StoreContext.Provider> 12 ); 13} 14 15export default function useGlobalReducer() { 16 return useContext(StoreContext); 17}
To allow all components to access the global state, we need to wrap our application with the StoreProvider in the main file.
1import React from "react"; 2import ReactDOM from "react-dom"; 3import { StoreProvider } from "./context/StoreContext"; 4import App from "./App"; 5 6ReactDOM.render( 7 <StoreProvider> 8 <App /> 9 </StoreProvider>, 10 document.getElementById("root") 11);
Now we can access the global state and modify it from any component with useGlobalReducer().
1import React from "react"; 2import useGlobalReducer from "../context/StoreContext"; 3 4const TodoList = () => { 5 const { store, dispatch } = useGlobalReducer(); 6 7 return ( 8 <div> 9 <ul> 10 {store.todos.map((task, i) => ( 11 <li key={i}>{task}</li> 12 ))} 13 </ul> 14 <button onClick={() => dispatch({ type: "ADD_TODO", payload: `Task ${store.todos.length + 1}` })}> 15 + Add task 16 </button> 17 </div> 18 ); 19}; 20 21export default TodoList;
The store is the central point of our application, so we must ensure that its information is not modified directly. Instead, we use dispatch to execute actions that update the state.