This is part of an ongoing series I am writing as I work my way through the modern web stack from a WordPress developer’s perspective. The series is aimed at WordPress veterans who, like me, have built things on the web for years and feel quietly behind the curve. The goal is broad literacy, not deep mastery. By the end you should be able to read any modern stack list and know what each piece is doing.
Each post comes with an audio companion (10-15 minutes, generated via NotebookLM) for gym or commute listening. Press play below if that suits you better than reading.
Hook
For about a year I avoided React. Every explanation I read was written by someone who had never touched WordPress, and the analogies were all wrong. Components were “like Lego bricks.” State was “like a database, but inside your browser.” JSX was “just HTML with JavaScript powers.” None of it stuck because none of it connected to anything I already knew.
Then I sat down with the actual React docs and realised something quietly obvious. React is a more disciplined version of what WordPress theming already taught you. Template parts. get_template_part arguments. A loop that re-renders when the data changes. You have been doing this for years. The vocabulary is different and the JavaScript is fancier, but the bones are the same.
This module is the translation layer. If you have ever written a WordPress theme, you already understand React. You just have not had it spelled out yet.
Core Concept
A React application is a tree of components. A component is a function that takes some input and returns markup. The framework re-runs the function whenever the input changes, and re-renders the affected parts of the page. That is the whole idea.
Three pieces to internalise: components, props, and state. Everything else in React is variations on these three.
Components Are Template Parts That Decided to Grow Up
In WordPress, a template part is a chunk of HTML in its own file (template-parts/content.php, template-parts/header.php) that you pull into a parent template with get_template_part(). It exists so you do not have to repeat yourself across the loop, the single post template, the archive template, the homepage, and so on.
A React component is the same idea, with two differences. First, the markup and the logic live in the same file. Second, the component is itself a JavaScript function, which means it can hold internal state, run side effects, and react to events without bolting on jQuery.
The simplest possible React component:
<span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> <span class="fu">PostCard</span>({ title<span class="op">,</span> excerpt }) {</span>
<span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (</span>
<span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> <span class="kw"><article</span> <span class="ot">className</span><span class="op">=</span><span class="st">"post-card"</span><span class="kw">></span></span>
<span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> <span class="kw"><h2></span><span class="va">{</span>title<span class="va">}</span><span class="kw"></h2></span></span>
<span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> <span class="kw"><p></span><span class="va">{</span>excerpt<span class="va">}</span><span class="kw"></p></span></span>
<span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> <span class="kw"></article></span></span>
<span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> )<span class="op">;</span></span>
<span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a>}</span>Then you use it elsewhere:
<span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="fu"><PostCard</span> <span class="ot">title</span><span class="op">=</span><span class="st">"Hello world"</span> <span class="ot">excerpt</span><span class="op">=</span><span class="st">"My first post"</span> <span class="fu">/></span></span>That is the React equivalent of:
<span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw"><?php</span> get_template_part(<span class="st">'template-parts/post-card'</span><span class="ot">,</span> <span class="kw">null</span><span class="ot">,</span> [</span>
<span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a> <span class="st">'title'</span> => <span class="st">'Hello world'</span><span class="ot">,</span></span>
<span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a> <span class="st">'excerpt'</span> => <span class="st">'My first post'</span><span class="ot">,</span></span>
<span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a>])<span class="ot">;</span> <span class="kw">?></span></span>Same idea. Different syntax. The thing in the angle brackets that looks like HTML is called JSX. It is not HTML. It is JavaScript syntax that gets compiled into function calls before it ever reaches the browser. You can drop JavaScript expressions inside it with curly braces, which is what {title} is doing.
Props Are What You Pass In
The argument to your component function is called props (short for properties). It is a plain JavaScript object with whatever keys you decide to support. In WordPress this is the $args array you pass into get_template_part(). In React it is the first parameter of the component function.
<span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> <span class="fu">UserBadge</span>({ user<span class="op">,</span> size <span class="op">=</span> <span class="st">'medium'</span> }) {</span>
<span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a> <span class="cf">return </span><span class="kw"><span</span> <span class="ot">className</span><span class="op">=</span><span class="va">{</span><span class="vs">`badge badge--</span><span class="sc">${</span>size<span class="sc">}</span><span class="vs">`</span><span class="va">}</span><span class="kw">></span><span class="va">{</span>user<span class="op">.</span><span class="at">name</span><span class="va">}</span><span class="kw"></span></span><span class="op">;</span></span>
<span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>}</span>The default-value syntax (size = 'medium') is the same idea as PHP’s default function arguments. Props are read-only inside the component. If you need to change something, you reach for state.
State is the Data the Component Remembers Between Renders
The deepest mental shift from WordPress is here. In a WordPress template, there is no “remembering.” Each page request starts fresh. The template runs, outputs HTML, dies. If you want to remember something between requests, you put it in the database or the session.
In React, the same component might re-render dozens of times during a single page view. The user clicks a button, the count goes up. The user types in a search box, the filtered list updates. The component function runs again from the top each time. So how does it remember the current count, or the current search query, between runs?
The answer is the useState hook:
<span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="im">import</span> { useState } <span class="im">from</span> <span class="st">'react'</span><span class="op">;</span></span>
<span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span> <span class="fu">Counter</span>() {</span>
<span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">const</span> [count<span class="op">,</span> setCount] <span class="op">=</span> <span class="fu">useState</span>(<span class="dv">0</span>)<span class="op">;</span></span>
<span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a></span>
<span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (</span>
<span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a> <span class="kw"><button</span> <span class="ot">onClick</span><span class="op">=</span><span class="va">{</span>() <span class="kw">=></span> <span class="fu">setCount</span>(count <span class="op">+</span> <span class="dv">1</span>)<span class="va">}</span><span class="kw">></span></span>
<span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a> Clicked <span class="va">{</span>count<span class="va">}</span> times</span>
<span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a> <span class="kw"></button></span></span>
<span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a> )<span class="op">;</span></span>
<span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a>}</span>useState(0) says “give me a piece of memory, initialised to zero, and give me a function I can call to update it.” When you call setCount(count + 1), React schedules a re-render. The function runs again, but useState returns the new value this time. The component shows the new number.
This is foreign to PHP brains because PHP scripts do not have a “between runs” to remember anything in. The closest analogue is a JavaScript closure or a long-running PHP process with persistent variables, neither of which is how WordPress works.
There are other hooks (useEffect for side effects, useMemo for caching computations, useContext for sharing data across the tree), but useState is the one you need to understand first. Everything else follows the same pattern: call a function starting with use, get back a value and maybe an updater.
The WordPress Analogue
Mental model: React components are template parts that own their own state. Props are
$args. TheuseStatehook is the database-and-session combo, except it lives in browser memory and only persists until the page is refreshed.The most important shift: WordPress renders the page once per request and then forgets everything. React keeps the application alive in the browser and re-runs your component functions every time the data they depend on changes. Once that clicks, the rest of React is just patterns for managing what data changes when.
The Landscape
React itself is one library. Around it has grown an ecosystem of tools you will see referenced together as if they were a single thing. A rough map:
| Tool | What it does | When you meet it |
|---|---|---|
| React | The core library: components, props, state, hooks | Every modern frontend job ad in 2026 |
| JSX | Syntax extension that lets you write HTML-like markup inside JS | Used by basically every React project |
| Next.js | A “meta-framework” built on React; adds routing, server rendering, file structure | The default React framework for full apps (covered in Module 4) |
| React Router | Just the routing piece, for projects that do not want a full meta-framework | Single-page apps and tools |
| Remix | A meta-framework that merged into React Router 7 in 2024 | Older codebases that have not migrated yet |
| Preact | A lightweight React alternative with the same API | Used by Astro by default; useful when bundle size matters |
| Vue, Svelte, Solid | React’s competitors | Different ecosystems, similar mental model |
If you are reading this for jobs or client work, learn React first. It is the lingua franca. Vue and Svelte have devoted communities and arguably nicer ergonomics, but React is what 70 percent of the modern frontend job market expects.
Two more terms you will keep meeting. Server components are React components that run on the server, return HTML, and cannot have client-side state. Client components are the ones I described above. Next.js mixes both. Module 4 covers what that means in practice.
What This Changes for WordPress People
If you can write a WordPress theme that uses ACF custom fields, conditional template loading, and a custom loop, you can write React. The hard part of WordPress development was never the templating. It was understanding the lifecycle: when each hook fires, what is in scope, what filters apply. React has its own lifecycle (mount, render, re-render, unmount), but it is dramatically simpler and better documented.
Three things become possible once React clicks.
The first is you can read any Next.js, Remix, or Astro repo. The component code dominates these codebases, so once you can parse a component file you can mostly navigate the project.
The second is custom Gutenberg blocks become approachable. WordPress’s block editor is built on React. If you have ever wanted to ship a custom block beyond what the editor allows out of the box, this is the unlock. The block API is awkward but the React inside it is the same React you would use anywhere else.
The third is your AI-paired workflow improves. AI agents generate React code constantly. Without React fluency you cannot evaluate, modify, or debug what they produce. With it, you can iterate fast.
Watch Out
A few traps that catch WordPress developers on first contact with React.
className, not class. Because class is a reserved word in JavaScript, JSX uses className for CSS classes. Same for htmlFor instead of for. Small, annoying, easy to forget for a year.
Lists need a key prop. When you render a list of components from an array, each item needs a unique key prop so React can track which is which between renders. Forgetting this works fine most of the time and then breaks in confusing ways when items reorder.
The infinite re-render trap. If you update state inside the render function without guarding it, the component re-renders, which updates state again, which triggers another render. Browsers crash. React’s useEffect exists to handle “do this once when the component appears” patterns safely.
State is local until you make it not. A useState value lives inside one component. If two siblings need to share state, you have to lift the state up to the common parent and pass it down as props. WordPress does not have this problem because state lives in the database. React forces you to think about where state belongs.
Hooks must be called in the same order every render. You cannot put useState inside an if statement. The linter will yell at you. This is the rule that catches the most beginners.
Going Deeper
If you want to graduate from “I can read React” to “I can write React,” a few resources worth your time.
YouTube, gym or commute friendly:
- “React in 100 Seconds” by Fireship, followed by “Learn React” by Scrimba (free, interactive). The 100-second version for orientation, the Scrimba course for the actual skill.
- “The React Hooks Crash Course” by Web Dev Simplified (~30 min). Goes through
useState,useEffect, anduseContextwith examples. Watch on a phone, then sit down and try the patterns yourself.
Official docs worth bookmarking:
- react.dev. The new official React docs are genuinely excellent. The “Learn React” tutorial is the path I would take if I were starting from zero.
- react.dev/reference. The API reference. Search for a hook, get a clean explanation with examples.
- Patterns.dev. When you start writing real React, this is the site you reach for to learn how the patterns fit together.
Next post in the series introduces Next.js, the meta-framework that turns React from a UI library into a full app development platform. If WordPress was your CMS, Next.js wants to be your everything.

Leave a Reply