Advanced React Concepts
Senior-level documentation focusing on the why (architecture/trade-offs) rather than just the how (syntax). These patterns solve real production problems: Flexibility, Performance, and Maintainability.
๐งฑ Module 1: The "Element Prop" Pattern
Stop passing booleans and strings to control what renders! ๐ The Element Prop pattern flips the script โ instead of telling a component WHAT to render, you hand it the actual JSX. It's like giving someone a painting instead of paint-by-numbers instructions! ๐จ๐ผ๏ธ
๐ Impact: HIGH โ Used extensively in Material UI, Ant Design, and Radix. This is how the pros build flexible component APIs! ๐ช
๐ In this section: Element props vs boolean props โข Slot-based rendering โข Decoupled component APIs โข Performance benefits
๐ก Benefits: Decoupling: The component doesn't need to import child components or know about their props. Performance: The passed elements are instantiated by the parent, so they don't necessarily re-render just because the parent re-renders (if they are memoized or static). ๐
// โ Rigid & Hard to Maintain// Card needs to know about Button, Icon, etc. - limited flexibilityconst Button = ({ variant, children }) => <button className={variant}>{children}</button>;const Icon = ({ name }) => <span>[{name}]</span>;const UserProfile = () => <div>User content</div>;type CardProps = {title: string;showEditButton?: boolean;showIcon?: boolean;iconName?: string;footerText?: string;};const Card = ({ title, showEditButton, showIcon, iconName, footerText, children }) => (<div className="card"><div className="header"><h1>{title}</h1>{showIcon && iconName && <Icon name={iconName} />}{showEditButton && <Button variant="ghost">Edit</Button>}</div><div className="content">{children}</div>{footerText && <div className="footer">{footerText}</div>}</div>);function App() {return (<Cardtitle="Profile"showEditButton={true}showIcon={false}iconName="user"footerText="Last updated: Today"><UserProfile /></Card>);}export default App;
// โ Flexible & Composable - Card doesn't care what you passimport { ReactNode } from 'react';const Button = ({ variant, children }) => <button className={variant}>{children}</button>;const Link = ({ href, children }) => <a href={href}>{children}</a>;const UserProfile = () => <div>User content</div>;type CardProps = {title: string;headerAction?: ReactNode;footer?: ReactNode;children: ReactNode;};const Card = ({ title, headerAction, footer, children }: CardProps) => (<div className="card"><div className="header"><h1>{title}</h1><div className="actions">{headerAction}</div></div><div className="content">{children}</div>{footer && <div className="footer">{footer}</div>}</div>);function App() {return (<Cardtitle="Profile"headerAction={<Button variant="ghost">Edit</Button>}footer={<span className="text-sm">Last updated: Today</span>}><UserProfile /></Card>);}export default App;
โก Module 2: Optimizing Context API (Split Context Pattern)
Your Context is probably causing more re-renders than you think! ๐ฌ Every time ANY part of your context changes, EVERY consumer re-renders โ even if they only use a tiny piece. The Split Context Pattern is like giving each consumer their own VIP subscription! ๐๏ธโจ
๐ด Impact: CRITICAL โ The #1 performance killer in large React apps. Fix this and watch your re-renders drop dramatically! ๐๐ฅ
๐ In this section: Re-render blast radius โข Context splitting โข useMemo for stable references โข Custom consumer hooks
๐ก Benefits: Performance: Components that only need to trigger an action (like a LogoutButton) utilize the actions context. They will never re-render when the state changes. ๐ฏ Scalability: Prevents the "Context Hell" performance bottleneck in large apps. ๐๏ธ
// โ The "God Object" - Any component using 'login' will re-render when 'user' changesimport { createContext, useContext, useState, ReactNode } from 'react';type User = { id: string; name: string; email: string };type AuthContextValue = {user: User | null;login: () => Promise<void>;logout: () => void;};const AuthContext = createContext<AuthContextValue | null>(null);export const AuthProvider = ({ children }: { children: ReactNode }) => {const [user, setUser] = useState<User | null>(null);const login = async () => {// ... login logicsetUser({ id: '1', name: 'John', email: 'john@example.com' });};const logout = () => {setUser(null);};// ๐ PROBLEM: If 'user' updates, this entire object reference changes// Every component using this context will re-render, even if they only// use the 'login' function and don't care about 'user' changesreturn (<AuthContext.Provider value={{ user, login, logout }}>{children}</AuthContext.Provider>);};export const useAuth = () => {const context = useContext(AuthContext);if (!context) throw new Error('useAuth must be used within AuthProvider');return context;};const LogoutButton = () => {const { logout } = useAuth();return <button onClick={logout}>Logout</button>;};function App() {return (<AuthProvider><LogoutButton /><UserProfile /></AuthProvider>);}export default App;
// โ Context Splitting - Separate state and actionsimport { createContext, useContext, useState, useMemo, ReactNode } from 'react';type User = { id: string; name: string; email: string };// โ 1. Create two separate contextsconst AuthStateContext = createContext<User | null>(null);const AuthActionsContext = createContext<{login: () => Promise<void>;logout: () => void;} | null>(null);export const AuthProvider = ({ children }: { children: ReactNode }) => {const [user, setUser] = useState<User | null>(null);// โ 2. Memoize the actions so they are referentially stable// This object NEVER changes, so consumers of this context NEVER re-render.const actions = useMemo(() => ({login: async () => {// ... login logicsetUser({ id: '1', name: 'John', email: 'john@example.com' });},logout: () => setUser(null)}), []); // Empty deps = stable reference foreverreturn (<AuthActionsContext.Provider value={actions}><AuthStateContext.Provider value={user}>{children}</AuthStateContext.Provider></AuthActionsContext.Provider>);};// โ Custom Hooks for consumptionexport const useUser = () => {const context = useContext(AuthStateContext);if (context === undefined) {throw new Error('useUser must be used within AuthProvider');}return context;};export const useAuthActions = () => {const context = useContext(AuthActionsContext);if (!context) {throw new Error('useAuthActions must be used within AuthProvider');}return context;};// Usage - LogoutButton NEVER re-renders when user changes!const LogoutButton = () => {const { logout } = useAuthActions(); // โ Only subscribes to actions contextreturn <button onClick={logout}>Logout</button>;};const UserProfile = () => {const user = useUser();return <div>{user?.name}</div>;};function App() {return (<AuthProvider><LogoutButton /><UserProfile /></AuthProvider>);}export default App;
๐งฎ Module 3: Less UseEffects (Derived State)
Stop putting EVERYTHING in useEffect! ๐ It's the most overused hook in React and the source of countless race conditions, infinite loops, and mystery re-renders. If you can calculate it during render, DO IT during render. Your components will be simpler, faster, and bug-free! ๐โก๏ธโจ
๐ด Impact: CRITICAL โ useEffect abuse is the #1 source of bugs in React apps. Learn derived state and eliminate an entire category of bugs! ๐ฏ
๐ In this section: useEffect overuse โข Derived state with useMemo โข Single vs double renders โข When to actually use effects
๐ก Why this matters: Derived state eliminates unnecessary re-renders, prevents race conditions, and makes the code more predictable. Effects should be reserved for synchronization with external systems (like connecting to a socket or observing DOM changes), not for transforming data. ๐
// โ Bad: Double Render - effect syncs state (two renders)import { useState, useEffect } from 'react';type Item = { id: string; category: string; name: string };const ProductList = ({ items, filter }) => {const [filteredItems, setFilteredItems] = useState([]);useEffect(() => {setFilteredItems(items.filter(i => i.category === filter));}, [items, filter]);return (<ul>{filteredItems.map(item => (<li key={item.id}>{item.name}</li>))}</ul>);};const MOCK_ITEMS = [{ id: '1', category: 'A', name: 'Item 1' },{ id: '2', category: 'B', name: 'Item 2' },{ id: '3', category: 'A', name: 'Item 3' },];function App() {const [filter, setFilter] = useState('A');return (<div><button onClick={() => setFilter('A')}>Category A</button><button onClick={() => setFilter('B')}>Category B</button><ProductList items={MOCK_ITEMS} filter={filter} /></div>);}export default App;
// โ Good: Single render - derive during renderimport { useMemo, useState } from 'react';type Item = { id: string; category: string; name: string };const ProductList = ({ items, filter }) => {const filteredItems = useMemo(() => items.filter(i => i.category === filter),[items, filter]);return (<ul>{filteredItems.map(item => (<li key={item.id}>{item.name}</li>))}</ul>);};const MOCK_ITEMS = [{ id: '1', category: 'A', name: 'Item 1' },{ id: '2', category: 'B', name: 'Item 2' },{ id: '3', category: 'A', name: 'Item 3' },];function App() {const [filter, setFilter] = useState('A');return (<div><button onClick={() => setFilter('A')}>Category A</button><button onClick={() => setFilter('B')}>Category B</button><ProductList items={MOCK_ITEMS} filter={filter} /></div>);}export default App;
๐ When to Use useEffect
OK so when SHOULD you use useEffect? ๐ค Think of it as a bridge to the outside world โ use it ONLY when you need to sync with something React doesn't control. Here's the definitive list! ๐
๐ต Impact: MEDIUM โ Knowing when NOT to use useEffect is just as important as knowing when to use it. Master this mental model! ๐ง
๐ In this section: Valid useEffect use cases โข External system sync โข Event listeners โข Cleanup patterns
โ
Valid useEffect Use Cases:
โข ๐ Connecting to external APIs (WebSockets, REST subscriptions)
โข ๐ Setting up event listeners (window resize, keyboard shortcuts)
โข ๐ Integrating with third-party libraries (charts, maps)
โข ๐งน Cleaning up resources (unsubscribing, removing listeners)
โข ๐พ Synchronizing with browser APIs (localStorage, history)
// โ Don't use useEffect for transforming data or user eventsimport { useState, useEffect } from 'react';function App() {const [items] = useState([{ id: '1', category: 'A', name: 'Item 1' }]);const [filter] = useState('A');const [filteredItems, setFilteredItems] = useState([]);useEffect(() => {setFilteredItems(items.filter(i => i.category === filter));}, [items, filter]);return (<div><p>Anti-pattern: effect syncing state (causes extra render)</p><ul>{filteredItems.map(item => <li key={item.id}>{item.name}</li>)}</ul></div>);}export default App;
// โ Use useEffect for sync with external systems (e.g. resize)import { useState, useEffect } from 'react';function App() {const [width, setWidth] = useState(typeof window !== 'undefined' ? window.innerWidth : 0);useEffect(() => {const handleResize = () => setWidth(window.innerWidth);window.addEventListener('resize', handleResize);return () => window.removeEventListener('resize', handleResize);}, []);return <div>Window width: {width}px (resize to see)</div>;}export default App;