Hello campers 💙
Today we’ll tackle the challenges every large app faces:
· Global state (without drilling props through 10 levels)
· Advanced state logic (like a spreadsheet for your data)
· Custom reusable logic (your own hooks)
· Performance (stop wasted re-renders)
· Portal modals, Error Boundaries, Lazy Loading, and more.
Let’s dive in!
Part 1 --- State Management Deep Dive
⚠️ The Prop Drilling Problem
Remember lifting state up? It works, but what if your component tree is 5 levels deep?
<App>
<Layout>
<Sidebar>
<UserMenu>
<Avatar user={user} /> {/* user had to travel all the way down */}
</UserMenu>
</Sidebar>
</Layout>
</App>Passing user through components that don’t even use it is called prop drilling. It’s messy and hard to refactor.
Analogy: Like giving a message to a receptionist, who gives it to a manager, who gives it to a team lead, just to reach the developer. Waste of time.
🧠 Context API --- The Solution
Context provides a way to share data across the entire component tree without passing props manually.
Steps to Use Context
1. Create the Context
import { createContext, useContext } from "react";
const UserContext = createContext();2. Provide the Context (wrap your parent)
function App() {
const [user, setUser] = useState({ name: "Megersa" });
return (
<UserContext.Provider value={{ user, setUser }}>
<Dashboard />
</UserContext.Provider>
);
}3. Consume the Context (in any child)
function Avatar() {
const { user } = useContext(UserContext);
return <h1>{user.name}</h1>;
}No more drilling! 🎉
Analogy: Context is like a company-wide announcement system. Instead of whispering down the hallway (props), you broadcast it to everyone who cares (useContext).
⚙️ useReducer --- Complex State Logic
useState is great for simple data (strings, numbers). But when you have complex state with multiple sub-values or transitions (e.g., "ADD_ITEM", "REMOVE_ITEM", "UPDATE_TOTAL"), useReducer is your friend.
Basic Syntax
import { useReducer } from "react";
// 1. Define a reducer function
function cartReducer(state, action) {
switch (action.type) {
case "ADD":
return { ...state, count: state.count + 1 };
case "REMOVE":
return { ...state, count: state.count - 1 };
default:
return state;
}
}
function Cart() {
// 2. useReducer returns [state, dispatch]
const [state, dispatch] = useReducer(cartReducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "ADD" })}>+</button>
<button onClick={() => dispatch({ type: "REMOVE" })}>-</button>
</div>
);
}Analogy: useState is a light switch (on/off). useReducer is a TV remote—lots of buttons (actions) that change the screen (state) in predictable ways.
🛠️ Custom Hooks --- Reusable Logic
If you find yourself repeating logic across components (e.g., fetching data, tracking window size, managing local storage), extract it into a Custom Hook.
Rules:
· Must start with use (e.g., useFetch).
· Can use other hooks inside (useState, useEffect, useContext).
Example: useFetch
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch(url)
.then(res => res.json())
.then(data => { setData(data); setLoading(false); })
.catch(err => { setError(err); setLoading(false); });
}, [url]);
return { data, loading, error };
}
// Usage in any component
function Users() {
const { data, loading, error } = useFetch("https://api.example.com/users");
if (loading) return <p>Loading...</p>;
return <div>{data.map(user => <p key={user.id}>{user.name}</p>)}</div>;
}