React — Complete Guide
React is a declarative, component-based JavaScript library for building user interfaces. Maintained by Meta (Facebook), it allows developers to build complex UIs from small, isolated pieces of code called components. React handles the rendering to the DOM, so you can focus on the state and logic of your application.
Since its release in 2013, React has become the most widely used frontend library in the world. It powers Instagram, Netflix, Airbnb, Uber, and thousands of production applications. Its ecosystem includes Next.js for server-rendered React, React Native for mobile apps, and an extensive library of community tools.
This guide covers React comprehensively — from writing your first component to mastering advanced patterns like compound components, render props, and performance optimization with memoization. Each section is self-contained with real code examples.
React is built on a few foundational ideas that make it powerful and predictable:
JSX — JavaScript XML
JSX is a syntax extension that lets you write HTML-like code inside JavaScript. It compiles to React.createElement() calls. JSX makes component trees readable and expressive.
| 1 | // JSX compiles to React.createElement() calls |
| 2 | // This JSX: |
| 3 | const element = <h1 className="title">Hello, {name}!</h1>; |
| 4 | |
| 5 | // Compiles to: |
| 6 | const element = React.createElement("h1", { className: "title" }, "Hello, ", name, "!"); |
Virtual DOM & Reconciliation
React maintains a lightweight copy of the actual DOM called the Virtual DOM. When state changes, React creates a new Virtual DOM tree, diffs it against the previous one, and only updates the real DOM where necessary. This reconciliation process makes React fast.
| 1 | // React automatically re-renders when state changes |
| 2 | function Counter() { |
| 3 | const [count, setCount] = React.useState(0); |
| 4 | |
| 5 | // When you click, React: |
| 6 | // 1. Updates state |
| 7 | // 2. Creates new Virtual DOM tree |
| 8 | // 3. Diffs against previous tree |
| 9 | // 4. Updates only the changed DOM nodes |
| 10 | return ( |
| 11 | <button onClick={() => setCount(count + 1)}> |
| 12 | Count: {count} |
| 13 | </button> |
| 14 | ); |
| 15 | } |
Component-Based Architecture
Everything in React is a component. Components accept inputs (props) and return React elements describing what should appear on screen. Components can be composed together to build complex UIs from simple, reusable pieces.
| 1 | // Components compose to build complex UIs |
| 2 | function App() { |
| 3 | return ( |
| 4 | <Layout> |
| 5 | <Header title="My App" /> |
| 6 | <Sidebar> |
| 7 | <NavItem label="Home" active /> |
| 8 | <NavItem label="Settings" /> |
| 9 | </Sidebar> |
| 10 | <Main> |
| 11 | <Dashboard /> |
| 12 | <RecentActivity limit={5} /> |
| 13 | </Main> |
| 14 | <Footer /> |
| 15 | </Layout> |
| 16 | ); |
| 17 | } |
Unidirectional Data Flow
Data in React flows downward from parent to child via props. Children never modify parent state directly — they communicate up through callback functions. This makes data changes predictable and easy to debug.
| 1 | // Data flows down, events flow up |
| 2 | function Parent() { |
| 3 | const [items, setItems] = React.useState([]); |
| 4 | |
| 5 | const handleAdd = (item) => { |
| 6 | setItems([...items, item]); |
| 7 | }; |
| 8 | |
| 9 | return ( |
| 10 | <div> |
| 11 | <InputForm onAdd={handleAdd} /> |
| 12 | <ItemList items={items} /> |
| 13 | </div> |
| 14 | ); |
| 15 | } |
| 16 | |
| 17 | function ItemList({ items }) { |
| 18 | // Receives data via props (downward) |
| 19 | return items.map(item => <Item key={item.id} {...item} />); |
| 20 | } |
| 21 | |
| 22 | function InputForm({ onAdd }) { |
| 23 | // Calls parent callback to send data up |
| 24 | return <button onClick={() => onAdd(newItem)}>Add</button>; |
| 25 | } |
The fastest way to start a new React project is with Vite. It provides a fast development server, hot module replacement, and optimized builds out of the box.
| 1 | # Create a new React project with Vite |
| 2 | npm create vite@latest my-react-app -- --template react |
| 3 | |
| 4 | # Navigate into the project |
| 5 | cd my-react-app |
| 6 | |
| 7 | # Install dependencies |
| 8 | npm install |
| 9 | |
| 10 | # Start the dev server |
| 11 | npm run dev |
| 12 | |
| 13 | # Build for production |
| 14 | npm run build |
| 15 | |
| 16 | # Preview the production build |
| 17 | npm run preview |
For server-rendered React applications with file-based routing, use Next.js:
| 1 | # Create a Next.js project (includes React) |
| 2 | npx create-next-app@latest my-next-app |
| 3 | |
| 4 | # With TypeScript, Tailwind, App Router (recommended) |
| 5 | npx create-next-app@latest my-next-app --typescript --tailwind --app --src-dir |
| 6 | |
| 7 | # Start development |
| 8 | npm run dev |
info
A well-organized React project follows a predictable structure. Here is a recommended layout for medium-to-large applications:
| 1 | my-app/ |
| 2 | src/ |
| 3 | components/ # Reusable UI components |
| 4 | Button/ |
| 5 | Button.tsx |
| 6 | Button.test.tsx |
| 7 | Button.module.css |
| 8 | index.ts # Re-export barrel |
| 9 | Modal/ |
| 10 | Modal.tsx |
| 11 | useModal.ts |
| 12 | Layout/ |
| 13 | Layout.tsx |
| 14 | Sidebar.tsx |
| 15 | hooks/ # Custom hooks |
| 16 | useAuth.ts |
| 17 | useDebounce.ts |
| 18 | useLocalStorage.ts |
| 19 | pages/ # Route-level components |
| 20 | Home.tsx |
| 21 | Dashboard.tsx |
| 22 | Settings.tsx |
| 23 | context/ # React Context providers |
| 24 | AuthContext.tsx |
| 25 | ThemeContext.tsx |
| 26 | services/ # API calls and external services |
| 27 | api.ts |
| 28 | auth.ts |
| 29 | utils/ # Pure helper functions |
| 30 | formatDate.ts |
| 31 | validateEmail.ts |
| 32 | types/ # TypeScript type definitions |
| 33 | index.ts |
| 34 | api.ts |
| 35 | styles/ # Global styles |
| 36 | globals.css |
| 37 | App.tsx # Root component |
| 38 | main.tsx # Entry point |
| 39 | public/ |
| 40 | favicon.ico |
| 41 | images/ |
| 42 | index.html |
| 43 | package.json |
| 44 | tsconfig.json |
| 45 | vite.config.ts |
best practice
Hooks are functions that let you "hook into" React state and lifecycle features from function components. They replaced class components as the primary way to manage state and side effects.
| Hook | Purpose | Category |
|---|---|---|
| useState | Local component state | State |
| useReducer | Complex state with reducer pattern | State |
| useEffect | Side effects (fetch, subscriptions, DOM) | Lifecycle |
| useContext | Consume context without nesting consumers | Context |
| useRef | Persist values between renders, DOM refs | Ref |
| useMemo | Memoize expensive calculations | Performance |
| useCallback | Memoize function references | Performance |
| useLayoutEffect | Synchronous DOM mutations | Lifecycle |
| useImperativeHandle | Customize ref handle exposed to parent | Ref |
| useId | Stable unique IDs for accessibility | Utility |
| useTransition | Mark non-urgent state updates | Concurrent |
| useDeferredValue | Defer re-rendering of non-urgent content | Concurrent |
| 1 | // Example: Using multiple hooks together |
| 2 | function UserProfile({ userId }) { |
| 3 | const [user, setUser] = useState(null); |
| 4 | const [loading, setLoading] = useState(true); |
| 5 | const abortRef = useRef(null); |
| 6 | |
| 7 | useEffect(() => { |
| 8 | const controller = new AbortController(); |
| 9 | abortRef.current = controller; |
| 10 | |
| 11 | fetch(`/api/users/${userId}`, { signal: controller.signal }) |
| 12 | .then(res => res.json()) |
| 13 | .then(data => { |
| 14 | setUser(data); |
| 15 | setLoading(false); |
| 16 | }) |
| 17 | .catch(err => { |
| 18 | if (err.name !== "AbortError") { |
| 19 | console.error(err); |
| 20 | setLoading(false); |
| 21 | } |
| 22 | }); |
| 23 | |
| 24 | return () => controller.abort(); |
| 25 | }, [userId]); |
| 26 | |
| 27 | if (loading) return <Spinner />; |
| 28 | if (!user) return <NotFound />; |
| 29 | |
| 30 | return ( |
| 31 | <div> |
| 32 | <h2>{user.name}</h2> |
| 33 | <p>{user.email}</p> |
| 34 | </div> |
| 35 | ); |
| 36 | } |
React supports several architectural patterns for organizing components. Understanding when to use each pattern is key to writing maintainable code.
Container / Presentational
Separate logic from display. Container components handle data fetching and state; presentational components handle rendering.
| 1 | // Container — handles data and logic |
| 2 | function UserListContainer() { |
| 3 | const [users, setUsers] = useState([]); |
| 4 | const [loading, setLoading] = useState(true); |
| 5 | |
| 6 | useEffect(() => { |
| 7 | fetchUsers().then(data => { |
| 8 | setUsers(data); |
| 9 | setLoading(false); |
| 10 | }); |
| 11 | }, []); |
| 12 | |
| 13 | const handleDelete = async (id) => { |
| 14 | await deleteUser(id); |
| 15 | setUsers(prev => prev.filter(u => u.id !== id)); |
| 16 | }; |
| 17 | |
| 18 | return <UserList users={users} loading={loading} onDelete={handleDelete} />; |
| 19 | } |
| 20 | |
| 21 | // Presentational — pure rendering, receives everything via props |
| 22 | function UserList({ users, loading, onDelete }) { |
| 23 | if (loading) return <p>Loading...</p>; |
| 24 | return ( |
| 25 | <ul> |
| 26 | {users.map(user => ( |
| 27 | <UserCard key={user.id} user={user} onDelete={onDelete} /> |
| 28 | ))} |
| 29 | </ul> |
| 30 | ); |
| 31 | } |
Compound Components
Related components share implicit state through Context, creating an expressive API for consumers.
| 1 | // Compound component pattern |
| 2 | function Tabs({ children, defaultTab }) { |
| 3 | const [activeTab, setActiveTab] = useState(defaultTab); |
| 4 | |
| 5 | return ( |
| 6 | <TabsContext.Provider value={{ activeTab, setActiveTab }}> |
| 7 | <div className="tabs">{children}</div> |
| 8 | </TabsContext.Provider> |
| 9 | ); |
| 10 | } |
| 11 | |
| 12 | Tabs.Tab = function Tab({ value, children }) { |
| 13 | const { activeTab, setActiveTab } = useContext(TabsContext); |
| 14 | return ( |
| 15 | <button |
| 16 | className={activeTab === value ? "active" : ""} |
| 17 | onClick={() => setActiveTab(value)} |
| 18 | > |
| 19 | {children} |
| 20 | </button> |
| 21 | ); |
| 22 | }; |
| 23 | |
| 24 | Tabs.Panel = function Panel({ value, children }) { |
| 25 | const { activeTab } = useContext(TabsContext); |
| 26 | return activeTab === value ? <div>{children}</div> : null; |
| 27 | }; |
| 28 | |
| 29 | // Usage — clean, declarative API |
| 30 | <Tabs defaultTab="overview"> |
| 31 | <Tabs.Tab value="overview">Overview</Tabs.Tab> |
| 32 | <Tabs.Tab value="details">Details</Tabs.Tab> |
| 33 | <Tabs.Panel value="overview">Overview content</Tabs.Panel> |
| 34 | <Tabs.Panel value="details">Details content</Tabs.Panel> |
| 35 | </Tabs> |
React excels in specific scenarios. Understanding its strengths helps you decide when it's the right tool:
| Good For | Consider Alternatives |
|---|---|
| Single-page applications (SPAs) | Simple static sites (use HTML/CSS) |
| Complex interactive UIs | Marketing pages (use Astro, Next.js) |
| Progressive web apps (PWAs) | Native mobile (use React Native) |
| Dashboard and admin panels | Server-rendered content (use Remix) |
| Real-time collaborative apps | Simple forms (use HTMX) |
| Large teams and long-lived codebases | Prototypes (use vanilla JS) |
note
React's power comes from its rich ecosystem. Here are the essential libraries and tools:
| Category | Recommended Libraries |
|---|---|
| Meta-framework | Next.js, Remix, Gatsby |
| Routing | React Router, TanStack Router |
| State Management | Zustand, Jotai, Redux Toolkit, TanStack Query |
| Forms | React Hook Form, Formik |
| Styling | Tailwind CSS, CSS Modules, styled-components |
| UI Components | shadcn/ui, Radix UI, Headless UI, MUI |
| Testing | Vitest, React Testing Library, Playwright, Storybook |
| Data Fetching | TanStack Query, SWR, Apollo Client, tRPC |
| Animation | Framer Motion, React Spring |
| TypeScript | Always recommended — use from the start |