Search across all documentation pages
23 component examples to get you started with React -- 10 basic and 13 intermediate.
Get a new React project running in under a minute:
# Create a new Next.js app (recommended)
npx create-next-app@latest my-app --typescript --tailwind --app
cd my-app
npm run devYour app is now running at http://localhost:3000. Edit app/page.tsx to start building. If port 3000 is already in use, Next.js will automatically increment to the next available port (3001, 3002, etc.) -- check your terminal output for the actual URL.
Other options: You can also use
npm create vite@latest my-app -- --template react-tsfor a lightweight Vite + React setup, ornpx create-react-app my-app --template typescriptfor the classic CRA approach (no longer recommended for new projects).
The simplest React component -- a function that returns JSX.
// components/hello-world.tsx
function HelloWorld() {
return <h1>Hello, World!</h1>;
}
export default HelloWorld;To use this component, import it into another file:
// app/page.tsx
import HelloWorld from "@/components/hello-world";
export default function Home() {
return (
<main>
<HelloWorld />
</main>
);
}@/ is a shortcut for your project root -- so @/components/hello-world points to components/hello-world.tsx<HelloWorld />Related: Components -- composition patterns, props, children | Function Component Syntax -- declaration vs. arrow, default vs. named export | JSX and TSX -- the syntax components return
All examples in this guide use TypeScript (.tsx files) because it is the industry standard for professional React development. Here's why:
Use curly braces {} to embed JavaScript expressions inside JSX.
function Greeting() {
const name = "Alice";
return <p>Welcome, {name}!</p>;
}{} create an expression slot -- you can put any JavaScript expression insideif or for) directly inside curly bracesRelated: JSX and TSX -- expression rules, fragments, and JSX compilation | TypeScript + React Basics -- typing variables and expressions
Map over an array to produce multiple elements.
function FruitList() {
const fruits = ["Apple", "Banana", "Cherry"];
return (
<ul>
{fruits.map((fruit) => (
<li key={fruit}>{fruit}</
.map() transforms each item in the array into a JSX elementkey prop so React can track which items changedRelated: Lists and Keys -- key strategies, nested lists, filtering, and common pitfalls
Props let you pass data from a parent component to a child.
function UserCard({ name, age }: { name: string; age: number }) {
return (
<div>
<h2>{name}</h2>
<p>Age: {age}</p
{ name, age } directly in the parameter list for clean accessRelated: Components -- composition patterns and the children prop | Typing Props -- interfaces, optional props, discriminated unions
Show or hide content based on a condition.
function LoginStatus({ isLoggedIn }: { isLoggedIn: boolean }) {
return (
<div>
{isLoggedIn ? <p>Welcome back!</p> : <p>Please log in.</p>}
</div
? : is the most common way to conditionally render in JSX&& for "show or nothing": {isLoggedIn && <p>Welcome!</p>}Related: Conditional Rendering -- ternary,
&&, early return, and guard clauses in depth
Attach an event handler to respond to user actions.
function ClickCounter() {
const handleClick = () => {
alert("Button clicked!");
};
return <button onClick={handleClick}>Click me</button>;
}onClick, not onclick)onClick={handleClick} not onClick={handleClick()}Related: Events -- synthetic events, event delegation, and handler patterns | Mouse Events -- click, double-click, hover | Typing Events -- event handler types
Add interactive state to a component.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
useState returns a pair: the current value and a setter functionuseState(0) is the initial value, used only on first renderRelated: useState -- updater functions, lazy initialization, and state batching | Typing State -- typing complex state shapes
Capture what the user types with a controlled input.
import { useState } from "react";
function NameInput() {
const [name, setName] = useState("");
return (
<div>
<input
value={name}
value={name}onChange fires on every keystroke and updates state with the new valueRelated: Forms -- controlled vs. uncontrolled inputs, form submission | Controlled vs. Uncontrolled -- when to use each | Form Events -- onChange, onSubmit, onBlur
Use the special children prop to wrap content.
function Card({ children }: { children: React.ReactNode }) {
return <div className="border rounded p-4 shadow">{children}</div>;
}
// Usage:
// <Card>
// <h2>Title</h2>
// <p>Some content here.</p>
// </Card>children is whatever you put between the opening and closing tags of a componentReact.ReactNode accepts strings, numbers, elements, arrays, or nullRelated: Composition -- slots, compound components, and layout patterns | Components -- the children prop in depth
Add CSS classes or inline styles to elements.
function StyledBox() {
return (
<div
className="container"
style={{ backgroundColor: "lightblue", padding: "1rem" }}
>
<p>Styled with both className and inline styles.</p>
</div>
);
}className instead of class (since class is a reserved word in JavaScript)backgroundColor, not background-color)Related: Tailwind Setup -- configuring Tailwind in a Next.js project | Tailwind Utilities -- spacing, typography, and layout classes | Dark Mode -- theme switching with Tailwind
Load data from an API when the component mounts.
import { useState, useEffect } from "react";
interface User {
id: number;
name: string;
email: string;
}
function UserProfile({ userId }: { userId
useEffect runs side effects -- things that happen outside of rendering (API calls, timers, subscriptions)[userId] tells React to re-run the effect only when userId changes[] means "run once on mount" -- omitting it entirely means "run after every render"Related: useEffect -- cleanup, dependency arrays, and common mistakes | SWR Basic Fetching -- a better approach to data fetching with caching and revalidation | Suspense -- loading states with React Suspense
Share state between sibling components by moving it to their parent.
import { useState } from "react";
function TemperatureInput({
label,
value,
onChange,
}: {
label: string;
value: string;
onChange: (val
Related: Context Patterns -- sharing state without prop drilling | Zustand Setup -- when lifting state isn't enough, use a store | Context vs. Zustand -- choosing between Context and Zustand
Control child component visibility from a parent button using state and props.
import { useState } from "react";
interface DataRowsProps {
showRows: boolean;
data: string[];
}
function DataRows({ showRows, data }: DataRowsProps) {
if (
showData) and passes it down as a propshowRows prop -- returning null hides it completelypx-4 py-2), colors (bg-blue-600 text-white), borders (rounded-md border), and interactive states (hover:bg-blue-700)last:border-0 removes the border from the final row for a clean lookRelated: Conditional Rendering -- early return, guards, and visibility patterns | Lifting State Up (Example 12) -- sharing state between siblings | Composition -- building flexible component hierarchies
Build a flexible component using props to control appearance.
function Button({
variant = "primary",
children,
onClick,
}: {
variant?: "primary" | "secondary" | "danger";
children: React.ReactNode;
onClick?:
variant = "primary") make props optional with sensible defaults"primary" | "secondary" | "danger" restricts the prop to valid optionsRelated: Compound Components -- multi-part component APIs | Button Component -- a production-ready variant button | Discriminated Unions -- type-safe variant props
Extract reusable logic into a custom hook.
import { useState } from "react";
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = () => setValue((v) =>
use and can call other hooksRelated: Custom Hooks Guide -- rules, testing, and patterns for custom hooks | useToggle -- the toggle hook used in this example | Custom Hooks (React Hooks) -- when and why to extract hooks
Manage a form with several inputs using a single state object.
import { useState, type FormEvent } from "react";
interface FormData {
name: string;
email: string;
message: string;
}
function ContactForm() {
const [form
useState calls{ ...prev, [field]: value } creates a new object with one field updatedkeyof FormData ensures you can only pass valid field names -- typos become compile errorse.preventDefault() on form submit to prevent the default browser page reloadRelated: Form Patterns (Complex) -- multi-step forms and dynamic fields | React Hook Form + Zod -- schema-validated forms at scale | Form Decision Checklist -- choosing the right form strategy | Gherkin Form Decision Checklist -- test-driven form planning
Skip useEffect entirely -- fetch on the server and render ready HTML.
// app/users/page.tsx
interface User {
id: number;
name: string;
email: string;
}
export default async function UsersPage() {
const res = await fetch(
async functions that run only on the server -- no JavaScript ships to the browser for themawait data directly in the component body; no useEffect, no loading flags, no client-side waterfallnext: { revalidate: 60 } option caches the response for 60 seconds, then refetches on the next requestRelated: Server Components -- when to use server vs. client components | Fetching -- caching, revalidation, and parallel fetches | Client Components -- when you need
"use client"
Handle form submissions on the server without writing an API route.
// app/todos/page.tsx
import { revalidatePath } from "next/cache";
async function createTodo(formData: FormData) {
"use server";
const title = formData.get("title") as string;
await db.todo.
"use server" directive marks a function as a Server Action -- it only runs on the server, even when called from a client form<form action={...}>; React serializes the form data and invokes the action on submitrevalidatePath("/todos") tells Next.js to purge the cached page so the new todo appears immediatelyfetch, no JSON parsing -- the network call is handled entirely by React and Next.jsRelated: Server Actions -- security, validation, and error handling | Form Actions -- React 19's form action support | Revalidation --
revalidatePathvs.revalidateTag
Track pending state and server responses from a form action.
"use client";
import { useActionState } from "react";
import { subscribe } from "./actions";
interface State {
message: string;
ok: boolean;
}
const initialState: State
And the matching server action file:
// app/subscribe/actions.ts
"use server";
interface State {
message: string;
ok: boolean;
}
export async function subscribe(_prev: State, formData: FormData):
useActionState wraps a Server Action and returns [state, action, isPending] in one callisPending flag flips to true during submission and back to false when the action resolves -- perfect for disabling buttons and showing spinners(prevState, formData) and whatever it returns becomes the new state"use server" directive at the top of actions.ts marks every export as a Server Action -- they run only on the server, so database calls and secrets stay safeFormData values on the server before trusting them -- the client can submit anything, so typeof email !== "string" and shape checks belong in the action itself"use client"), but the underlying action still runs on the serverRelated: useActionState -- return shape, error handling, and validation patterns | Form Actions -- the React 19 form action contract | Server Actions -- validating FormData on the server
Show the result instantly while the server catches up.
"use client";
import { useOptimistic, useState } from "react";
import { addLike } from "./actions";
export function LikeButton({ postId, initialLikes }: { postId: number; initialLikes: number }) {
useOptimistic gives you a temporary state that updates instantly, then reverts if the action fails(current) => current + 1 describes how to apply the optimistic update on top of the real statesetLikes(next)), React reconciles and the optimistic value is replacedRelated: useOptimistic -- reducer patterns, multiple pending updates, and error recovery | Server Actions -- pairing optimistic UI with mutations
Stream parts of the page as their data becomes ready.
// app/dashboard/page.tsx
import { Suspense } from "react";
import { RecentOrders } from "./recent-orders";
import { SalesChart } from "./sales-chart";
export default function DashboardPage() {
return (
<div className="grid grid-cols-2 gap-4"
async Server Component that fetches its own data -- the slow one doesn't block the fast one<Suspense fallback={...}> tells React: "show this placeholder until the child resolves"Promise.all choreography -- the tree just renders as each piece is readyRelated: Suspense -- boundaries, nested suspense, and error handling | Streaming -- how Next.js streams HTML | Suspense + Streaming (Performance) -- measuring and optimizing streaming perf | Loading & Error UI -- route-level
loading.tsx
Push live updates from the server to the browser over a single HTTP connection.
// app/api/ticker/route.ts
export async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
for (let i = 0; i
// app/ticker/page.tsx
"use client";
import { useEffect, useState } from "react";
interface Tick {
tick: number;
time: number;
}
export default function TickerPage() {
const [
ReadableStream with Content-Type: text/event-stream -- the SSE protocol format is just lines like data: <payload>\n\nEventSource auto-reconnects on network drops and delivers each data: line to onmessageuseEffect that calls source.close() -- otherwise the connection leaks on unmount or navigationRelated: Streaming -- HTML streaming vs. data streaming | Server Actions -- when to mutate vs. subscribe | useEffect -- cleanup functions and subscription patterns
Pass data through the tree without prop drilling.
"use client";
import { createContext, useContext, useState, type ReactNode } from "react";
type Theme = "light" | "dark";
interface ThemeContextValue {
theme: Theme;
toggle: () => void;
createContext makes a container for shared values; <ThemeContext.Provider value={...}> wraps the part of the tree that can read ituseContext(ThemeContext) to read the value -- no need to pass props through every intermediate componentuseTheme) and throwing when the provider is missing gives you a clear error instead of a silent nullRelated: useContext -- provider patterns, performance, and common pitfalls | Context Patterns -- splitting state vs. dispatch, compound providers | Context vs. Zustand -- when to reach for a store instead