Home  ›  Web Development  ›  React

WEB DEVELOPMENT / REACT

React: Complete Guide to React Components, Hooks, State & Modern Web Development

Learn React from the fundamentals to components, JSX, props, state, hooks, forms, APIs, performance, accessibility, testing and modern frontend application architecture.

Every concept comes with a short, copyable example using current React patterns — function components and hooks throughout.

ILLUSTRATIVE COMPONENT TREE
App
├── Header
├── Navigation
├── Main
│   ├── Hero
│   ├── ProductGrid
│   │   ├── ProductCard
│   │   ├── ProductCard
│   │   └── ProductCard
│   └── ContactForm
└── Footer
Illustrative React component tree for a web application

THE BASICS

What Is React?

React is a JavaScript library for building user interfaces from reusable components. You describe what the interface should look like for a given set of data, and React works out how to update the page when that data changes.

That word — library — matters. React is not a programming language, and it isn’t a complete application framework. It concerns itself with the UI layer. Routing, data fetching strategies, server rendering, authentication and build tooling come from additional libraries or from frameworks built on React. Being clear about that boundary saves a lot of confusion about what React does and doesn’t give you.

The appeal is mostly about managing complexity. Reusable components stop you rebuilding the same card five times. The declarative model means you describe the result rather than writing manual DOM updates. Composition lets small pieces combine into large interfaces. State-driven rendering keeps what’s on screen consistent with your data. And a large ecosystem and mature tooling mean most problems have been solved by someone.

None of that makes React the right choice for every project. A brochure site with two interactive elements doesn’t need a component library and a build step. React earns its complexity when an interface has genuine state to manage.

React handles

React does not handle

WHERE REACT FITS

React vs JavaScript, HTML, CSS and PHP

React sits on top of the web platform rather than replacing any part of it. Understanding what each piece still does prevents a lot of architectural confusion.

JavaScript

The programming language React is written in and built around. React does not replace it — React applications are JavaScript applications using functions, arrays, objects, destructuring, spread, map(), promises and modules constantly. Weak JavaScript is the most common reason React feels confusing. See our JavaScript guide.

HTML

Defines document structure. JSX resembles HTML but is not HTML — it's a syntax extension that compiles to JavaScript function calls. React still renders real HTML elements, and semantic markup matters as much as ever. See our HTML guide.

CSS

Handles presentation, exactly as it does without React. React decides structure and behaviour; CSS decides how it looks and how it adapts across screen sizes. The two work together unchanged. See our CSS guide.

PHP

Runs on the server for application logic, database access and authorisation. React runs in the browser. They aren't alternatives — React typically calls a PHP API over HTTP and renders the JSON it returns. See our PHP guide.

INTERACTIONUser clicks, types or submits
EVENTYour handler runs
STATEA state setter is called
RENDERReact re-runs the affected components
COMPAREReact works out what actually changed
DOMOnly the necessary updates are applied
UIThe interface reflects the new state
How React updates the interface from user interaction through state change to DOM update

HOW IT WORKS

How React Works

The cycle is consistent: something happens, state changes, React re-renders, and the page updates.

“Re-render” is where people get the wrong idea. When state changes, React calls the relevant component functions again to produce a new description of the UI. It then compares that description with the previous one and applies only the differences to the real DOM. It does not rebuild the whole page, and re-rendering a component is not the same as updating the DOM — a component can re-render and produce identical output, in which case nothing is touched.

This comparison process is called reconciliation. The tree React compares against is often described as the virtual DOM: a lightweight in-memory representation of what the UI should be. It’s a real part of how React works, but the claim that “the virtual DOM makes React fast” is an oversimplification. Direct, carefully written DOM manipulation can be faster. What React actually offers is a predictable programming model where you stop manually tracking which elements need updating — the performance is acceptable rather than magical, and React’s modern architecture involves considerably more than tree diffing.

COMPONENTS

Components, Function Components and JSX

A component is a JavaScript function that returns a description of some UI. Modern React is built around function components — class components still work and appear in older codebases, but they’re not how new React is written.

Component names must start with a capital letter. This isn’t style preference: JSX treats lowercase names as built-in HTML elements and capitalised names as components, so <productCard /> becomes an unknown HTML tag rather than your component.

JSX is a syntax extension that lets you write UI structure in something resembling markup. It compiles to ordinary JavaScript function calls — which is why you can use it inside variables, return it from functions and map over arrays to produce it.

A few JSX conventions differ from HTML because the attribute names follow JavaScript properties: className instead of class, htmlFor instead of for, and camelCase for multi-word attributes. Curly braces embed any JavaScript expression, and every element must be closed, including void elements written as <img />. A component must return a single root element — a Fragment, written <>...</>, groups siblings without adding a wrapper div to the DOM.

 React — components and JSXEXAMPLE
function Welcome() {
  return <h2>Welcome to React</h2>;
}

function UserCard() {
  const name = "Alex";
  const isOnline = true;

  return (
    <article className="card">
      <h2>Hello {name}</h2>

      {/* Ternary for either-or */}
      {isOnline ? <span>Online</span> : <span>Offline</span>}

      {/* && renders only when the condition is true */}
      {isOnline && <button type="button">Message</button>}

      <img src="/images/avatar.png" alt="Profile photo of Alex" />
    </article>
  );
}

// Fragment groups elements without a wrapper div
function Layout() {
  return (
    <>
      <Welcome />
      <UserCard />
    </>
  );
}
 React — props, children and compositionEXAMPLE
function UserCard({ name, role, onSelect }) {
  return (
    <article className="card">
      <h2>{name}</h2>
      <p>{role}</p>
      <button type="button" onClick={onSelect}>View profile</button>
    </article>
  );
}

// Passing props from the parent
<UserCard
  name="Alex"
  role="Developer"
  onSelect={handleSelect}
/>

// Default values
function Badge({ label, tone = "neutral" }) {
  return <span className={`badge badge--${tone}`}>{label}</span>;
}

// children: composition instead of configuration
function Card({ title, children }) {
  return (
    <section className="card">
      <h2>{title}</h2>
      {children}
    </section>
  );
}

<Card title="Account">
  <UserCard name="Alex" role="Developer" />
  <Badge label="Active" tone="success" />
</Card>

PROPS

Props, Children and Composition

Props are how a parent passes data to a child. They arrive as a single object, usually destructured in the parameter list, and can carry anything — strings, numbers, arrays, objects, other components, and functions for the child to call.

Props are read-only. A component must never modify the props it receives. Data flows one way, from parent to child, and that one-way flow is what makes React applications traceable: when something on screen is wrong, you follow the props back up to find where the value came from. If a child needs to change something, the parent passes down a function for it to call.

The children prop holds whatever you nest inside a component’s tags. It turns a component from something you configure with a long list of props into something you compose with. A Card that accepts children works for any content; a Card with fifteen props for every possible variation quickly becomes unmaintainable.

Composition — small components combining into larger ones — is React’s main answer to complexity. Reach for it before reaching for Context or a state library.

STATE

State, useState and Immutable Updates

State is data that changes over time and affects what’s rendered. The useState hook returns a pair: the current value and a setter function. Calling the setter tells React the value has changed and the component should re-render.

When new state depends on the previous value, use the functional update form — setCount(prev => prev + 1). State updates are processed in batches, so reading the current variable and adding to it can give stale results when several updates happen together.

Never mutate state directly. Pushing to a state array or assigning to a state object’s property changes the value in place, so React sees the same reference and may not re-render. Instead create a new value: spread the old object with your changes, or use array methods that return new arrays such as map(), filter() and spreading.

Keep state as close as possible to the components that use it. Hoisting everything to the top of the app makes every change re-render a large tree and makes components harder to reuse. Lift state only when two components genuinely need to share it.

 React — useState and immutable updatesEXAMPLE
import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button type="button" onClick={() => setCount(prev => prev + 1)}>
      Count: {count}
    </button>
  );
}

// Objects: create a new object, never mutate
const [user, setUser] = useState({ name: "Alex", role: "Developer" });

// user.name = "Sam";           // wrong — mutation
setUser(prev => ({ ...prev, name: "Sam" }));   // correct

// Arrays: return new arrays
const [items, setItems] = useState([]);

// items.push(newItem);         // wrong
setItems(prev => [...prev, newItem]);              // add
setItems(prev => prev.filter(i => i.id !== id));   // remove
setItems(prev => prev.map(i =>                     // update one
  i.id === id ? { ...i, done: true } : i
));
 React — events and a controlled formEXAMPLE
import { useState, useId } from "react";

function ContactForm({ onSubmit }) {
  const emailId = useId();
  const [email, setEmail] = useState("");
  const [error, setError] = useState("");
  const [status, setStatus] = useState("idle");

  async function handleSubmit(event) {
    event.preventDefault();

    if (!email.includes("@")) {
      setError("Enter a valid email address.");
      return;
    }

    setError("");
    setStatus("saving");

    try {
      await onSubmit({ email });
      setStatus("success");
    } catch {
      setStatus("error");
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <label htmlFor={emailId}>Email address</label>

      <input
        id={emailId}
        type="email"
        value={email}
        onChange={event => setEmail(event.target.value)}
        aria-invalid={error ? "true" : undefined}
        aria-describedby={error ? `${emailId}-error` : undefined}
      />

      {error && <p id={`${emailId}-error`}>{error}</p>}

      <button type="submit" disabled={status === "saving"}>
        {status === "saving" ? "Saving…" : "Subscribe"}
      </button>
    </form>
  );
}

EVENTS & FORMS

Events, Forms and Controlled Components

React event handlers are camelCase props taking a function: onClick, onChange, onSubmit, onFocus, onBlur, onKeyDown. Pass the function itself, not the result of calling it — onClick={handleClick}, not onClick={handleClick()}, which runs it during render.

Handlers receive an event object with the usual properties, including event.target.value for inputs and event.preventDefault() to stop the browser’s default behaviour.

A controlled component is an input whose value comes from state, with onChange updating that state. React becomes the single source of truth for what’s in the field, which makes validation, conditional logic and resetting straightforward. Uncontrolled inputs let the DOM hold the value and read it with a ref — occasionally simpler, but less flexible.

Handle onSubmit on the form rather than onClick on the button, so keyboard submission works. Call preventDefault(), validate, then handle the data. Client-side validation is a user-experience improvement only — the server must validate independently, since anyone can send a request directly.

For accessibility, useId generates unique IDs for label and error associations that stay stable across server and client rendering.

RENDERING DATA

Lists, Keys and Conditional Rendering

Rendering a collection means mapping data to elements with map(). Each item needs a key — a string or number that identifies it among its siblings.

Keys tell React which item is which between renders. With stable keys, reordering a list moves the existing elements; without them, React matches by position and can associate the wrong state with the wrong item. Using the array index as a key is the classic trap: it works while the list never changes order and breaks subtly when items are added, removed or sorted — typically showing as input values attached to the wrong row. Use a genuine unique identifier from your data.

Conditional rendering has several forms, each suiting a different situation. The ternary handles either-or. The && operator renders something or nothing — but beware numbers, since 0 && ... renders 0 on the page rather than nothing. Early returns are usually clearest for loading, error and empty states, keeping the main render path uncluttered.

 React — lists, keys and conditional renderingEXAMPLE
function ProductList({ products, isLoading, error }) {
  // Early returns keep the main path clean
  if (isLoading) return <Spinner />;
  if (error)     return <ErrorMessage error={error} />;
  if (products.length === 0) return <p>No products found.</p>;

  return (
    <ul>
      {products.map(product => (
        // Stable id, not the array index
        <li key={product.id}>
          {product.name}

          {/* Careful: a count of 0 would render "0" */}
          {product.stock > 0 && <span>In stock</span>}

          {product.isNew ? <Badge label="New" /> : null}
        </li>
      ))}
    </ul>
  );
}
 React — useEffect and cleanupEXAMPLE
import { useEffect, useState } from "react";

function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();

    // Cleanup runs before the next effect and on unmount
    return () => connection.disconnect();
  }, [roomId]);   // re-synchronises when roomId changes

  return <h2>Room: {roomId}</h2>;
}

// Synchronising with a browser API
useEffect(() => {
  document.title = `Count: ${count}`;
}, [count]);

// You usually do NOT need an effect for derived values
function Profile({ firstName, lastName, items }) {
  // Just calculate during render
  const fullName = `${firstName} ${lastName}`;
  const total = items.reduce((sum, i) => sum + i.price, 0);

  return <h2>{fullName} — {total}</h2>;
}

EFFECTS

useEffect, Dependencies and Cleanup

useEffect synchronises a component with something outside React — a network connection, a subscription, a browser API, a third-party widget. That framing matters more than the common shorthand of “run code after render”, because it tells you when an effect is the wrong tool.

The dependency array declares what the effect depends on. React re-runs it when any dependency changes. An empty array means it runs once after mount; omitting the array entirely means it runs after every render, which is rarely what you want. Include every value from the component that the effect uses — the linting rule for this is worth trusting rather than silencing.

The cleanup function you return runs before the effect runs again and when the component unmounts. Anything you start — a connection, subscription, timer, event listener — should be stopped there, or you’ll leak resources and get updates from components that no longer exist.

When to avoid effects entirely: derived values don’t need one. Calculating a full name, a total or a filtered list during render is simpler, always correct, and avoids an extra render pass. Transforming props into state with an effect is a common anti-pattern that produces stale values. Handling a user event belongs in the event handler, not an effect.

HOOKS

React Hooks Beyond useState

useRef

Holds a mutable value that persists across renders without causing one. Most often a DOM reference for focus management or measurement, sometimes a value like a timer id.

useContext

Reads a value from a Context provider, letting data reach deep descendants without passing props through every level. Good for theme, locale, auth state — not a general state manager.

useReducer

Manages state through a reducer function and dispatched actions. Suits state with several related transitions, where scattered useState calls become hard to keep consistent.

useMemo

Caches a calculated value between renders. Useful for genuinely expensive calculations or stable references — not something to apply by default.

useCallback

Caches a function definition between renders. Mainly useful when passing callbacks to memoised children or effect dependencies.

useId

Generates a unique, stable identifier for accessibility relationships such as label and error associations.

 React — useRef, useContext and useReducerEXAMPLE
import { useRef, useContext, useReducer, createContext } from "react";

// useRef: DOM access without re-rendering
function SearchBox() {
  const inputRef = useRef(null);

  function focusInput() {
    inputRef.current?.focus();
  }

  return <input ref={inputRef} type="search" />;
}

// useContext: avoid passing props through every level
const ThemeContext = createContext("light");

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  const theme = useContext(ThemeContext);
  return <div className={`toolbar toolbar--${theme}`} />;
}

// useReducer: related state transitions in one place
function cartReducer(state, action) {
  switch (action.type) {
    case "add":    return { ...state, items: [...state.items, action.item] };
    case "remove": return { ...state, items: state.items.filter(i => i.id !== action.id) };
    case "clear":  return { ...state, items: [] };
    default:       throw new Error(`Unknown action: ${action.type}`);
  }
}

const [state, dispatch] = useReducer(cartReducer, { items: [] });
dispatch({ type: "add", item: product });
 React — a custom hookEXAMPLE
import { useState, useEffect } from "react";

// Custom hooks are functions starting with "use"
// that call other hooks. They share logic, NOT state —
// each component calling this gets its own independent state.
function useOnlineStatus() {
  const [isOnline, setIsOnline] = useState(navigator.onLine);

  useEffect(() => {
    function handleOnline()  { setIsOnline(true); }
    function handleOffline() { setIsOnline(false); }

    window.addEventListener("online", handleOnline);
    window.addEventListener("offline", handleOffline);

    return () => {
      window.removeEventListener("online", handleOnline);
      window.removeEventListener("offline", handleOffline);
    };
  }, []);

  return isOnline;
}

// Used like any other hook
function StatusBar() {
  const isOnline = useOnlineStatus();
  return <p>{isOnline ? "Connected" : "Offline"}</p>;
}

Custom hooks extract reusable stateful logic into a function whose name begins with use. An important clarification: custom hooks share logic, not state. Every component calling the same custom hook gets its own independent state — if you need shared state, that’s Context or a state library.

The Rules of Hooks exist because React tracks hooks by call order. Call them at the top level of a component or custom hook — never inside conditions, loops or nested functions — and only from React components or other hooks, not from ordinary JavaScript functions. The ESLint plugin catches violations reliably and is worth having.

STATE ARCHITECTURE

Prop Drilling, Context and State Management

Prop drilling is passing a value down through components that don’t use it, purely to reach a descendant. Two or three levels is usually fine — it’s explicit and easy to trace. It becomes a problem when a value threads through many layers, or when adding a prop means touching six files.

Before reaching for Context, try composition: passing a rendered element as children often removes the need to drill entirely, because the element is created where the data already lives.

Context makes a value available to a whole subtree without prop passing. It suits genuinely global, infrequently changing data: theme, locale, the current user, application settings. It is not a general-purpose state manager, and putting frequently changing values in a Context re-renders every consumer.

The most useful distinction is between UI state and server state. UI state is yours — you create it, you own it. Server state is a cached copy of something that lives elsewhere and can change without you knowing, bringing concerns your useState won’t handle: caching, revalidation, loading and error states, deduplicating requests, and staleness. Recognising that difference explains why dedicated data-fetching libraries exist. Which one to use is a project decision; there’s no universally correct answer.

Kind of state
Examples
Typically handled by
UI state
ExamplesModal open, selected tab, input value
Typically handled byuseState, useReducer, sometimes URL
Shared UI state
ExamplesTheme, locale, signed-in user
Typically handled byContext, or lifting state up
URL state
ExamplesCurrent page, filters, search query
Typically handled byThe URL itself, via the router
Server state
ExamplesProducts, orders, profiles from an API
Typically handled byFetching with caching, loading and error handling
Form state
ExamplesField values, validation, submission
Typically handled byLocal state or a form library
Comparison of UI state, URL state and server state in React applications
APPHolds the user data
LAYOUTPasses it down, doesn't use it
SIDEBARPasses it down, doesn't use it
PROFILEFinally uses the user data
Illustration of prop drilling passing data through intermediate components
ILLUSTRATIVE API DATA FLOW
USERInteracts with the interface
COMPONENTTriggers a request
FETCHHTTP request with credentials
BACKENDAuthenticates, authorises, queries
DATABASEReturns the data
JSONResponse received and checked
STATEComponent state updated
UIInterface re-renders with the data
React frontend communicating with a backend API
 React — fetching data with loading and error statesEXAMPLE
import { useState, useEffect } from "react";

function UserList() {
  const [users, setUsers]     = useState([]);
  const [status, setStatus]   = useState("loading");
  const [error, setError]     = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    async function loadUsers() {
      try {
        setStatus("loading");

        const response = await fetch("/api/users", {
          signal: controller.signal,
        });

        // fetch does not reject on 404 or 500
        if (!response.ok) {
          throw new Error(`Request failed: ${response.status}`);
        }

        const data = await response.json();
        setUsers(data);
        setStatus("success");
      } catch (err) {
        if (err.name === "AbortError") return;
        setError(err);
        setStatus("error");
      }
    }

    loadUsers();

    // Cancel the request if the component unmounts
    return () => controller.abort();
  }, []);

  if (status === "loading") return <Spinner />;
  if (status === "error")   return <ErrorMessage error={error} />;

  return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

APIS

React and API Integration

Most React applications get their data from an HTTP API. The component requests it, stores the result in state, and renders whatever state currently says.

Three states are always involved, and skipping any of them produces a poor interface: loading, success and error. Users need to know something is happening, and they need a useful message when it fails — a blank screen is the worst of both.

Two details catch people out. fetch does not reject on HTTP error statuses; a 404 or 500 resolves normally, so check response.ok yourself. And a request in flight when a component unmounts will try to set state on something that no longer exists — an AbortController cleaned up in the effect handles that.

In larger applications, move request logic out of components into an API layer or custom hooks. Components then describe UI rather than knowing about endpoints, headers and error shapes, and changing an endpoint means editing one file. More on API design in our website APIs guide.

BACKEND INTEGRATION

React With PHP, Laravel and WordPress

React handles the interface; something else handles data and security. The pattern is the same regardless of what that something is: React sends an HTTP request, the backend authenticates, authorises, validates and queries, then returns JSON.

React With PHP

A PHP application exposes JSON endpoints that React calls. PHP keeps everything that must be trusted — database access, authorisation rules, secrets — because browser code can hold none of those. React does not replace PHP; they handle different halves. See our PHP guide.

React With Laravel

Laravel is a PHP framework that can serve as the API layer behind a React frontend, providing routing, Eloquent, validation and authentication. Neither requires the other — Laravel serves Blade templates perfectly well, and React works with any backend. See our Laravel guide.

React With WordPress

Several architectures work: React components embedded in specific parts of a WordPress theme, a plugin admin interface built in React, or a fully headless setup where WordPress serves content via the REST API. Headless is one option among several, not an upgrade every site needs. See our CMS guides.

REACTUser action in the interface
HTTPRequest sent to the backend
BACKENDPHP, Laravel or WordPress REST API
AUTHAuthentication and authorisation enforced here
DATABASEQuery executed
JSONResponse returned
REACTState updated, UI re-rendered
React frontend communicating with a PHP, Laravel or WordPress backend

ROUTING

Routing, Navigation and Single-Page Applications

React itself has no routing. A router — a separate library, or one built into a React framework — maps URLs to components, so /products/123 renders a product page with 123 available as a route parameter.

In a single-page application, the browser loads an application shell once and the router swaps views without full page navigations. Internal navigation uses the router’s link component rather than a plain anchor, which would trigger a full reload. Programmatic navigation after an action — redirecting once a form succeeds — goes through the router too.

Whichever router you use, URLs must behave like URLs: every view needs its own address, the back button must work, and links must be shareable and bookmarkable. Breaking those expectations is the most common SPA failure, and it also matters for search engines.

React isn’t only for SPAs. It’s equally usable for a handful of interactive components inside a server-rendered page, or within a framework that renders on the server and hydrates in the browser. No router library is mandatory — the right choice depends on the architecture you’ve picked.

URL/products/123
ROUTERMatches the route pattern
PARAMSid = 123 extracted
COMPONENTProductPage rendered
DATAProduct fetched and displayed
How a URL maps to a route, parameters and a component in a React application
REQUESTBrowser requests a page
SERVERReact rendered to HTML on the server
HTMLMarkup sent — content visible immediately
BUNDLEJavaScript downloads
HYDRATIONReact attaches to the existing markup
INTERACTIVEFull React application running
Illustrative React server rendering and hydration workflow

RENDERING

Client Rendering, Server Rendering and Hydration

Client-side rendering sends a near-empty HTML file plus a JavaScript bundle; React builds the interface in the browser. It’s simple to host and works well for applications behind a login, where the first paint matters less than the interaction that follows.

Server-side rendering runs React on the server to produce HTML, which the browser displays immediately. The JavaScript then loads and hydration attaches React’s event handling to that existing markup, making it interactive. Static rendering does the same work ahead of time at build, serving pre-built HTML — excellent for content that doesn’t change per request.

These strategies come from frameworks built on React, not from React alone, and most frameworks let you mix them per route.

On SEO: React neither helps nor harms rankings by itself, and both “React is bad for SEO” and “React sites rank better” are wrong. What matters is whether content is in the HTML or requires JavaScript execution, whether each view has a real URL, whether titles and metadata are set per route, whether internal links are real anchors, and how the page performs. A client-rendered dashboard has no SEO requirements; a client-rendered marketing site probably has a problem. See our website SEO guides.

Aspect
Client-side rendering
Server rendering
Initial delivery
Client-side renderingMinimal HTML, then JavaScript renders
Server renderingHTML arrives rendered from the server
First paint
Client-side renderingWaits for the bundle to download and run
Server renderingContent visible sooner
Interactivity
Client-side renderingInteractive once JavaScript runs
Server renderingInteractive after hydration
Crawlability
Client-side renderingDepends on the crawler executing JavaScript
Server renderingContent present in the initial HTML
Server requirements
Client-side renderingStatic hosting is enough
Server renderingA server or rendering platform is needed
Caching
Client-side renderingBundle cached; data fetched client-side
Server renderingRendered output can be cached
Best suited to
Client-side renderingDashboards and apps behind a login
Server renderingContent that needs to be found and read quickly
Client-side rendering compared with server rendering in React applications

PERFORMANCE

React Performance

The first rule is to measure before optimising. React DevTools has a profiler showing which components rendered and why; browser performance tools show where time actually goes. Optimising on intuition usually adds complexity without improving anything.

The largest wins are usually about how much JavaScript ships, not about render counts. Code splitting with dynamic import() and lazy() loads code only when needed — route level is the natural boundary — with Suspense providing fallback UI while a lazy component loads. Suspense also coordinates fallbacks for other supported asynchronous rendering scenarios, though its exact role in data fetching depends on the framework and architecture.

For rendering work: keep state close to where it’s used so updates affect fewer components, use stable keys, avoid unnecessary effects, and virtualise very long lists so only visible rows exist in the DOM.

Memoisation — React.memo, useMemo, useCallback — is where most premature optimisation happens. Each has a cost: comparison work, memory, and code that’s harder to read. Applied everywhere by reflex, it can make an application slower and definitely makes it harder to maintain. Apply it where profiling shows a real problem: an expensive calculation, or a memoised child receiving a new function reference every render.

The current Core Web Vitals are LCP, INP and CLS — the interaction metric is INP, which replaced the older first-input metric. React affects all three: bundle size delays LCP, long rendering tasks hurt INP, and content appearing after load causes CLS. Images need correct dimensions, modern formats, responsive sizing and lazy loading below the fold. See website speed.

MEASUREProfile before changing anything
IDENTIFYFind the actual bottleneck
OPTIMISEApply a targeted fix
TESTConfirm behaviour is unchanged
MEASUREVerify it actually helped
React performance workflow from measuring through identifying bottlenecks to verifying improvements
 React — code splitting and memoisationEXAMPLE
import { lazy, Suspense, useMemo, memo } from "react";

// Load this route's code only when it is needed
const Dashboard = lazy(() => import("./Dashboard"));

function App() {
  return (
    <Suspense fallback={<Spinner />}>
      <Dashboard />
    </Suspense>
  );
}

// useMemo: only for genuinely expensive work
function Report({ rows, filter }) {
  const summary = useMemo(
    () => expensiveAggregation(rows, filter),
    [rows, filter]
  );

  return <Summary data={summary} />;
}

// React.memo: skip re-render when props are unchanged
const ProductCard = memo(function ProductCard({ product }) {
  return <article>{product.name}</article>;
});

ACCESSIBILITY & STYLING

Accessible, Responsive and Well-Styled React

React and accessibility

React makes it easy to build components that look interactive without behaving like it. A div with an onClick renders identically to a button and is invisible to keyboard and screen-reader users.

Start from semantic HTML: real button, a, label, nav and heading elements bring focusability, keyboard activation and correct roles for free. Beyond that: associate labels with inputs using useId, manage focus when opening and closing dialogs, keep focus visible, support Escape to close, give icon-only controls accessible names, update ARIA states such as aria-expanded when the visual state changes, and announce errors properly.

React provides no accessibility automatically. Test with a keyboard first — it surfaces most problems in minutes. See website accessibility.

Styling and responsive design

Several styling approaches are common: plain CSS files, CSS Modules for scoped class names, inline styles for dynamic values, CSS-in-JS libraries, and utility-class systems. They involve real trade-offs around tooling, bundle size and team preference, and none is universally correct — consistency within a project matters more than the choice.

Responsive behaviour is CSS’s job, not React’s. Media queries, container queries, flexible grids and fluid typography all work exactly as they do elsewhere. Resist the urge to branch layouts in JavaScript by measuring the window — it re-renders on resize and breaks server rendering. See responsive design and website design basics.

A design system of shared components — buttons, inputs, cards, modals, alerts — is where React and design consistency meet most productively. Accessibility fixed once in a shared Button benefits every screen.

 React — accessible componentsEXAMPLE
import { useId, useRef, useEffect } from "react";

function Field({ label, error, ...inputProps }) {
  const id = useId();

  return (
    <div className="field">
      <label htmlFor={id}>{label}</label>

      <input
        id={id}
        aria-invalid={error ? "true" : undefined}
        aria-describedby={error ? `${id}-error` : undefined}
        {...inputProps}
      />

      {error && <p id={`${id}-error`} role="alert">{error}</p>}
    </div>
  );
}

// Use real elements, and manage focus deliberately
function Dialog({ isOpen, onClose, children }) {
  const closeRef = useRef(null);

  useEffect(() => {
    if (isOpen) closeRef.current?.focus();
  }, [isOpen]);

  if (!isOpen) return null;

  return (
    <div role="dialog" aria-modal="true" aria-label="Settings">
      {children}
      <button type="button" ref={closeRef} onClick={onClose}>Close</button>
    </div>
  );
}

// Not this — invisible to keyboard and assistive tech
// <div onClick={handleClick}>Save</div>
 React with TypeScriptEXAMPLE
type UserCardProps = {
  name: string;
  role: string;
  isActive?: boolean;
  onSelect: (id: string) => void;
};

function UserCard({ name, role, isActive = false, onSelect }: UserCardProps) {
  return (
    <article className="card">
      <h2>{name}</h2>
      <p>{role}</p>
      {isActive && <span>Active</span>}
      <button type="button" onClick={() => onSelect(name)}>Select</button>
    </article>
  );
}

// Typed state
const [users, setUsers] = useState<User[]>([]);
const [status, setStatus] = useState<"idle" | "loading" | "error">("idle");

TOOLING

TypeScript, Project Structure and Testing

TypeScript is optional but widely used with React. It documents component props, catches mistakes before runtime, and improves editor autocomplete and refactoring. The benefit grows with project size and team size; a small personal project may reasonably skip it. React does not require it.

For project structure, a common arrangement separates components/, pages/, hooks/, api/ or services/, utils/, styles/ and assets/. Larger applications often group by feature instead, keeping everything for one area together. Both work; consistency matters more than the layout.

Organise components by role: shared UI components, layout components, feature components and page components. Resist abstracting a component before you have a second use for it — premature abstraction produces components with too many props and no clear purpose.

For testing, React Testing Library is the common choice, and its guiding idea is worth adopting: test what a user experiences, not implementation details. Find elements by their accessible role or label, interact as a user would, assert on what becomes visible. Tests written that way survive refactoring; tests asserting on internal state break constantly. Component and integration tests usually give the best return, with end-to-end tests for critical journeys.

ERRORS & SECURITY

Error Handling, Security and Authentication

Error boundaries catch rendering errors in their descendants and show fallback UI instead of a blank screen. They have real limits: they don’t catch errors in event handlers, asynchronous code, or the boundary’s own rendering. Those need ordinary try/catch and explicit error state.

The central security principle for React: nothing in the browser can be trusted. Frontend code is downloadable and modifiable by anyone.

That means environment variables in a frontend build are not secrets. Anything included in the bundle — however the variable is named — ships to every visitor and is readable in seconds. API keys, database credentials and private tokens belong on the server, with the browser calling your backend, which holds them.

It also means hiding a button is not authorisation. Conditionally rendering an admin panel improves the experience for people who shouldn’t see it; it stops nobody from calling the endpoint directly. Every permission must be enforced by the backend on every request.

React escapes values in JSX by default, which prevents most XSS. dangerouslySetInnerHTML opts out of that protection — the alarming name is deliberate. Use it only with content you fully control or that has been sanitised by a maintained library, never with raw user input.

Authentication follows the same logic: React collects credentials and sends them, the backend verifies and issues a session or token, React stores enough to know someone is signed in and includes credentials with requests. Every one of those checks is re-verified server-side. Serve over HTTPS and keep dependencies updated — each package runs with full access to your application.

LOGIN FORMCredentials collected in React
REQUESTSent over HTTPS to the backend
BACKENDCredentials verified server-side
SESSIONSession or token issued
REQUESTSCredentials sent with each call
AUTHORISEBackend checks permission every time
UIReact renders the permitted view
React authentication workflow connecting a frontend to a backend
 React — error boundary and safe renderingEXAMPLE
// Error boundaries catch rendering errors in children.
// They do NOT catch event handler or async errors.
class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    logToService(error, info);
  }

  render() {
    if (this.state.hasError) return <p>Something went wrong.</p>;
    return this.props.children;
  }
}

// JSX escapes values by default — this is safe
<p>{userComment}</p>

// This opts out of that protection
// <div dangerouslySetInnerHTML={{ __html: userComment }} />
// Only ever with content you control or have sanitised

// Never put secrets in frontend environment variables —
// everything in the bundle is public.
// const key = import.meta.env.VITE_SECRET_KEY;  // visible to all

ECOSYSTEM

Build Tools, npm and React Frameworks

React projects need a build step, because browsers don’t understand JSX. Node.js runs the tooling, npm (or an alternative package manager) installs dependencies and runs scripts, and package.json records dependencies, dev dependencies and scripts, while the lock file pins exact installed versions for reproducible builds.

Vite is a widely used modern build tool providing a development server with fast updates and an optimised production build. It’s a common choice rather than the only valid one — other bundlers and framework-provided tooling work fine.

React frameworks build on React to supply what it deliberately leaves out: routing, server and static rendering, data loading conventions, and deployment integration. Next.js is the best known, and router-centred architectures and other frameworks take different approaches. React does not require any of them — plenty of applications use React with a router and a build tool and nothing more. Which to use depends on whether you need server rendering, how much of the application is content, and what your team already knows. There’s no best framework, and anyone claiming otherwise is selling something.

 React — project setup essentialsEXAMPLE
// package.json (simplified)
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "test": "vitest"
  },
  "dependencies": {
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}

// Terminal
// npm install
// npm run dev
// npm run build

// A typical structure — one example, not a rule
src/
├── components/     // shared UI
├── pages/          // route-level components
├── hooks/          // custom hooks
├── api/            // request logic
├── utils/
├── styles/
├── App.jsx
└── main.jsx

PRACTICE

React Best Practices and Common Mistakes

Compose Components

Build interfaces from small, focused pieces.

Keep State Close

Store it near the components that need it.

Never Mutate

Create new objects and arrays.

Use Stable Keys

Real identifiers from your data.

Handle Loading

Show meaningful feedback while waiting.

Handle Errors

Give users something useful when things fail.

Keep Effects Focused

Synchronisation with external systems only.

Build Accessibly

Semantic HTML and keyboard support from the start.

Protect Secrets

Keep credentials on the server.

Measure Performance

Profile before optimising.

Test Behaviour

Test what users do, not internals.

Common React mistakes to avoid

Mutating State Directly

Pushing to a state array or assigning to a state object; React may not re-render.

Array Index as Key

Breaks when lists reorder, attaching state to the wrong item.

Overusing useEffect

Effects for derived values that should be calculated during render.

Ignoring Loading States

Blank screens while data is in flight.

Ignoring Error States

Nothing shown when a request fails.

All State at the Top

Every change re-renders a large tree unnecessarily.

Excessive Prop Drilling

Threading a value through many uninterested layers.

Context for Everything

Frequently changing values re-rendering every consumer.

Reflexive Memoisation

memo, useMemo and useCallback everywhere, adding cost and noise.

Giant Components

Hundreds of lines doing rendering, fetching and business logic.

Secrets in Frontend Code

Anything in the bundle is public.

Client-Side Authorisation

Hiding a button and calling it access control.

Ignoring Accessibility

Clickable divs and unlabelled controls.

Ignoring Responsive Design

Layouts only ever checked at desktop width.

Over-Fetching

Requesting more data, more often, than the view needs.

Unsafe HTML Rendering

dangerouslySetInnerHTML with untrusted content.

Optimising Without Measuring

Complexity added with no evidence of a problem.

Skipping Server Validation

Trusting client-side checks alone.

DEBUGGING

Debugging React Applications

React Developer Tools, a browser extension, adds panels for inspecting the component hierarchy with each component’s props and state, and a profiler for recording which components rendered and why. It’s the fastest way to answer “why did this re-render?” and “what props is it actually receiving?”

Standard browser DevTools cover the rest: the Console for errors, the Network tab for requests, Elements for the rendered DOM, Performance for profiling, and Application for storage inspection.

For API problems, the Network tab answers most questions: check the URL, method, request headers and body, the status code and the response. A request that never appears points to your code; a 401 or 403 points to authentication or authorisation; a 200 with unexpected shape points to the backend contract.

CORS errors come up constantly when a React app on one origin calls an API on another. The browser blocks your script from reading the response unless the server sends permitting headers. Two things follow: it’s fixed on the server, not in your fetch call, and disabling browser security or using a public proxy is not a production solution. A development proxy is fine locally; production needs correct server configuration.

01

Reproduce

Find the exact interaction that triggers it.

02

Check the Console

Read the error and the component stack.

03

Check the Network Tab

URL, method, headers, status and response body.

04

Inspect Components

React DevTools shows props, state and hierarchy.

05

Check Rendering Logic

Confirm the values actually reaching the component.

06

Check the Response

Is the data shaped as the component expects?

07

Fix

Address the cause, not the visible symptom.

08

Test

Verify success and failure paths.

09

Measure

Profile if the issue was performance-related.

UI ACTIONUser triggers a request
REQUESTCheck URL, method, headers, body
STATUS2xx, 4xx or 5xx?
RESPONSEIs the shape what you expected?
STATEDid state actually update?
UIDoes the render logic match the state?
Workflow for debugging React API requests through the browser network tab

CHECKLISTS

React Accessibility, Performance and Security Checklists

Three review passes over the same application. Practical working lists rather than formal audits — the accessibility one is not a complete WCAG assessment.

Accessibility

Performance

Security

 React accessibility, performance and security checklistEXAMPLE
ACCESSIBILITY
[ ] Semantic HTML used throughout
[ ] Keyboard navigation works
[ ] Focus is visible
[ ] Form labels associated with inputs
[ ] Accessible names on icon-only controls
[ ] Logical heading hierarchy
[ ] Meaningful alt text
[ ] Error messages are clear and associated
[ ] Focus managed in dialogs and menus
[ ] Sufficient colour contrast
[ ] Reduced motion respected
[ ] Screen reader behaviour tested
[ ] No colour-only communication

PERFORMANCE
[ ] Measure before optimising
[ ] Avoid unnecessary effects
[ ] Keep state close to where it is used
[ ] Use memoisation only where justified
[ ] Split large bundles by route
[ ] Lazy-load suitable content
[ ] Optimise images and dimensions
[ ] Reduce unnecessary requests
[ ] Handle large lists efficiently
[ ] Cache appropriate data
[ ] Profile rendering behaviour
[ ] Monitor real-world performance

SECURITY
[ ] No secrets in frontend code or env variables
[ ] HTTPS everywhere
[ ] Validate all data server-side
[ ] Enforce authorisation server-side
[ ] Avoid unsafe HTML rendering
[ ] Keep dependencies updated
[ ] Review third-party packages
[ ] Handle authentication tokens carefully
[ ] Protect sensitive API endpoints
[ ] Validate uploads on the backend
[ ] Configure CORS on the server
[ ] Avoid leaking error details to users

REFERENCE

React Cheat Sheet

A categorised quick reference for the APIs covered above. For authoritative documentation, use react.dev, and MDN for the underlying web platform behaviour.

COMPONENTfunction App() {}export default App
JSXreturn <h1>Hello</h1>{expression}className<>...</>
PROPSfunction Card({ title }){children}defaultValue = x
STATEuseState(0)setCount(prev => prev + 1){...prev, key: value}
EVENTSonClick={handleClick}onChangeonSubmitpreventDefault()
CONDITIONALcond ? a : bcond && <X />early return
LISTSitems.map()key={item.id}
EFFECTSuseEffect(() => {}, [dep])return () => cleanup()
HOOKSuseRef()useContext()useReducer()useId()
MEMOISATIONuseMemo()useCallback()memo()
DATAfetch()response.okasync / await
LAZYlazy(() => import())<Suspense fallback>
Categorised React reference covering components, JSX, props, state, events, conditional rendering, lists, effects, hooks, memoisation, data fetching and lazy loading

INTERVIEW PREP

Common React Interview Questions

Concise, technically accurate answers to the React questions that come up most often in interviews and technical assessments.

LEARNING PATH

React Learning Roadmap and Practice Projects

React sits fifth on this list for a reason. Learning it before you’re comfortable with JavaScript functions, arrays, objects, destructuring, spread, map() and promises means fighting two things at once, and most “React is confusing” frustration is actually JavaScript confusion wearing a React costume.

HTML
CSS
JavaScript fundamentals
Modern JavaScript syntax
React basics
JSX
Components
Props
State
Events
Forms
Lists and keys
useEffect
Other hooks
Custom hooks
Context
API integration
Routing
State management
Performance
Accessibility
Testing
TypeScript
React frameworks
Production projects

These are projects to build yourself rather than templates to download. Each isolates a set of techniques, and each will raise questions that reading alone never does — especially around error states and data that arrives in an unexpected shape.

Counter App

Beginner — state and event handling.

To-Do List

Beginner — arrays, forms and immutable updates.

Product Card

Beginner — props and reusable components.

Weather Dashboard

Intermediate — APIs, loading and error states.

Expense Tracker

Intermediate — forms, derived values and totals.

Product Search

Intermediate — filtering, lists and debounced API calls.

Admin Dashboard

Advanced — layouts, routing and complex state.

E-Commerce Frontend

Advanced — cart state, forms and API integration.

Headless CMS Frontend

Advanced — API-driven content and rendering strategy.

React + PHP or Laravel App

Advanced — full frontend and backend integration.

REQUIREMENTWhat the feature must do
UI STRUCTURESketch the interface
COMPONENT TREEBreak it into components
STATE DESIGNDecide what state exists and where
DATA MODELAPI shape and request logic
COMPONENTSBuild the reusable pieces
STYLINGResponsive CSS
ACCESSIBILITYKeyboard, labels, focus
TESTINGBehaviour, not internals
PERFORMANCEMeasure and optimise
DEPLOYBuild and ship
React project workflow from requirement through component design, state, styling, accessibility and testing to deployment

NEXT TOPICS

Continue Learning Web Development

HTML

Learn how HTML structures web content and interfaces.

CSS

Learn how CSS controls layouts, styling and responsive design.

JavaScript

Learn the language fundamentals behind React development.

PHP

Understand server-side programming and backend application logic.

Laravel

Explore a PHP framework for modern web application development.

Website APIs

Learn how frontend applications communicate with backend systems.

Responsive Design

Learn how to create interfaces that adapt across devices.

Website Accessibility

Learn how to build inclusive and keyboard-friendly interfaces.

FAQ

React: Frequently Asked Questions

Short answers to the questions people ask most about React and modern frontend development.

Build Better Interfaces With React

Learn how React components, JSX, state, hooks, APIs, accessibility and performance techniques come together to create modern interactive web applications.