Welcome to Part 5 where we’ll elevate your React skills with professional patterns for state management and navigation. These techniques are used in production-grade React applications!
Creating Custom Hooks
Custom hooks let you extract component logic into reusable functions.
useLocalStorage Hook
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const storedValue = localStorage.getItem(key);
return storedValue ? JSON.parse(storedValue) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// Usage:
function ThemeToggle() {
const [darkMode, setDarkMode] = useLocalStorage('darkMode', false);
return (
<button onClick={() => setDarkMode(!darkMode)}>
{darkMode ? '🌙 Dark' : '☀️ Light'} Mode
</button>
);
}
useFetch Hook
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
setLoading(true);
fetch(url, { signal: controller.signal })
.then(res => {
if (!res.ok) throw new Error(res.statusText);
return res.json();
})
.then(data => {
setData(data);
setError(null);
})
.catch(err => {
if (err.name !== 'AbortError') setError(err.message);
})
.finally(() => setLoading(false));
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
// Usage:
function UserProfile({ userId }) {
const { data: user, loading, error } = useFetch(`/api/users/${userId}`);
if (loading) return <Spinner />;
if (error) return <Error message={error} />;
return (
<div>
<h2>{user.name}</h2>
<p>{user.bio}</p>
</div>
);
}
Managing Global State with Context API
1. Creating Context
import { createContext, useContext, useState } from 'react';
const AuthContext = createContext();
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = (userData) => setUser(userData);
const logout = () => setUser(null);
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
return useContext(AuthContext);
}
2. Using Context
// Wrap your app
function App() {
return (
<AuthProvider>
<Router>
<Header />
<MainContent />
</Router>
</AuthProvider>
);
}
// In any component
function Header() {
const { user, logout } = useAuth();
return (
<header>
{user ? (
<button onClick={logout}>Logout {user.name}</button>
) : (
<LoginButton />
)}
</header>
);
}
Adding Routing with React Router
Basic Setup (v6)
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/about" element={<AboutPage />} />
<Route path="/users/:id" element={<UserProfile />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>
);
}
Protected Routes
function ProtectedRoute({ children }) {
const { user } = useAuth();
if (!user) {
return <Navigate to="/login" replace />;
}
return children;
}
// Usage:
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
Practical Example: E-Commerce App
// hooks/useCart.js
export function useCart() {
const [cart, setCart] = useState([]);
const addToCart = (product) => {
setCart(prev => [...prev, product]);
};
const removeFromCart = (productId) => {
setCart(prev => prev.filter(item => item.id !== productId));
};
return { cart, addToCart, removeFromCart };
}
// context/CartContext.js
const CartContext = createContext();
export const CartProvider = ({ children }) => {
const cart = useCart();
return <CartContext.Provider value={cart}>{children}</CartContext.Provider>;
};
export const useCartContext = () => useContext(CartContext);
// App.js
function App() {
return (
<CartProvider>
<BrowserRouter>
<Header />
<Routes>
<Route path="/" element={<ProductListing />} />
<Route path="/cart" element={<CartPage />} />
</Routes>
</BrowserRouter>
</CartProvider>
);
}
What’s Next?
In Part 6, we’ll explore:
✅ Advanced React patterns (Render Props, HOCs)
✅ Performance optimization techniques
✅ Testing React apps with Jest and React Testing Library
Exercise for You
Create a useDebounce hook that delays updating a value until after a specified timeout. Use it to implement a search input that only triggers API calls after the user stops typing for 500ms.
Enjoyed this tutorial? Subscribe for Part 6! 🔥
Questions? Ask in the comments below!
Series Navigation:
🔹 Part 1: Introduction to React
🔹 Part 2: JSX, Components & Props
🔹 Part 3: State and Events
🔹 Part 4: useEffect & Conditional Rendering
🔹 Part 5: Custom Hooks & Context API (You’re here)
🔹 Part 6: Advanced Patterns & Optimization
