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.

React component code on a developer screen

Component Patterns

Each card shows what the pattern is, when to use it, and a representative snippet

1. Functional Component with Props

Beginner Friendly Fundamental

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

Beginner Friendly State Basics

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

Intermediate Forms

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

Intermediate Side Effects

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)

Intermediate Reuse Logic

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

Beginner Friendly 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

Intermediate Accessibility

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

Beginner Friendly Design System

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

Intermediate Global 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

Advanced State Management

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

1

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
2

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.js barrel only when it genuinely simplifies imports
3

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 Templates

JavaScript Frameworks Course

Go deeper into React and other modern JavaScript frameworks with a structured course.

Explore Course

UI Design Cheat Sheet

Quick-reference principles and patterns for designing clean, usable component interfaces.

Open Cheat Sheet

Full-Stack Development Course

Connect your React front end to back-end APIs and databases in an end-to-end course.

Start Learning

Frequently Asked Questions

Common questions about these React templates