React Component Templates
Ready-to-use templates for common web development projects and components
Modern React Patterns You Can Reuse
This resource collects practical, reusable React component patterns built with function components and hooks—the current standard for writing React. Each template includes a short description of what it does and when to use it, plus an accurate, copy-ready code snippet. There are no class components here: everything uses hooks such as useState, useEffect, useReducer, and useContext. These are educational starter snippets meant to be adapted to your project, not drop-in production libraries—add types, error boundaries, accessibility refinements, and tests as your application requires.
Component Patterns
Each card shows what the pattern is, when to use it, and a representative snippet
1. Functional Component with Props
The building block of every React app: a function that receives props and returns JSX. Use it whenever you need a presentational, reusable piece of UI driven by input data. Destructure props for readability and supply sensible defaults.
Snippet:
function Greeting({ name, role = "guest" }) {
return (
<p className="greeting">
Hello, {name}! You are signed in as {role}.
</p>
);
}
// Usage
<Greeting name="Ada" role="admin" />
2. useState: Counter & Toggle
Use useState to add local, reactive state to a component. Use the functional updater form (prev => ...) whenever the next value depends on the previous one to avoid stale-state bugs.
Snippet:
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
const [on, setOn] = useState(false);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount((c) => c + 1)}>Increment</button>
<button onClick={() => setCount(0)}>Reset</button>
<button onClick={() => setOn((prev) => !prev)}>
{on ? "ON" : "OFF"}
</button>
</div>
);
}
3. Controlled Form Input with Validation
A controlled input binds its value to state and updates on onChange, making React the single source of truth. Use this for forms that need live validation, formatting, or conditional submission. Prevent the default submit and only proceed when valid.
Snippet:
import { useState } from "react";
function EmailForm() {
const [email, setEmail] = useState("");
const [error, setError] = useState("");
const validate = (value) =>
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
? ""
: "Please enter a valid email address.";
const handleSubmit = (e) => {
e.preventDefault();
const message = validate(email);
setError(message);
if (!message) {
// submit the value, e.g. send to an API
console.log("Submitting:", email);
}
};
return (
<form onSubmit={handleSubmit} noValidate>
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
aria-invalid={error ? "true" : "false"}
aria-describedby="email-error"
/>
{error && (
<p id="email-error" role="alert">{error}</p>
)}
<button type="submit">Subscribe</button>
</form>
);
}
4. Data Fetching with useEffect
Use useEffect to fetch data after render and handle loading, error, and empty states explicitly. Cleanup note: guard against state updates after unmount (or a changed dependency) using an AbortController or an "ignore" flag returned from the effect. For real apps, prefer a data library such as React Query or the framework's loader, but this pattern shows the underlying mechanics.
Snippet:
import { useEffect, useState } from "react";
function UserList({ url }) {
const [users, setUsers] = useState([]);
const [status, setStatus] = useState("loading"); // loading | error | success
useEffect(() => {
const controller = new AbortController();
async function load() {
try {
setStatus("loading");
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
setUsers(data);
setStatus("success");
} catch (err) {
if (err.name !== "AbortError") setStatus("error");
}
}
load();
// Cleanup: abort the request if url changes or the component unmounts
return () => controller.abort();
}, [url]);
if (status === "loading") return <p>Loading…</p>;
if (status === "error") return <p role="alert">Something went wrong.</p>;
if (users.length === 0) return <p>No users found.</p>;
return (
<ul>
{users.map((u) => (
<li key={u.id}>{u.name}</li>
))}
</ul>
);
}
5. Custom Hook (useLocalStorage)
A custom hook is a function whose name starts with use and that calls other hooks. Use it to extract and reuse stateful logic across components without duplication. This useLocalStorage hook keeps a value in sync with localStorage and mirrors the useState API.
Snippet:
import { useState, useEffect } from "react";
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
try {
const stored = window.localStorage.getItem(key);
return stored !== null ? JSON.parse(stored) : initialValue;
} catch {
return initialValue;
}
});
useEffect(() => {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch {
// ignore write errors (e.g. storage full or unavailable)
}
}, [key, value]);
return [value, setValue];
}
// Usage
function ThemeToggle() {
const [theme, setTheme] = useLocalStorage("theme", "light");
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
Theme: {theme}
</button>
);
}
6. List & Conditional Rendering
Render collections with .map() and a stable, unique key (use a real ID, not the array index, when items can reorder). Combine with conditional rendering (&&, ternaries, or early returns) to handle empty and filtered states.
Snippet:
function TodoList({ todos }) {
if (todos.length === 0) {
return <p>Nothing to do yet.</p>;
}
return (
<ul>
{todos.map((todo) => (
<li key={todo.id} className={todo.done ? "is-done" : ""}>
{todo.title}
{todo.done && <span aria-label="completed"> ✓</span>}
</li>
))}
</ul>
);
}
7. Accessible Modal / Dialog
A modal overlays the page and traps the user's attention. Use it for confirmations or focused tasks. Accessibility notes: set role="dialog" and aria-modal="true", close on Escape, and move focus into the dialog when it opens (returning focus to the trigger on close). For production, consider the native <dialog> element or a vetted library for full focus-trapping.
Snippet:
import { useEffect, useRef } from "react";
function Modal({ isOpen, onClose, title, children }) {
const dialogRef = useRef(null);
useEffect(() => {
if (!isOpen) return;
// Move focus into the dialog when it opens
dialogRef.current?.focus();
const onKeyDown = (e) => {
if (e.key === "Escape") onClose();
};
document.addEventListener("keydown", onKeyDown);
return () => document.removeEventListener("keydown", onKeyDown);
}, [isOpen, onClose]);
if (!isOpen) return null;
return (
<div className="modal-overlay" onClick={onClose}>
<div
className="modal"
role="dialog"
aria-modal="true"
aria-labelledby="modal-title"
tabIndex={-1}
ref={dialogRef}
onClick={(e) => e.stopPropagation()}
>
<h2 id="modal-title">{title}</h2>
{children}
<button onClick={onClose}>Close</button>
</div>
</div>
);
}
8. Reusable Button with Variants
A single configurable Button keeps styling consistent across an app. Drive appearance with a variant prop, and forward remaining props (...rest) so the button still accepts onClick, type, disabled, and ARIA attributes.
Snippet:
function Button({
variant = "primary",
size = "md",
children,
...rest
}) {
const className = `btn btn--${variant} btn--${size}`;
return (
<button className={className} {...rest}>
{children}
</button>
);
}
// Usage
<Button variant="outline" size="sm" onClick={save}>
Save
</Button>
<Button variant="danger" disabled>Delete</Button>
9. Context for Shared State
Use createContext + useContext to share values (theme, auth, locale) without prop-drilling through many layers. Wrap consumers in a Provider and expose a small custom hook that throws if used outside the Provider. Keep context for low-frequency, broadly-needed data.
Snippet:
import { createContext, useContext, useState } from "react";
const ThemeContext = createContext(null);
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
const toggle = () => setTheme((t) => (t === "light" ? "dark" : "light"));
return (
<ThemeContext.Provider value={{ theme, toggle }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
return ctx;
}
// Usage inside any descendant
function Toolbar() {
const { theme, toggle } = useTheme();
return <button onClick={toggle}>Current: {theme}</button>;
}
10. useReducer for Complex State
When state has multiple sub-values or the next state depends on the previous one through several actions, useReducer centralizes the logic in a pure reducer function. It pairs well with Context for app-wide state and makes transitions explicit and testable.
Snippet:
import { useReducer } from "react";
const initialState = { count: 0, step: 1 };
function reducer(state, action) {
switch (action.type) {
case "increment":
return { ...state, count: state.count + state.step };
case "decrement":
return { ...state, count: state.count - state.step };
case "setStep":
return { ...state, step: action.payload };
case "reset":
return initialState;
default:
throw new Error(`Unknown action: ${action.type}`);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</div>
);
}
Folder Structure & Component Organization
Conventions that keep a growing React codebase maintainable
Group by Feature, Not by File Type
Co-locate everything a feature needs—components, hooks, styles, and tests—so related code lives together and is easy to move or delete. This scales better than a single giant components/ folder once an app grows.
Example layout:
src/
components/ # shared, generic UI (Button, Modal)
features/
auth/
LoginForm.jsx
useAuth.js
auth.test.js
dashboard/
Dashboard.jsx
widgets/
hooks/ # cross-cutting custom hooks
lib/ # api clients, helpers
pages/ # route-level components
App.jsx
main.jsx
One Component Per File, Named Clearly
Name files in PascalCase to match the component (UserCard.jsx), and keep one primary export per file. Custom hooks go in useSomething.js files. Consistent naming makes imports predictable and improves editor navigation.
Key Actions:
- PascalCase for components, camelCase for hooks and utilities
- Keep files small—split when a component does too much
- Use an
index.jsbarrel only when it genuinely simplifies imports
Separate Presentational and Logic Concerns
Push reusable stateful logic into custom hooks and keep components focused on rendering. Lift state only as high as it needs to go, and reach for Context or a state library only when prop-drilling becomes painful.
Key Actions:
- Extract data fetching and side effects into custom hooks
- Keep "dumb" presentational components free of side effects
- Colocate small helper components next to their parent
Related Resources
Continue building your web development skills
Web Development Templates
Starter templates and boilerplates for common web projects, layouts, and pages.
View TemplatesJavaScript Frameworks Course
Go deeper into React and other modern JavaScript frameworks with a structured course.
Explore CourseUI Design Cheat Sheet
Quick-reference principles and patterns for designing clean, usable component interfaces.
Open Cheat SheetFull-Stack Development Course
Connect your React front end to back-end APIs and databases in an end-to-end course.
Start LearningFrequently Asked Questions
Common questions about these React templates
These are educational starter snippets that demonstrate correct, modern React patterns. They are intentionally minimal. Before shipping, add appropriate validation, error handling, accessibility refinements (especially focus management for modals), tests, and—if you use TypeScript—types.
Function components with hooks are the recommended way to write React today. Hooks (useState, useEffect, useReducer, useContext, and custom hooks) cover the state and lifecycle needs that previously required class components, with less boilerplate and easier logic reuse.
Context is ideal for low-frequency, broadly-shared values such as theme, locale, or the current user. For high-frequency updates or complex global state, a dedicated library (such as Redux Toolkit or Zustand) or a server-state library (such as React Query) is often a better fit because Context re-renders all consumers when its value changes.
Return a cleanup function from useEffect that aborts in-flight requests (via AbortController) or sets an "ignore" flag, so you never call a state setter after the component has unmounted or the dependency changed. The data-fetching snippet above shows the AbortController approach.