modern-react-spa

Chapter 01

React Evolution at a Glance (v15 → v18)

React's history at a glance — v15, v16 (Fiber), v17, v18 — what carried forward into 19.2, and what got deprecated.

Published 2026-05-23

🎯 Chapter Goal — After this chapter you can place any React API on a timeline (v15 / v16 / v17 / v18), recognise what carried forward into 19.2 and what didn’t, and stop confusing eras when reading legacy code or old blog posts.

🧭 Prerequisites — None. Skim before Ch 2.


🔹 1.1 React 15 era (2016)

The world before hooks. Components were class components, or stateless function components for read-only render.

// React 15-style
const Counter = React.createClass({
  getInitialState() { return { count: 0 }; },
  componentDidMount() { /* … */ },
  render() {
    return <div>{this.state.count}</div>;
  },
  mixins: [PureRenderMixin],   // ← extinct
});

Things that existed in 15 and don’t anymore:

  • React.createClass — replaced by ES6 classes, then by function components.
  • Mixins — composition via shared methods. Replaced by HOCs, then render props, then hooks.
  • React.PropTypes — moved to its own prop-types package.

Carried forward: the basic component / JSX / state / lifecycle mental model.


🔹 1.2 React 16 era (2017–2019) — Fiber

The under-the-hood rewrite. React’s reconciliation algorithm moved from synchronous recursion to Fiber — a cooperatively-scheduled, interruptible work loop. Same API for the user; entirely different runtime.

New APIs that landed in 16:

  • ES6 class components (the class extends React.Component style) replaced createClass.
  • Error boundaries — class components with componentDidCatch / getDerivedStateFromError.
  • Fragments<>…</> syntax; sibling elements without a wrapper.
  • PortalsReactDOM.createPortal(child, container); render into a different DOM subtree.
  • Hooks (16.8, Feb 2019) — the inflection point. useState, useEffect, etc. Function components became first-class.
  • forwardRef — pass refs through wrapper components.
  • React.memo — function-component equivalent of PureComponent.
  • Context (modern)React.createContext API replaced the old childContextTypes system.
  • Suspense (for lazy components) — React.lazy() + <Suspense fallback>.

Why hooks won so fast: they collapsed three separate concerns (state, lifecycle, code reuse) into one mental model. Class components still work, but new code is hooks.

// React 16.8+ idiom
const Counter = () => {
  const [count, setCount] = useState(0);
  useEffect(() => { /* mount */ return () => { /* unmount */ }; }, []);
  return <div>{count}</div>;
};

🔹 1.3 React 17 era (2020) — the boring release

By design. Zero new developer-facing features. The point was:

  • Gradual upgrades — 17 made it possible to embed React 17 in a React 16 page (and vice versa). Big-org migration support.
  • New JSX transformimport React from 'react' no longer required in every file. Bundler-level setting flipped this on.
  • Event delegation moved from document to the React root — small but consequential for embedding.

If you skipped 17 in real-time, you didn’t miss features. You missed groundwork for the next two majors.


🔹 1.4 React 18 era (2022–2024)

Concurrent rendering shipped for users in 18. Fiber had been there since 16; 18 exposed the user-facing primitives.

Headline APIs:

  • Concurrent rendering — React can interrupt rendering work to handle higher-priority updates.
  • Automatic batchingsetState calls in promises, timeouts, native handlers are batched. Previously only batched inside React event handlers.
  • useTransition — mark a state update as non-urgent. UI stays responsive during heavy renders.
  • useDeferredValue — debounce a value to be used in render.
  • useId — stable IDs for SSR/hydration without collision.
  • useSyncExternalStore — the bridge for external state libraries (Zustand, Redux). Removed an entire class of tearing bugs.
  • useInsertionEffect — for CSS-in-JS libraries; fires before layout effects.
  • Suspense for data (with frameworks) — Suspense started being usable for async data, not just lazy components.
  • createRoot replaced ReactDOM.render. Old API still works, gated to legacy mode.
  • Strict Mode double-invoke in dev — runs effects + state setters twice in dev to surface impurity bugs.
// React 18 idiom
const App = () => {
  const [filter, setFilter] = useState('');
  const [isPending, startTransition] = useTransition();

  const onChange = (next: string) => {
    setFilter(next);                              // urgent
    startTransition(() => recomputeExpensive(next)); // non-urgent
  };

  return /* … */;
};

Strict Mode in 18 became actively useful. Effects firing twice surfaced bugs around cleanup, idempotence, and missing dependencies — bugs that were hiding under “it works in dev because dev runs once.”


🔹 1.5 What carried forward; what got deprecated

Carried forward into 19.2

  • The whole hooks API (useState, useEffect, etc.).
  • Strict Mode double-invoke.
  • createRoot, <Suspense>, useTransition, useDeferredValue, useId, useSyncExternalStore.
  • Fragments, portals, error boundaries.
  • The new JSX transform (import React is optional).
  • React.memo (still works; usually unnecessary after the compiler — Ch 2 §2.1).
  • Context (still works; carefully — Ch 13 §13.6).

Deprecated / removed by 19.2

  • React.createClass (gone in 16, didn’t survive past then).
  • Mixins (replaced by hooks; PureRenderMixin etc. are extinct).
  • String refs (ref="foo"); use callback refs or useRef.
  • componentWillMount, componentWillUpdate, componentWillReceiveProps — removed (UNSAFE_ aliases linger for backward compat).
  • findDOMNode — gone in 19.
  • Default export of Children API; helper utilities discouraged.
  • forwardRef for new components — refs are props in 19 (Ch 2 §2.7).
  • react-helmet-async for metadata — native in 19.2 (Ch 2 §2.4).

The big mental shift across the eras

React 15:  classes + mixins; sync rendering
React 16:  classes (or fn) + hooks; sync from outside, Fiber under the hood
React 17:  no new surface; gradual-upgrade plumbing
React 18:  function components + hooks; concurrent rendering exposed
React 19:  function components + hooks + compiler; refs are props; native metadata

If you learned React after 16.8 and skipped the classes era, you saved no time — but you also miss the why behind some current APIs (the “why does memo even exist?” question makes more sense if you’ve seen shouldComponentUpdate).

🪤 Common Pitfalls (in legacy migration)

  1. Treating class components as urgent removals — they still work; migrate when it’s natural.
  2. Translating lifecycle methods 1:1 to useEffect — sometimes the right answer is a different pattern (data layer, store).
  3. Keeping forwardRef after migrating to 19 — refs are props now (Ch 2 §2.7).
  4. Removing <StrictMode> because “it makes effects fire twice” — fix the effects.
  5. Trusting blog posts without an era marker — patterns from 2019 may not apply.

✅ Recap

  • React 15 → 16 → 17 → 18 → 19.2: each era has identifying APIs.
  • Hooks (16.8) was the inflection point.
  • 17 was about plumbing, not features.
  • 18 exposed concurrent rendering and the modern data-fetching story.
  • 19 closes the loop: compiler, native metadata, refs as props.

🔗 Further Reading

  • React blog archive — each major version’s announcement post.
  • Dan Abramov — “Things I don’t know as of 2018” / “Before You memo()” / various era-pieces.
  • Ch 2 — what’s new in 19.2 (the destination of this whole evolution).

In the book — not on the site

Each topic has an 🧠 Under-the-hood subsection — the algorithm, the data structures, what React DevTools surfaces, debugging recipes. Plus a 🧪 hands-on lab per chapter with a starter repo. Reserved for the book.

Topics in this chapter (5)
  1. 1.1 React 15 era (2016)
  2. 1.2 React 16 era (2017–2019) — Fiber
  3. 1.3 React 17 era (2020) — the boring release
  4. 1.4 React 18 era (2022–2024)
  5. 1.5 What carried forward; what got deprecated