Back to Articles
45 min read

Mastering ReactJS: The Comprehensive Guide to Fundamentals, Hooks, and Advanced Patterns

Unlock the full potential of ReactJS. This deep-dive reference covers the entire ecosystem: understanding the Virtual DOM, mastering the complete Hooks API, navigating legacy class lifecycles, implementation of High-Order Components, and modern styling strategies for enterprise-grade applications.

React Fundamentals

React overview

React is a declarative, component-based JavaScript library for building user interfaces, developed by Facebook (now Meta). It allows you to create reusable UI components that efficiently update when data changes, making it ideal for single-page applications and complex interactive UIs.

┌─────────────────────────────────────┐
│           React App                 │
│  ┌─────────┐  ┌─────────┐          │
│  │Component│  │Component│          │
│  │  ┌───┐  │  │  ┌───┐  │          │
│  │  │ C │  │  │  │ C │  │          │
│  │  └───┘  │  │  └───┘  │          │
│  └─────────┘  └─────────┘          │
└─────────────────────────────────────┘

Virtual DOM

The Virtual DOM is a lightweight JavaScript representation of the actual DOM that React keeps in memory. When state changes, React creates a new Virtual DOM tree, diffs it against the previous one (reconciliation), and only updates the real DOM nodes that actually changed, making updates extremely efficient.

┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│  New State   │───▶│ Virtual DOM  │───▶│   Diff/Patch │
└──────────────┘    │   (New Tree) │    │   Real DOM   │
                    └──────────────┘    └──────────────┘
                           │
                           ▼
                    ┌──────────────┐
                    │ Virtual DOM  │
                    │  (Old Tree)  │
                    └──────────────┘

JSX syntax

JSX is a syntax extension that allows you to write HTML-like markup directly in JavaScript, which gets transpiled to React.createElement() calls by Babel. It makes component code more readable and intuitive while maintaining the full power of JavaScript.

// JSX syntax const element = <h1 className="greeting">Hello, World!</h1>; // Transpiles to: const element = React.createElement('h1', { className: 'greeting' }, 'Hello, World!');

JSX expressions

JSX expressions allow you to embed any valid JavaScript expression inside curly braces {} within your JSX markup. This enables dynamic content rendering, calculations, function calls, and conditional logic directly in your template.

const name = "Alice"; const age = 25; const element = ( <div> <p>Name: {name}</p> <p>Age: {age}</p> <p>Birth Year: {2024 - age}</p> <p>Is Adult: {age >= 18 ? 'Yes' : 'No'}</p> </div> );

JSX attributes

JSX attributes work similarly to HTML attributes but use camelCase naming convention and accept JavaScript expressions. Some attributes have different names (e.g., className instead of class, htmlFor instead of for) to avoid conflicts with JavaScript reserved words.

const element = ( <input type="text" className="input-field" // not 'class' htmlFor="username" // not 'for' tabIndex={0} // number without quotes disabled={false} // boolean style={{ color: 'red' }} // object for inline styles onClick={handleClick} // function reference /> );

JSX children

JSX children are the content placed between opening and closing tags, which can include strings, other JSX elements, expressions, or arrays. Children are passed to components via the special props.children property, enabling flexible component composition.

// Different types of children <Container> Plain text child <span>Element child</span> {variableChild} {items.map(item => <Item key={item.id} />)} {showExtra && <Extra />} </Container>

React elements

React elements are immutable plain JavaScript objects that describe what you want to see on the screen, representing DOM nodes or component instances. They are lightweight and cheap to create, and React uses them to construct and update the Virtual DOM.

// JSX creates React elements const element = <div id="root">Hello</div>; // Element object structure: { type: 'div', props: { id: 'root', children: 'Hello' } }

React components

Components are independent, reusable pieces of UI that accept inputs (props) and return React elements describing what should appear on screen. They enable you to split the UI into isolated pieces that can be developed, tested, and reasoned about independently.

// Component = Function that returns UI function Welcome(props) { return <h1>Hello, {props.name}</h1>; } // Usage <Welcome name="Alice" />

Function components

Function components are JavaScript functions that accept props as an argument and return JSX, representing the modern and preferred way to write React components. With hooks, they can manage state and lifecycle, making class components largely unnecessary.

function Greeting({ name, age }) { const [count, setCount] = useState(0); return ( <div> <h1>Hello, {name}!</h1> <p>Age: {age}</p> <button onClick={() => setCount(count + 1)}> Clicked {count} times </button> </div> ); }

Class components (legacy)

Class components are ES6 classes that extend React.Component and must implement a render() method, representing the older pattern for creating stateful components. While still supported, they are considered legacy and function components with hooks are now preferred.

class Greeting extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } render() { return ( <div> <h1>Hello, {this.props.name}</h1> <button onClick={() => this.setState({ count: this.state.count + 1 })}> Count: {this.state.count} </button> </div> ); } }

Component composition

Component composition is the pattern of building complex UIs by combining simpler components together, favoring composition over inheritance. This approach makes components more reusable, testable, and follows the single responsibility principle.

// Small, focused components function Avatar({ user }) { return <img src={user.avatar} alt={user.name} />; } function UserInfo({ user }) { return ( <div className="user-info"> <Avatar user={user} /> <span>{user.name}</span> </div> ); } function Comment({ author, text, date }) { return ( <div className="comment"> <UserInfo user={author} /> <p>{text}</p> <span>{date}</span> </div> ); }

Props

Props (short for properties) are read-only inputs passed from parent to child components, similar to function arguments. They enable data flow down the component tree and make components configurable and reusable.

// Parent passes props function App() { return <UserCard name="Alice" role="Developer" active={true} />; } // Child receives props function UserCard(props) { return ( <div className={props.active ? 'active' : ''}> <h2>{props.name}</h2> <p>{props.role}</p> </div> ); }

Props destructuring

Props destructuring extracts specific properties from the props object directly in the function parameter or body, making code cleaner and more readable. It also allows for easy default value assignment and helps document which props a component expects.

// Destructure in parameter function UserCard({ name, role, active = false }) { return <div className={active ? 'active' : ''}>{name} - {role}</div>; } // Destructure in body function UserCard(props) { const { name, role, active = false } = props; return <div className={active ? 'active' : ''}>{name} - {role}</div>; } // Rest operator for remaining props function Button({ variant, ...rest }) { return <button className={variant} {...rest} />; }

Children prop

The children prop is a special prop that contains whatever content is passed between a component's opening and closing tags. It enables flexible component composition patterns like wrappers, layouts, and containers.

function Card({ title, children }) { return ( <div className="card"> <h2>{title}</h2> <div className="card-body"> {children} </div> </div> ); } // Usage <Card title="Welcome"> <p>This paragraph is passed as children</p> <button>Click me</button> </Card>

Default props

Default props provide fallback values for props when they are not supplied by the parent component. In function components, you typically use default parameter values or nullish coalescing for this purpose.

// Modern approach: Default parameters function Button({ text = "Click", variant = "primary", size = "medium" }) { return <button className={`btn-${variant} btn-${size}`}>{text}</button>; } // Legacy approach: defaultProps property Button.defaultProps = { text: "Click", variant: "primary", size: "medium" };

Prop types

Prop types define the expected types and requirements for component props, providing runtime validation during development. They serve as documentation and catch bugs by warning when props don't match their expected types.

import PropTypes from 'prop-types'; function UserCard({ name, age, email, onSave }) { return <div>{name} ({age})</div>; } UserCard.propTypes = { name: PropTypes.string.isRequired, age: PropTypes.number, email: PropTypes.string, onSave: PropTypes.func.isRequired };

PropTypes library

The prop-types library provides a range of validators for runtime type checking of props, including primitives, arrays, objects, custom shapes, and more. For production apps, TypeScript is often preferred for compile-time type safety.

import PropTypes from 'prop-types'; Component.propTypes = { // Primitives optionalString: PropTypes.string, requiredNumber: PropTypes.number.isRequired, // Complex types optionalArray: PropTypes.array, optionalObject: PropTypes.object, optionalFunc: PropTypes.func, // Specific shapes user: PropTypes.shape({ id: PropTypes.number.isRequired, name: PropTypes.string.isRequired, role: PropTypes.oneOf(['admin', 'user']) }), // Array of specific type items: PropTypes.arrayOf(PropTypes.string), // Any renderable content children: PropTypes.node };

State

State is mutable data managed within a component that, when changed, triggers a re-render to reflect the new state in the UI. Unlike props, state is private to the component and can only be updated by that component itself.

┌─────────────────────────────────────┐
│           Component                 │
│                                     │
│  Props (from parent) ───▶ Read-only │
│                                     │
│  State (internal) ─────▶ Mutable    │
│         │                           │
│         ▼                           │
│  setState() ───▶ Re-render          │
└─────────────────────────────────────┘

useState hook

The useState hook is a function that returns a state variable and its setter function, allowing function components to maintain local state. The setter can accept a new value directly or a function that receives the previous state.

import { useState } from 'react'; function Counter() { const [count, setCount] = useState(0); // number const [user, setUser] = useState(null); // null or object const [items, setItems] = useState([]); // array return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(count + 1)}>Increment</button> <button onClick={() => setCount(prev => prev + 1)}> Increment (functional) </button> </div> ); }

State updates

State updates in React are asynchronous and batched for performance; to update state based on previous state, always use the functional form of the setter. React will merge multiple state updates and trigger a single re-render.

function Counter() { const [count, setCount] = useState(0); const handleClick = () => { // ❌ Wrong: May use stale state setCount(count + 1); setCount(count + 1); // Still uses original count! // ✅ Correct: Uses latest state setCount(prev => prev + 1); setCount(prev => prev + 1); // Correctly adds 2 }; return <button onClick={handleClick}>{count}</button>; }

State immutability

React requires state to be treated as immutable; instead of mutating existing state, you must create new objects or arrays with the changes. This enables React to detect changes efficiently and is crucial for proper re-rendering.

function TodoList() { const [todos, setTodos] = useState([]); const [user, setUser] = useState({ name: '', age: 0 }); // ❌ Wrong: Mutating state directly const badAdd = (todo) => { todos.push(todo); // Mutation! setTodos(todos); // Same reference, no re-render }; // ✅ Correct: Create new array/object const addTodo = (todo) => setTodos([...todos, todo]); const removeTodo = (id) => setTodos(todos.filter(t => t.id !== id)); const updateUser = (name) => setUser({ ...user, name }); }

Event handling

React events use camelCase naming and pass functions (not strings) as event handlers, with events being SyntheticEvents that wrap native browser events. You can pass additional arguments using arrow functions or bind.

function Form() { const handleClick = (e) => { e.preventDefault(); console.log('Button clicked'); }; const handleInput = (e) => { console.log(e.target.value); }; const handleItemClick = (id, e) => { console.log(`Item ${id} clicked`); }; return ( <form onSubmit={handleClick}> <input onChange={handleInput} /> <button type="submit">Submit</button> {items.map(item => ( <div key={item.id} onClick={(e) => handleItemClick(item.id, e)}> {item.name} </div> ))} </form> ); }

Synthetic events

SyntheticEvent is React's cross-browser wrapper around the native event, providing a consistent API across all browsers. It has the same interface as native events but works identically across different browsers.

function EventDemo() { const handleClick = (e) => { console.log(e.type); // 'click' console.log(e.target); // DOM element console.log(e.currentTarget); // Element handler attached to console.log(e.nativeEvent); // Underlying native event e.preventDefault(); // Prevent default behavior e.stopPropagation(); // Stop event bubbling }; return <button onClick={handleClick}>Click</button>; } // Common events: onClick, onChange, onSubmit, onFocus, // onBlur, onKeyDown, onMouseEnter, etc.

Event binding

In class components, event handlers need to be bound to this context; in function components, this isn't necessary since arrow functions don't have their own this binding. There are several patterns for binding in class components.

// Class component binding patterns class Button extends React.Component { constructor(props) { super(props); // Pattern 1: Bind in constructor this.handleClick = this.handleClick.bind(this); } handleClick() { console.log(this.props); } // Pattern 2: Class field arrow function handleClick2 = () => { console.log(this.props); }; render() { return ( <> <button onClick={this.handleClick}>Bound</button> <button onClick={this.handleClick2}>Arrow</button> {/* Pattern 3: Inline arrow (creates new function each render) */} <button onClick={() => this.handleClick()}>Inline</button> </> ); } }

Conditional rendering

Conditional rendering in React uses JavaScript operators to decide which elements to render based on component state or props. Common patterns include ternary operators, logical AND (&&), and early returns.

function Dashboard({ isLoggedIn, isAdmin, notifications }) { // Early return if (!isLoggedIn) { return <LoginScreen />; } return ( <div> {/* Ternary operator */} {isAdmin ? <AdminPanel /> : <UserPanel />} {/* Logical AND (short-circuit) */} {notifications.length > 0 && ( <NotificationBadge count={notifications.length} /> )} {/* Nullish handling */} {user?.name ?? 'Anonymous'} </div> ); }

Lists and keys

Rendering lists in React uses JavaScript's map() method to transform arrays of data into arrays of elements, with each element requiring a unique key prop. Keys help React identify which items have changed, been added, or removed.

function TodoList({ todos }) { return ( <ul> {todos.map(todo => ( <li key={todo.id}> {/* Unique, stable key */} <span>{todo.text}</span> <span>{todo.completed ? '✓' : '○'}</span> </li> ))} </ul> ); } // ❌ Avoid: Using index as key (problematic for reordering) {items.map((item, index) => <Item key={index} {...item} />)} // ✅ Prefer: Stable unique identifier {items.map(item => <Item key={item.id} {...item} />)}

Key prop importance

Keys are crucial for React's reconciliation algorithm to efficiently update the DOM by identifying which elements changed. Using stable, unique keys prevents unnecessary re-renders and preserves component state correctly during list modifications.

Without proper keys (using index):
┌────────────────────────────────────────────────┐
│ Before: [A(0), B(1), C(2)]                     │
│ After:  [X(0), A(1), B(2), C(3)]              │
│ React thinks: A→X, B→A, C→B, +C (4 operations)│
└────────────────────────────────────────────────┘

With proper keys (using id):
┌────────────────────────────────────────────────┐
│ Before: [A(a), B(b), C(c)]                     │
│ After:  [X(x), A(a), B(b), C(c)]              │
│ React knows: Just insert X (1 operation)       │
└────────────────────────────────────────────────┘

Fragments (<> and <React.Fragment>)

Fragments let you group multiple elements without adding extra nodes to the DOM, solving the requirement that components must return a single root element. Use <>...</> shorthand or <React.Fragment> when you need to pass a key.

// Short syntax (most common) function Columns() { return ( <> <td>Column 1</td> <td>Column 2</td> </> ); } // Long syntax (needed for keys) function List({ items }) { return ( <dl> {items.map(item => ( <React.Fragment key={item.id}> <dt>{item.term}</dt> <dd>{item.description}</dd> </React.Fragment> ))} </dl> ); }

Rendering elements

Rendering is the process of React calling your components, computing the Virtual DOM, and updating the actual DOM with changes. The createRoot API mounts your React application to a DOM node, and React handles all subsequent updates automatically when state changes.

import { createRoot } from 'react-dom/client'; // Mount application const container = document.getElementById('root'); const root = createRoot(container); root.render(<App />); // React render cycle: // 1. State/props change // 2. Component function called // 3. New Virtual DOM created // 4. Diff with previous Virtual DOM // 5. Minimal DOM updates applied

React Hooks

Hooks overview

Hooks are functions that let you "hook into" React state and lifecycle features from function components, introduced in React 16.8. They enable code reuse, simplify component logic, and eliminate the need for class components in most cases.

┌─────────────────────────────────────────────────┐
│                  React Hooks                    │
├─────────────────────────────────────────────────┤
│ State:      useState, useReducer                │
│ Effects:    useEffect, useLayoutEffect          │
│ Context:    useContext                          │
│ Refs:       useRef, useImperativeHandle         │
│ Perf:       useMemo, useCallback                │
│ Misc:       useId, useTransition, etc.          │
└─────────────────────────────────────────────────┘

Rules of hooks

Hooks must follow two strict rules: only call hooks at the top level (not inside loops, conditions, or nested functions), and only call hooks from React function components or custom hooks. These rules ensure hooks are called in the same order every render.

function Component() { // ✅ Correct: Top level const [count, setCount] = useState(0); const [name, setName] = useState(''); // ❌ Wrong: Inside condition if (someCondition) { const [data, setData] = useState(null); // BREAKS! } // ❌ Wrong: Inside loop for (let i = 0; i < 5; i++) { useEffect(() => {}); // BREAKS! } // ❌ Wrong: Inside nested function const handleClick = () => { const [value, setValue] = useState(0); // BREAKS! }; }

useState

useState declares a state variable, returning the current value and a setter function; the initial value is only used on the first render. For expensive initial computations, pass a function (lazy initialization) to avoid running it on every render.

function Profile() { // Basic usage const [name, setName] = useState(''); const [age, setAge] = useState(0); // Object state const [user, setUser] = useState({ name: '', email: '' }); // Lazy initialization (expensive computation) const [data, setData] = useState(() => { return JSON.parse(localStorage.getItem('data')) || []; }); // Update object (must spread) const updateEmail = (email) => { setUser(prev => ({ ...prev, email })); }; return <input value={name} onChange={e => setName(e.target.value)} />; }

useEffect

useEffect performs side effects in function components, running after render and optionally after specific dependencies change. It replaces componentDidMount, componentDidUpdate, and componentWillUnmount lifecycle methods from class components.

function UserProfile({ userId }) { const [user, setUser] = useState(null); useEffect(() => { // Side effect: fetch data async function fetchUser() { const response = await fetch(`/api/users/${userId}`); const data = await response.json(); setUser(data); } fetchUser(); }, [userId]); // Re-run when userId changes return user ? <div>{user.name}</div> : <div>Loading...</div>; }

useEffect cleanup

The cleanup function returned from useEffect runs before the component unmounts and before the effect re-runs due to dependency changes. It's essential for preventing memory leaks from subscriptions, timers, or ongoing async operations.

function ChatRoom({ roomId }) { useEffect(() => { const connection = createConnection(roomId); connection.connect(); // Cleanup function return () => { connection.disconnect(); // Runs on unmount or before re-run }; }, [roomId]); // Timer example useEffect(() => { const intervalId = setInterval(() => { console.log('Tick'); }, 1000); return () => clearInterval(intervalId); // Clear timer }, []); }

useEffect dependencies

The dependency array controls when the effect re-runs: empty array [] means run once on mount, omitted array means run after every render, and specific dependencies trigger re-runs when those values change. React compares dependencies using Object.is.

function Component({ id, filter }) { // Runs once on mount useEffect(() => { console.log('Mounted'); }, []); // Runs when id OR filter changes useEffect(() => { fetchData(id, filter); }, [id, filter]); // Runs after EVERY render (usually a bug) useEffect(() => { console.log('Rendered'); }); // ⚠️ Missing dependency warning! useEffect(() => { doSomething(id); // Should include 'id' in deps }, []); }

useContext

useContext subscribes to a React context and returns its current value, eliminating the need for the Consumer component pattern. The component will re-render whenever the context value changes.

const ThemeContext = createContext('light'); function App() { const [theme, setTheme] = useState('dark'); return ( <ThemeContext.Provider value={theme}> <Toolbar /> </ThemeContext.Provider> ); } function Toolbar() { return <ThemedButton />; } function ThemedButton() { const theme = useContext(ThemeContext); // 'dark' return <button className={theme}>Themed Button</button>; }

useReducer

useReducer manages complex state logic through a reducer function, similar to Redux patterns, and is preferable over useState when state logic involves multiple sub-values or the next state depends on the previous one.

const initialState = { count: 0 }; function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; case 'reset': return initialState; default: throw new Error('Unknown action'); } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count} <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> <button onClick={() => dispatch({ type: 'reset' })}>Reset</button> </> ); }

useCallback

useCallback memoizes a callback function, returning the same function reference between renders unless dependencies change. It's useful for preventing unnecessary re-renders of child components that rely on referential equality.

function Parent({ items }) { const [count, setCount] = useState(0); // Without useCallback: new function every render // Child would re-render even if items didn't change const handleClick = useCallback((id) => { console.log(`Clicked item ${id}`); }, []); // Never changes const handleDelete = useCallback((id) => { deleteItem(id); setCount(c => c + 1); }, []); // Doesn't need count due to functional update return ( <> <ItemList items={items} onClick={handleClick} /> <span>Deleted: {count}</span> </> ); } const ItemList = React.memo(({ items, onClick }) => { return items.map(item => ( <div key={item.id} onClick={() => onClick(item.id)}>{item.name}</div> )); });

useMemo

useMemo memoizes the result of an expensive computation, only recalculating when dependencies change. Use it to avoid expensive recalculations on every render, not as a premature optimization.

function ProductList({ products, filterTerm }) { // Expensive filtering only runs when products or filterTerm change const filteredProducts = useMemo(() => { console.log('Filtering...'); return products.filter(p => p.name.toLowerCase().includes(filterTerm.toLowerCase()) ); }, [products, filterTerm]); // Expensive calculation const stats = useMemo(() => { return { total: filteredProducts.length, avgPrice: filteredProducts.reduce((a, p) => a + p.price, 0) / filteredProducts.length }; }, [filteredProducts]); return ( <div> <p>Showing {stats.total} products, avg price: ${stats.avgPrice}</p> {filteredProducts.map(p => <Product key={p.id} {...p} />)} </div> ); }

useRef

useRef returns a mutable ref object whose .current property persists across renders without causing re-renders when changed. It's commonly used for accessing DOM elements and storing mutable values that don't need to trigger updates.

function TextInput() { const inputRef = useRef(null); const renderCount = useRef(0); // DOM access const focusInput = () => { inputRef.current.focus(); }; // Track value without re-render useEffect(() => { renderCount.current += 1; console.log(`Rendered ${renderCount.current} times`); }); return ( <> <input ref={inputRef} /> <button onClick={focusInput}>Focus</button> </> ); }

useImperativeHandle

useImperativeHandle customizes the instance value exposed to parent components when using ref, used together with forwardRef. It limits what the parent can access, encapsulating the component's internal implementation.

const FancyInput = forwardRef((props, ref) => { const inputRef = useRef(); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current.focus(); }, clear: () => { inputRef.current.value = ''; }, // Parent can't access inputRef.current directly }), []); return <input ref={inputRef} {...props} />; }); function Parent() { const fancyRef = useRef(); return ( <> <FancyInput ref={fancyRef} /> <button onClick={() => fancyRef.current.focus()}>Focus</button> <button onClick={() => fancyRef.current.clear()}>Clear</button> </> ); }

useLayoutEffect

useLayoutEffect fires synchronously after all DOM mutations but before the browser paints, allowing you to read layout and synchronously re-render. Use it for DOM measurements or when you need to prevent visual flicker that useEffect might cause.

function Tooltip({ targetRef, children }) { const tooltipRef = useRef(); const [position, setPosition] = useState({ top: 0, left: 0 }); // Runs BEFORE paint - prevents flicker useLayoutEffect(() => { const targetRect = targetRef.current.getBoundingClientRect(); const tooltipRect = tooltipRef.current.getBoundingClientRect(); setPosition({ top: targetRect.bottom + 10, left: targetRect.left + (targetRect.width - tooltipRect.width) / 2 }); }, [targetRef]); return ( <div ref={tooltipRef} style={{ position: 'absolute', ...position }}> {children} </div> ); }

useDebugValue

useDebugValue displays a label for custom hooks in React DevTools, helping with debugging. It only runs when DevTools are open and should only be used in custom hooks.

function useOnlineStatus() { const [isOnline, setIsOnline] = useState(navigator.onLine); // Shows in React DevTools: "OnlineStatus: Online" or "OnlineStatus: Offline" useDebugValue(isOnline ? 'Online' : 'Offline'); useEffect(() => { const handleOnline = () => setIsOnline(true); const handleOffline = () => setIsOnline(false); window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); }; }, []); return isOnline; } // With formatting function (deferred until DevTools inspects) useDebugValue(date, date => date.toDateString());

useId

useId generates a unique ID that is stable across server and client renders, perfect for accessibility attributes and form labels. It should not be used for keys in lists.

function FormField({ label }) { const id = useId(); return ( <div> <label htmlFor={id}>{label}</label> <input id={id} type="text" /> </div> ); } function PasswordField() { const id = useId(); return ( <> <label htmlFor={`${id}-password`}>Password</label> <input id={`${id}-password`} type="password" aria-describedby={`${id}-hint`} /> <p id={`${id}-hint`}>Must be at least 8 characters</p> </> ); }

useTransition

useTransition marks state updates as non-urgent transitions, allowing React to keep the UI responsive by interrupting the transition to handle more urgent updates. It returns a pending flag and a function to wrap the low-priority update.

function SearchResults({ query }) { const [isPending, startTransition] = useTransition(); const [results, setResults] = useState([]); const handleSearch = (newQuery) => { // Urgent: update input immediately setQuery(newQuery); // Non-urgent: can be interrupted startTransition(() => { const filtered = heavyFilterOperation(data, newQuery); setResults(filtered); }); }; return ( <div> <input onChange={e => handleSearch(e.target.value)} /> {isPending && <Spinner />} <ResultsList results={results} style={{ opacity: isPending ? 0.7 : 1 }} /> </div> ); }

useDeferredValue

useDeferredValue defers re-rendering of a non-urgent part of the UI, accepting a value and returning a "lagged" version that can fall behind the latest value during urgent updates. It's similar to debouncing but integrated with React's rendering.

function SearchPage() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); const isStale = query !== deferredQuery; return ( <div> <input value={query} onChange={e => setQuery(e.target.value)} placeholder="Search..." /> <Suspense fallback={<Loading />}> <SearchResults query={deferredQuery} style={{ opacity: isStale ? 0.5 : 1 }} /> </Suspense> </div> ); } // The input stays responsive while results lag behind

useSyncExternalStore

useSyncExternalStore subscribes to external stores (like Redux or browser APIs) in a way that's compatible with concurrent rendering. It ensures the component reads a consistent snapshot of the external store.

// Subscribe to browser online/offline status function useOnlineStatus() { return useSyncExternalStore( // subscribe (callback) => { window.addEventListener('online', callback); window.addEventListener('offline', callback); return () => { window.removeEventListener('online', callback); window.removeEventListener('offline', callback); }; }, // getSnapshot (client) () => navigator.onLine, // getServerSnapshot () => true ); } function StatusBar() { const isOnline = useOnlineStatus(); return <div>{isOnline ? '✅ Online' : '❌ Disconnected'}</div>; }

useInsertionEffect

useInsertionEffect fires before any DOM mutations, designed specifically for CSS-in-JS libraries to inject styles before layout effects read them. It's a very specialized hook and should not be used in application code.

// Used internally by CSS-in-JS libraries like styled-components function useCSS(rule) { useInsertionEffect(() => { const style = document.createElement('style'); style.innerHTML = rule; document.head.appendChild(style); return () => document.head.removeChild(style); }, [rule]); } // Execution order: // 1. useInsertionEffect - inject styles // 2. DOM mutations // 3. useLayoutEffect - read layout // 4. Browser paint // 5. useEffect

Custom hooks

Custom hooks are JavaScript functions starting with "use" that can call other hooks, enabling you to extract and reuse stateful logic between components. They follow the same rules as built-in hooks and can encapsulate complex behavior.

function useLocalStorage(key, initialValue) { const [value, setValue] = useState(() => { const stored = localStorage.getItem(key); return stored ? JSON.parse(stored) : initialValue; }); useEffect(() => { localStorage.setItem(key, JSON.stringify(value)); }, [key, value]); return [value, setValue]; } 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(setData) .catch(setError) .finally(() => setLoading(false)); }, [url]); return { data, loading, error }; }

Custom hook patterns

Common custom hook patterns include data fetching, form handling, subscriptions, toggle states, and debouncing. Well-designed custom hooks abstract complex logic and return a simple, intuitive API.

// Toggle pattern function useToggle(initial = false) { const [value, setValue] = useState(initial); const toggle = useCallback(() => setValue(v => !v), []); return [value, toggle]; } // Form pattern function useForm(initialValues) { const [values, setValues] = useState(initialValues); const handleChange = (e) => { const { name, value } = e.target; setValues(prev => ({ ...prev, [name]: value })); }; const reset = () => setValues(initialValues); return { values, handleChange, reset }; } // Debounce pattern function useDebounce(value, delay) { const [debounced, setDebounced] = useState(value); useEffect(() => { const timer = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(timer); }, [value, delay]); return debounced; }

Hooks best practices

Follow these best practices: keep hooks at the top level, extract complex logic into custom hooks, include all dependencies in effect arrays, use functional updates for state depending on previous state, and clean up effects properly to prevent memory leaks.

// ✅ Good practices function UserProfile({ userId }) { // 1. All hooks at top const [user, setUser] = useState(null); const mounted = useRef(true); // 2. Complete dependency array useEffect(() => { async function fetchUser() { const data = await api.getUser(userId); // 3. Check mounted before setting state if (mounted.current) setUser(data); } fetchUser(); // 4. Cleanup return () => { mounted.current = false; }; }, [userId]); // All deps listed // 5. Functional updates for prev state const incrementAge = () => setUser(prev => ({ ...prev, age: prev.age + 1 })); // 6. Extract complex logic const { data, loading } = useFetch(`/api/posts?userId=${userId}`); }

Component Lifecycle (Class Components)

Mounting phase

The mounting phase occurs when a component instance is being created and inserted into the DOM, involving constructor, static getDerivedStateFromProps, render, and componentDidMount in that order. This phase happens only once during a component's life.

┌─────────────────────────────────────────────┐
│              MOUNTING PHASE                 │
├─────────────────────────────────────────────┤
│  1. constructor(props)                      │
│     └─▶ Initialize state, bind methods      │
│                                             │
│  2. static getDerivedStateFromProps()       │
│     └─▶ Sync state to props (rare)          │
│                                             │
│  3. render()                                │
│     └─▶ Return JSX                          │
│                                             │
│  4. componentDidMount()                     │
│     └─▶ DOM ready, fetch data, subscribe    │
└─────────────────────────────────────────────┘

componentDidMount

componentDidMount is called immediately after a component is mounted (inserted into the DOM tree), making it the ideal place for network requests, subscriptions, and DOM manipulations. It runs only once after the first render.

class UserProfile extends React.Component { state = { user: null, loading: true }; componentDidMount() { // API calls fetch(`/api/users/${this.props.userId}`) .then(res => res.json()) .then(user => this.setState({ user, loading: false })); // Subscriptions this.subscription = eventBus.subscribe('update', this.handleUpdate); // DOM manipulation this.inputRef.current.focus(); // Timers this.timer = setInterval(this.tick, 1000); } componentWillUnmount() { this.subscription.unsubscribe(); clearInterval(this.timer); } }

Updating phase

The updating phase occurs when a component is being re-rendered due to changes in props or state, involving getDerivedStateFromProps, shouldComponentUpdate, render, getSnapshotBeforeUpdate, and componentDidUpdate. This phase can happen many times.

┌─────────────────────────────────────────────┐
│              UPDATING PHASE                 │
├─────────────────────────────────────────────┤
│  Trigger: New props, setState, forceUpdate  │
│                                             │
│  1. static getDerivedStateFromProps()       │
│  2. shouldComponentUpdate() ─▶ true/false   │
│  3. render()                                │
│  4. getSnapshotBeforeUpdate()               │
│  5. componentDidUpdate(prevProps, prevState)│
└─────────────────────────────────────────────┘

componentDidUpdate

componentDidUpdate is called immediately after updating occurs (not for the initial render), allowing you to operate on the DOM or perform network requests based on prop/state changes. Always compare previous and current props/state to avoid infinite loops.

class UserProfile extends React.Component { componentDidUpdate(prevProps, prevState, snapshot) { // Compare props to avoid infinite loop if (this.props.userId !== prevProps.userId) { this.fetchUser(this.props.userId); } // Compare state if (this.state.count !== prevState.count) { document.title = `Count: ${this.state.count}`; } // Use snapshot from getSnapshotBeforeUpdate if (snapshot !== null) { this.listRef.current.scrollTop = this.listRef.current.scrollHeight - snapshot; } } }

Unmounting phase

The unmounting phase occurs when a component is being removed from the DOM, with componentWillUnmount being the only lifecycle method in this phase. It's crucial for cleanup operations to prevent memory leaks.

┌─────────────────────────────────────────────┐
│            UNMOUNTING PHASE                 │
├─────────────────────────────────────────────┤
│                                             │
│  componentWillUnmount()                     │
│     └─▶ Cleanup before removal              │
│         - Cancel network requests           │
│         - Remove event listeners            │
│         - Clear timers                      │
│         - Unsubscribe from stores           │
│         - Invalidate caches                 │
│                                             │
└─────────────────────────────────────────────┘

componentWillUnmount

componentWillUnmount is invoked immediately before a component is unmounted and destroyed, used for cleanup such as canceling network requests, removing event listeners, clearing timers, and unsubscribing from external sources.

class LiveFeed extends React.Component { componentDidMount() { this.socket = new WebSocket('ws://api.example.com'); this.socket.onmessage = this.handleMessage; this.timer = setInterval(this.poll, 5000); window.addEventListener('resize', this.handleResize); } componentWillUnmount() { // Close socket this.socket.close(); // Clear timer clearInterval(this.timer); // Remove event listeners window.removeEventListener('resize', this.handleResize); // Cancel pending requests this.abortController?.abort(); } }

Error handling

React provides error boundary lifecycle methods to catch JavaScript errors in child component trees, log errors, and display fallback UIs. Error boundaries catch errors during rendering, in lifecycle methods, and in constructors of child components.

class ErrorBoundary extends React.Component { state = { hasError: false, error: null }; static getDerivedStateFromError(error) { // Update state to show fallback UI return { hasError: true, error }; } componentDidCatch(error, errorInfo) { // Log error to service logErrorToService(error, errorInfo.componentStack); } render() { if (this.state.hasError) { return <h1>Something went wrong.</h1>; } return this.props.children; } } // Usage <ErrorBoundary> <MyComponent /> </ErrorBoundary>

componentDidCatch

componentDidCatch is called when an error is thrown by a descendant component, receiving the error and info about which component threw it. It's used for logging errors to external services and is called during the commit phase.

class ErrorBoundary extends React.Component { state = { hasError: false }; componentDidCatch(error, errorInfo) { // error: The thrown error console.error('Error:', error.message); // errorInfo.componentStack: Component stack trace console.error('Stack:', errorInfo.componentStack); // Log to error tracking service errorTrackingService.log({ error: error.toString(), stack: errorInfo.componentStack, user: getCurrentUser(), url: window.location.href }); } }

getDerivedStateFromError

getDerivedStateFromError is a static lifecycle method called during the render phase after a descendant throws an error, returning an object to update state. It's used to render a fallback UI and must be pure (no side effects).

class ErrorBoundary extends React.Component { state = { hasError: false, errorMessage: '' }; static getDerivedStateFromError(error) { // Called during render phase // Return new state based on error return { hasError: true, errorMessage: error.message }; } render() { if (this.state.hasError) { return ( <div className="error-boundary"> <h2>Oops! Something went wrong</h2> <details> <summary>Error details</summary> <p>{this.state.errorMessage}</p> </details> <button onClick={() => this.setState({ hasError: false })}> Try again </button> </div> ); } return this.props.children; } }

shouldComponentUpdate (legacy)

shouldComponentUpdate lets you skip re-renders by returning false when props/state changes don't affect the output, serving as a performance optimization. It's largely replaced by React.PureComponent or React.memo.

class ExpensiveComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) { // Only re-render if these specific values change return ( nextProps.data.id !== this.props.data.id || nextState.isExpanded !== this.state.isExpanded ); } render() { return <div>{/* expensive rendering */}</div>; } } // Modern alternative: PureComponent (shallow comparison) class MyComponent extends React.PureComponent { render() { return <div>{this.props.name}</div>; } } // Function component alternative const MyComponent = React.memo(({ name }) => { return <div>{name}</div>; });

getSnapshotBeforeUpdate (legacy)

getSnapshotBeforeUpdate is called right before DOM changes are applied, enabling you to capture information (like scroll position) that might change. The returned value is passed to componentDidUpdate as the third parameter.

class MessageList extends React.Component { listRef = React.createRef(); getSnapshotBeforeUpdate(prevProps, prevState) { // Capture scroll position before DOM updates if (prevProps.messages.length < this.props.messages.length) { const list = this.listRef.current; return list.scrollHeight - list.scrollTop; } return null; } componentDidUpdate(prevProps, prevState, snapshot) { // Restore scroll position after new messages if (snapshot !== null) { const list = this.listRef.current; list.scrollTop = list.scrollHeight - snapshot; } } render() { return ( <ul ref={this.listRef}> {this.props.messages.map(msg => <li key={msg.id}>{msg.text}</li>)} </ul> ); } }

Advanced Components

Higher-Order Components (HOC)

A Higher-Order Component is a function that takes a component and returns a new enhanced component with additional props or behavior. HOCs are a pattern for reusing component logic, though hooks have largely replaced their use cases.

// HOC that adds loading state function withLoading(WrappedComponent) { return function WithLoadingComponent({ isLoading, ...props }) { if (isLoading) { return <div>Loading...</div>; } return <WrappedComponent {...props} />; }; } // HOC that adds authentication function withAuth(WrappedComponent) { return function WithAuthComponent(props) { const { user } = useAuth(); if (!user) { return <Navigate to="/login" />; } return <WrappedComponent {...props} user={user} />; }; } // Usage const ProtectedDashboard = withAuth(withLoading(Dashboard));

HOC patterns

Common HOC patterns include props proxy (manipulating props), inheritance inversion (extending the wrapped component), and composition chaining. HOCs should be pure, pass through unrelated props, and wrap the display name for debugging.

// Props proxy pattern function withLogger(WrappedComponent) { return function(props) { console.log('Props:', props); return <WrappedComponent {...props} />; }; } // Inject props pattern function withTheme(WrappedComponent) { return function(props) { const theme = useContext(ThemeContext); return <WrappedComponent {...props} theme={theme} />; }; } // Display name for DevTools function withSubscription(WrappedComponent) { function WithSubscription(props) { /* ... */ } WithSubscription.displayName = `WithSubscription(${WrappedComponent.displayName || WrappedComponent.name})`; return WithSubscription; } // Compose multiple HOCs const enhance = compose(withAuth, withTheme, withLogger); const EnhancedComponent = enhance(MyComponent);

Render props

Render props is a pattern where a component receives a function as a prop (often called render or children) that returns React elements, enabling dynamic composition and code sharing between components. Hooks have largely replaced this pattern.

// Render prop component function MouseTracker({ render }) { const [position, setPosition] = useState({ x: 0, y: 0 }); useEffect(() => { const handleMove = (e) => setPosition({ x: e.clientX, y: e.clientY }); window.addEventListener('mousemove', handleMove); return () => window.removeEventListener('mousemove', handleMove); }, []); return render(position); } // Usage with render prop <MouseTracker render={({ x, y }) => ( <div>Mouse: {x}, {y}</div> )} /> // Usage with children as function <MouseTracker> {({ x, y }) => <div>Mouse: {x}, {y}</div>} </MouseTracker>

Component composition patterns

Component composition patterns organize how components work together, including container/presentational separation, compound components, render props, and the slot pattern. Good composition leads to flexible, maintainable, and reusable components.

// Container/Presentational function UserListContainer() { const users = useFetch('/api/users'); return <UserList users={users} />; } function UserList({ users }) { return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>; } // Slot pattern function Layout({ header, sidebar, children }) { return ( <div className="layout"> <header>{header}</header> <aside>{sidebar}</aside> <main>{children}</main> </div> ); } <Layout header={<Nav />} sidebar={<Menu />} > <Content /> </Layout>

Compound components

Compound components are a pattern where multiple components work together to accomplish a task, sharing implicit state while providing a flexible, declarative API. Think of <select> and <option> as a native HTML example.

const TabContext = createContext(); function Tabs({ children, defaultTab }) { const [activeTab, setActiveTab] = useState(defaultTab); return ( <TabContext.Provider value={{ activeTab, setActiveTab }}> <div className="tabs">{children}</div> </TabContext.Provider> ); } Tabs.List = function TabList({ children }) { return <div className="tab-list">{children}</div>; }; Tabs.Tab = function Tab({ id, children }) { const { activeTab, setActiveTab } = useContext(TabContext); return ( <button className={activeTab === id ? 'active' : ''} onClick={() => setActiveTab(id)} > {children} </button> ); }; Tabs.Panel = function TabPanel({ id, children }) { const { activeTab } = useContext(TabContext); return activeTab === id ? <div>{children}</div> : null; }; // Usage <Tabs defaultTab="one"> <Tabs.List> <Tabs.Tab id="one">Tab 1</Tabs.Tab> <Tabs.Tab id="two">Tab 2</Tabs.Tab> </Tabs.List> <Tabs.Panel id="one">Content 1</Tabs.Panel> <Tabs.Panel id="two">Content 2</Tabs.Panel> </Tabs>

Controlled components

Controlled components have their form data controlled by React state, where the component's value is always driven by state and changes are handled through event callbacks. This gives React full control over the form element.

function ControlledForm() { const [values, setValues] = useState({ name: '', email: '', agree: false }); const handleChange = (e) => { const { name, value, type, checked } = e.target; setValues(prev => ({ ...prev, [name]: type === 'checkbox' ? checked : value })); }; return ( <form> <input name="name" value={values.name} onChange={handleChange} /> <input name="email" value={values.email} onChange={handleChange} /> <input type="checkbox" name="agree" checked={values.agree} onChange={handleChange} /> </form> ); }

Uncontrolled components

Uncontrolled components store form data in the DOM itself, accessed via refs when needed, similar to traditional HTML forms. They're simpler for basic cases but offer less control over input behavior and validation.

function UncontrolledForm() { const nameRef = useRef(); const emailRef = useRef(); const fileRef = useRef(); const handleSubmit = (e) => { e.preventDefault(); console.log({ name: nameRef.current.value, email: emailRef.current.value, file: fileRef.current.files[0] }); }; return ( <form onSubmit={handleSubmit}> {/* defaultValue for initial value */} <input ref={nameRef} defaultValue="John" /> <input ref={emailRef} type="email" /> {/* File inputs must be uncontrolled */} <input ref={fileRef} type="file" /> <button type="submit">Submit</button> </form> ); }

Refs

Refs provide a way to access DOM nodes or React elements created in the render method directly, bypassing the typical data flow. They're useful for focus management, animations, integrating with third-party DOM libraries, and triggering imperative actions.

function Component() { // Create ref const inputRef = useRef(null); const videoRef = useRef(null); const focusInput = () => inputRef.current.focus(); const playVideo = () => videoRef.current.play(); return ( <div> <input ref={inputRef} /> <button onClick={focusInput}>Focus</button> <video ref={videoRef} src="movie.mp4" /> <button onClick={playVideo}>Play</button> </div> ); }

Ref forwarding

Ref forwarding is a technique for passing a ref through a component to one of its children, enabling parent components to access the DOM node of a child component. It's done using React.forwardRef().

// Forward ref to inner input const FancyInput = forwardRef((props, ref) => { return ( <input ref={ref} className="fancy-input" {...props} /> ); }); // Usage: parent can access the input DOM node function Form() { const inputRef = useRef(); useEffect(() => { inputRef.current.focus(); // Works! }, []); return <FancyInput ref={inputRef} placeholder="Enter text" />; } // With displayName for DevTools FancyInput.displayName = 'FancyInput';

Callback refs

Callback refs use a function instead of a ref object, called with the DOM element when mounted and with null when unmounted. They provide more control over when refs are set and unset, useful for measuring elements or handling dynamic refs.

function MeasuredComponent() { const [height, setHeight] = useState(0); // Callback ref const measuredRef = useCallback((node) => { if (node !== null) { setHeight(node.getBoundingClientRect().height); } }, []); return ( <div ref={measuredRef}> Content height: {height}px </div> ); } // Dynamic refs for list items function List({ items }) { const refs = useRef({}); const scrollToItem = (id) => { refs.current[id]?.scrollIntoView(); }; return items.map(item => ( <div key={item.id} ref={el => refs.current[item.id] = el}> {item.text} </div> )); }

createRef (class components)

React.createRef() creates a ref object for class components, typically assigned in the constructor. For function components, use useRef instead as createRef creates a new ref on every render.

class TextInput extends React.Component { constructor(props) { super(props); this.inputRef = React.createRef(); this.buttonRef = React.createRef(); } componentDidMount() { this.inputRef.current.focus(); } handleClick = () => { this.inputRef.current.select(); }; render() { return ( <div> <input ref={this.inputRef} type="text" /> <button ref={this.buttonRef} onClick={this.handleClick}> Select All </button> </div> ); } }

useRef for DOM access

useRef can hold a reference to a DOM element, enabling direct DOM manipulation for focus, scroll, measurements, or third-party library integration. The ref is attached via the ref attribute on a JSX element.

function ImageGallery() { const scrollContainerRef = useRef(); const canvasRef = useRef(); const scroll = (direction) => { scrollContainerRef.current.scrollBy({ left: direction * 200, behavior: 'smooth' }); }; useEffect(() => { const ctx = canvasRef.current.getContext('2d'); ctx.fillStyle = 'blue'; ctx.fillRect(0, 0, 100, 100); }, []); return ( <> <div ref={scrollContainerRef} style={{ overflow: 'auto' }}> {images.map(img => <img key={img.id} src={img.src} />)} </div> <button onClick={() => scroll(-1)}></button> <button onClick={() => scroll(1)}></button> <canvas ref={canvasRef} width={400} height={300} /> </> ); }

useRef for mutable values

useRef can store any mutable value that persists across renders without triggering re-renders when changed, unlike state. It's perfect for storing previous values, interval IDs, or any instance-like data in function components.

function Timer() { const [count, setCount] = useState(0); const intervalRef = useRef(null); const prevCountRef = useRef(); // Store previous value useEffect(() => { prevCountRef.current = count; }); const prevCount = prevCountRef.current; const startTimer = () => { intervalRef.current = setInterval(() => { setCount(c => c + 1); }, 1000); }; const stopTimer = () => { clearInterval(intervalRef.current); }; return ( <div> <p>Now: {count}, Before: {prevCount}</p> <button onClick={startTimer}>Start</button> <button onClick={stopTimer}>Stop</button> </div> ); }

Portals

Portals provide a way to render children into a DOM node that exists outside the parent component's DOM hierarchy, useful for modals, tooltips, and dropdowns that need to break out of parent containers with overflow: hidden or z-index constraints.

import { createPortal } from 'react-dom'; function Modal({ isOpen, onClose, children }) { if (!isOpen) return null; return createPortal( <div className="modal-overlay" onClick={onClose}> <div className="modal-content" onClick={e => e.stopPropagation()}> {children} <button onClick={onClose}>Close</button> </div> </div>, document.getElementById('modal-root') // Outside #root ); } // HTML structure: // <body> // <div id="root">...</div> // <div id="modal-root"></div> <!-- Portal target --> // </body>

Error boundaries

Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log errors, and display a fallback UI. They must be class components and catch errors during rendering, lifecycle methods, and constructors.

class ErrorBoundary extends React.Component { state = { hasError: false, error: null }; static getDerivedStateFromError(error) { return { hasError: true, error }; } componentDidCatch(error, errorInfo) { logToService(error, errorInfo); } render() { if (this.state.hasError) { return this.props.fallback || <h1>Something went wrong.</h1>; } return this.props.children; } } // Usage with different fallbacks <ErrorBoundary fallback={<ErrorPage />}> <Routes /> </ErrorBoundary> <ErrorBoundary fallback={<p>Widget failed to load</p>}> <Widget /> </ErrorBoundary>

Strict Mode

<StrictMode> is a development tool that helps identify potential problems in an application by activating additional checks and warnings. It double-invokes certain functions to detect side effects and warns about deprecated APIs.

import { StrictMode } from 'react'; createRoot(document.getElementById('root')).render( <StrictMode> <App /> </StrictMode> ); // StrictMode effects (development only): // ✓ Double-renders components to detect impure renders // ✓ Double-runs effects to catch missing cleanup // ✓ Warns about deprecated lifecycle methods // ✓ Warns about legacy string refs // ✓ Detects unexpected side effects // ✓ No impact on production build

Profiler API

The Profiler API measures how often a component renders and what the "cost" of rendering is, helping identify performance bottlenecks. It can be used declaratively in JSX or programmatically.

import { Profiler } from 'react'; function onRenderCallback( id, // "Navigation" phase, // "mount" | "update" actualDuration, // Time spent rendering baseDuration, // Estimated time without memoization startTime, // When React started rendering commitTime // When React committed this update ) { console.log(`${id} [${phase}]: ${actualDuration.toFixed(2)}ms`); } function App() { return ( <Profiler id="Navigation" onRender={onRenderCallback}> <Navigation /> </Profiler> <Profiler id="Main" onRender={onRenderCallback}> <Main /> </Profiler> ); }

Styling in React

Inline Styles

Inline styles use a JavaScript object with camelCased properties instead of CSS strings; they're scoped to the element but lack pseudo-classes, media queries, and can clutter JSX.

<div style={{ backgroundColor: 'blue', fontSize: '16px', marginTop: 10 }}> Styled content </div>

CSS Stylesheets

Traditional CSS files imported into React components apply styles globally; simple and familiar but can cause naming collisions and specificity issues in larger applications.

import './Button.css'; <button className="btn btn-primary">Click</button>

CSS Modules

CSS Modules automatically generate unique class names at build time, providing local scope by default; you import styles as an object and access classes as properties.

import styles from './Button.module.css'; <button className={styles.primary}>Click</button> // Compiles to: class="Button_primary_x7f2a"

CSS-in-JS

CSS-in-JS is a pattern where CSS is written in JavaScript, enabling dynamic styles based on props, automatic scoping, and co-location of styles with components; examples include styled-components and Emotion.

┌─────────────────────────────────────┐
│  Component.jsx                      │
│  ┌─────────────┐ ┌────────────────┐ │
│  │   Logic     │ │   Styles       │ │
│  │   (JS)      │ │   (CSS-in-JS)  │ │
│  └─────────────┘ └────────────────┘ │
└─────────────────────────────────────┘

Styled-components

Styled-components uses tagged template literals to create React components with styles attached; it supports theming, props-based styling, and automatic vendor prefixing.

import styled from 'styled-components'; const Button = styled.button` background: ${props => props.primary ? 'blue' : 'gray'}; padding: 10px 20px; &:hover { opacity: 0.8; } `; <Button primary>Click</Button>

Emotion

Emotion is a performant CSS-in-JS library offering both styled-components API and a css prop approach; it's highly flexible with excellent runtime performance and SSR support.

/** @jsxImportSource @emotion/react */ import { css } from '@emotion/react'; <div css={css`color: hotpink; font-size: 24px;`}>Styled</div> // Or with styled API import styled from '@emotion/styled'; const Box = styled.div`padding: 20px;`;

Style Props

Style props pass styling values directly as component props, commonly used in component libraries like Chakra UI; they enable quick inline customization with type safety and theme consistency.

// Chakra UI example <Box bg="blue.500" p={4} borderRadius="md" color="white"> Content </Box>

className Management

Managing multiple conditional classNames can get messy; organizing them properly is crucial for readable, maintainable code, especially when combining static and dynamic classes.

<div className={`card ${isActive ? 'active' : ''} ${size}`}> // Gets messy quickly with multiple conditions

classnames/clsx Libraries

classnames and clsx are tiny utilities for conditionally joining classNames together; clsx is smaller and faster, both eliminate messy string concatenation.

import clsx from 'clsx'; <div className={clsx('btn', { 'btn-active': isActive, 'btn-large': size === 'lg' })}> // Output: "btn btn-active" (if isActive is true)

Tailwind with React

Tailwind CSS is a utility-first framework that works excellently with React; you apply utility classes directly in JSX, and unused styles are purged in production for tiny bundles.

<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"> Click me </button>

Bootstrap with React

Use react-bootstrap or reactstrap for Bootstrap components as proper React components with props instead of jQuery; alternatively, use Bootstrap CSS with manual className application.

import { Button, Modal } from 'react-bootstrap'; <Button variant="primary" onClick={handleShow}> Launch Modal </Button>

CSS Modules with SASS/SCSS

Combine CSS Modules' local scoping with SASS/SCSS features like variables, nesting, and mixins; configure your bundler to handle .module.scss files for the best of both worlds.

// Button.module.scss @import 'variables'; .button { background: $primary-color; &:hover { background: darken($primary-color, 10%); } } // Component import styles from './Button.module.scss';