Advanced Patterns
Explore advanced React patterns including custom hooks composition, provider patterns, and performance optimization techniques. This is a foundational concept in component-based UI development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world React experience. Take your time with each section and practice the examples
Custom Hooks Composition
Learn to compose multiple custom hooks together to create powerful, reusable logic that can be shared across components.. This is an essential concept that every React developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
Provider Pattern
// Advanced provider pattern with multiple contexts
const AppContext = createContext();
const ThemeContext = createContext();
const AuthContext = createContext();
const AppProvider = ({ children }) => {
const [theme, setTheme] = useState('light');
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
const login = async (credentials) => {
setIsLoading(true);
try {
const userData = await authService.login(credentials);
setUser(userData);
} catch (error) {
throw error;
} finally {
setIsLoading(false);
}
};
const logout = () => {
setUser(null);
authService.logout();
};
return (
<AppContext.Provider value={{ isLoading }}>
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
</ThemeContext.Provider>
</AppContext.Provider>
);
};Advanced Custom Hooks
// Complex custom hook with multiple concerns
const useAsyncOperation = (asyncFunction, dependencies = []) => {
const [state, setState] = useState({
data: null,
loading: false,
error: null
});
const execute = useCallback(async (...args) => {
setState(prev => ({ ...prev, loading: true, error: null }));
try {
const result = await asyncFunction(...args);
setState({ data: result, loading: false, error: null });
return result;
} catch (error) {
setState({ data: null, loading: false, error });
throw error;
}
}, dependencies);
const reset = useCallback(() => {
setState({ data: null, loading: false, error: null });
}, []);
return { ...state, execute, reset };
};
// Usage
const UserProfile = ({ userId }) => {
const { data: user, loading, error, execute } = useAsyncOperation(
(id) => userService.getUser(id),
[userId]
);
useEffect(() => {
execute(userId);
}, [execute, userId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{user?.name}</div>;
};Performance Optimization Patterns
- useMemo for expensive calculations — a critical concept in component-based UI development that you will use frequently in real projects
- useCallback for stable function references — a critical concept in component-based UI development that you will use frequently in real projects
- React.memo for component memoization — a critical concept in component-based UI development that you will use frequently in real projects
- Lazy loading with React.lazy — a critical concept in component-based UI development that you will use frequently in real projects
- Code splitting strategies — a critical concept in component-based UI development that you will use frequently in real projects
- Virtual scrolling for large lists — a critical concept in component-based UI development that you will use frequently in real projects
Advanced State Management
// Custom state management with reducer pattern
const useReducerWithMiddleware = (reducer, initialState, middleware = []) => {
const [state, dispatch] = useReducer(reducer, initialState);
const enhancedDispatch = useCallback((action) => {
let currentAction = action;
// Apply middleware
middleware.forEach(middlewareFn => {
currentAction = middlewareFn(currentAction, state);
});
dispatch(currentAction);
}, [state, middleware]);
return [state, enhancedDispatch];
};
// Logging middleware
const loggingMiddleware = (action, state) => {
console.log('Action:', action);
console.log('State before:', state);
return action;
};
// Usage
const [state, dispatch] = useReducerWithMiddleware(
counterReducer,
{ count: 0 },
[loggingMiddleware]
);Mini-Project: Advanced Data Management
// Complete data management system with caching and synchronization
import React, { createContext, useContext, useReducer, useCallback } from 'react';
const DataContext = createContext();
const dataReducer = (state, action) => {
switch (action.type) {
case 'SET_LOADING':
return { ...state, loading: action.payload };
case 'SET_DATA':
return {
...state,
data: action.payload,
cache: { ...state.cache, [action.key]: action.payload }
};
case 'SET_ERROR':
return { ...state, error: action.payload };
case 'INVALIDATE_CACHE':
return { ...state, cache: {} };
default:
return state;
}
};
const DataProvider = ({ children }) => {
const [state, dispatch] = useReducer(dataReducer, {
data: null,
loading: false,
error: null,
cache: {}
});
const fetchData = useCallback(async (key, fetcher) => {
// Check cache first
if (state.cache[key]) {
dispatch({ type: 'SET_DATA', payload: state.cache[key], key });
return;
}
dispatch({ type: 'SET_LOADING', payload: true });
try {
const data = await fetcher();
dispatch({ type: 'SET_DATA', payload: data, key });
} catch (error) {
dispatch({ type: 'SET_ERROR', payload: error.message });
}
}, [state.cache]);
const invalidateCache = useCallback(() => {
dispatch({ type: 'INVALIDATE_CACHE' });
}, []);
return (
<DataContext.Provider value={{ ...state, fetchData, invalidateCache }}>
{children}
</DataContext.Provider>
);
};
const useData = (key, fetcher) => {
const { data, loading, error, fetchData } = useContext(DataContext);
const loadData = useCallback(() => {
fetchData(key, fetcher);
}, [key, fetcher, fetchData]);
return { data, loading, error, loadData };
};
export { DataProvider, useData };