Chapter 13: React and the UI as a Function of State
React is the most influential frontend library of the past decade. The decade is long enough now that the influence is worth describing precisely.
React’s central idea wasn’t JSX. It wasn’t the virtual DOM. It wasn’t even components. React’s central idea was that the UI should be expressed as a function of state, and that the framework should be responsible for keeping the rendered output and the application state synchronized. Before React, most frameworks treated the DOM as something the application reached into and modified imperatively. After React, the dominant mental model was that the developer described what the UI should look like for a given state, and the framework figured out how to make the DOM match.
This was a real architectural shift. It changed how a generation of developers thought about frontend code, what kinds of products were practical to build, and which problems felt easy versus hard. It’s also a shift the rest of this book has to engage with carefully — because the cost of React’s approach is not zero, and the architectural questions it doesn’t answer turn out to matter more than the architectural questions it does.
Where React Came From
Section titled “Where React Came From”React was built at Facebook in 2011 by Jordan Walke, an engineer on the Facebook Ads team. The original internal project was called FaxJS; an iteration of it that became more recognizable as React was first deployed on Facebook’s Newsfeed in 2011 and on Instagram in 2012 (Facebook had acquired Instagram in April 2012; React was one of the technologies the Instagram web team adopted shortly after).
The Newsfeed work mattered. Facebook’s product engineers had been struggling with the complexity of synchronizing the news feed UI against an increasingly complex data layer. The number of derived states the feed had to maintain (read/unread, like states, comment counts, hover states, drafts) had exceeded what manual DOM manipulation could comfortably keep consistent. Walke’s prototype let the team describe what each feed item should look like for a given data state and re-render when the state changed. The approach worked. The team kept building on it.
The publicly visible debut came at JSConf US 2013, held in Florida that May. Walke and his colleague Pete Hunt presented a talk titled JS Apps at Facebook, and Tom Occhino followed with a separate talk titled Rethinking Best Practices. The audience response was, by the polite standards of conference talk reactions, hostile. The reaction had two main objections.
The first was JSX. React’s recommended syntax for describing UI mixed JavaScript and HTML-like markup in the same file, which contradicted a decade of separation of concerns orthodoxy. CSS in separate files; HTML in separate files; JavaScript in separate files. Mixing markup into JavaScript looked, to many developers in 2013, like a regression to the worst patterns of early 2000s web development.
The second was the virtual DOM. The idea of producing a tree of objects describing what the UI should look like, then diffing that tree against the previous tree, then applying the changes to the real DOM, struck many engineers as an obvious performance loss compared to direct DOM manipulation. The argument that the diffing was fast enough in practice, and that the architectural clarity made up for any overhead, took years to fully land.
The JSConf US 2013 talks are still online and worth watching if you’re curious about how the React team made the case in the moment. The talks are also worth knowing about because the controversy is itself a piece of the story. React was a deliberate provocation. The design choices were chosen partly because they were uncomfortable in the way the team thought the field needed to be uncomfortable. Hunt’s Rethinking Best Practices title was the thesis.
JSX as a Deliberate Provocation
Section titled “JSX as a Deliberate Provocation”JSX was the most-discussed piece of the React design, and the design choice was explicit about what it was rejecting.
In a typical JSX file, the component definition looks like this:
function Cart({ items }) { const subtotal = calculateSubtotal(items)
return ( <section> <h2>Cart</h2> <p>{items.length} items</p> <p>Subtotal: {subtotal}</p> <button disabled={items.length === 0}>Checkout</button> </section> )}The HTML-like syntax compiles to JavaScript function calls — every JSX tag becomes a React.createElement() call — but the source code reads as markup interspersed with logic. The argument the React team made for this design was that the markup and the logic that produces it are inseparable. A list of cart items is a transformation of an array of data; trying to express that transformation across two separate files (an HTML template plus a JavaScript controller) creates a fictional separation that obscures the relationship.
Sebastian Markbåge, who joined the React team in 2013 and became one of its most influential architects, has written and spoken about the JSX decision over the years. The position is essentially that separation of concerns in the original 1970s sense — keep the parts of a system separate so they can be reasoned about independently — is being misapplied when developers use it to mean put HTML in one file and JavaScript in another. The HTML and the JavaScript in a typical UI component are coupled by the data they share. Putting them in separate files is filing them by language, not by concern. The real concerns are the data, the rendering logic, and the user interactions, and those should live together.
Whether this argument is correct is still debated. The argument is at least coherent, and the field has largely accepted it in practice. Vue’s single-file components, Svelte’s .svelte files, and Solid’s JSX all use variations of the colocated markup and logic approach. Even template-first frameworks like Astro use a hybrid model. The dominant style of frontend authoring in 2025 is closer to JSX’s design than to the 2010 separation-of-files orthodoxy.
The Virtual DOM
Section titled “The Virtual DOM”The virtual DOM is the other React design choice that took years to be widely understood.
Most accounts of the virtual DOM emphasize the performance angle. The framework keeps a lightweight JavaScript representation of the UI in memory. When state changes, the framework re-renders the affected components, producing a new virtual DOM tree. The framework then diffs the new tree against the previous tree and applies only the necessary changes to the real DOM. The performance argument is that real DOM updates are expensive, and the diffing is cheap enough to make the net cost lower than doing manual mutations.
The performance argument is true at certain scales and not at others. A small interaction in a small application doesn’t benefit much from the virtual DOM. A complex interaction in a large application can benefit substantially. The benchmark answers shift depending on which framework you compare and which workload you run. Chapter 14 (Vue, Solid, Svelte) describes some of the alternatives that achieve similar UI ergonomics with different — sometimes faster — runtime characteristics.
The deeper value of the virtual DOM was architectural, not performance. It gave React a predictable update model. The developer described the UI declaratively; the framework decided how to apply the updates. The developer didn’t have to write the imperative first hide the old element, then show the new element, then animate, then remove the listeners sequence by hand. The framework reduced a class of bug — I forgot to update this part of the UI when that part changed — to almost zero.
At the time, this was a real fix. Manual DOM synchronization at scale was an error-prone, hard-to-reason-about discipline. Even the MVC frameworks of Chapter 11, with their two-way binding and observable properties, ran into edge cases where the developer had to think carefully about how state changes propagated. React’s render-from-state model replaced that thinking with a simpler rule. If the state is correct, the UI is correct. The framework handles the rest.
Components as the Unit of Authoring
Section titled “Components as the Unit of Authoring”The third major contribution was making components the dominant unit of frontend authoring.
A React component combined markup, behavior, local state, and rendering logic into a single function (or class, in the original API). Components could be composed into trees. Data flowed down through props. Events flowed up through callbacks. Reusable interfaces became practical to build — a single <Button> component could be used in fifty places in an application, with its visual design, its accessibility semantics, and its click handling all consistent.
The component model wasn’t new in 2013 — the libraries in Chapter 10 had widget systems with similar properties — but React’s specific approach was clean enough to be widely adopted. The component became the unit teams talked about. Design systems were structured as component libraries. Storybook (introduced in 2016) became the tool for developing components in isolation. The whole frontend industry shifted to thinking about UIs as compositions of components.
This was good. It made large interfaces tractable. It gave teams a shared vocabulary. It created a reusable artifact (the component) that could be shared between projects.
It also turned out to be the source of one of React’s deeper architectural problems, which the chapter has to land honestly.
The Component Trap
Section titled “The Component Trap”Components became so central that they started absorbing everything.
Need analytics? Add a hook to the button. Need permissions? Wrap the form in a permission-checking component. Need feature flags? Use a feature-flag provider higher in the tree. Need data fetching? Add a query hook. Need storage? Add a storage hook. Need a router? Use route components. Need observability? Add a logging hook. Need notifications? Use a notification context.
Each of these patterns is reasonable in isolation. Together they push every product capability into the render tree, which means every capability has to be expressed as a React concept — a hook, a component, a context, a provider, a higher-order component. The component tree becomes not just the UI structure, but the dependency graph, the service locator, the side-effect map, the policy layer, and the application runtime.
A typical React component in a production application now imports analytics hooks, permission hooks, feature-flag hooks, validation hooks, form-state hooks, query hooks, mutation hooks, observability hooks, and audit hooks — and renders a single button. The component is modular in files but not in responsibility. The button knows about everything.
This is the architectural cost the book has been preparing to land. UI organization and product-capability organization are different problems. React solved the first one cleanly. It made the second one harder by encouraging every capability to express itself in the framework’s vocabulary, which means every capability ends up entangled in the render tree.
Kitsune’s approach to this problem — separating intent (declared on the component through metadata attributes) from consequence (handled by modules subscribed to the runtime) — is one of the book’s central architectural arguments. The hook-based React way of solving the same problem isn’t wrong. It’s just one of many possible designs, and it pulls in a direction the rest of the book is going to push back against.
The Class-to-Hooks Transition
Section titled “The Class-to-Hooks Transition”React’s other canonical breaking-change moment came in October 2018, when the team introduced Hooks at the React Conf in Henderson, Nevada.
Before hooks, React components were either function components (simple, stateless render functions) or class components (full React.Component subclasses with state, lifecycle methods like componentDidMount, and instance methods). The class-component API had been the framework’s primary model since the beginning. By 2018, the React team had concluded that classes were the wrong abstraction. The lifecycle method names (componentDidMount, componentDidUpdate, componentWillUnmount) made the code’s structure depend on when things happened rather than what they were doing. Sharing logic between components required higher-order components or render props, which produced deeply nested wrapper hell in real applications. Class binding semantics caused subtle bugs.
The hooks proposal — useState, useEffect, useContext, useMemo, useCallback, and a small set of others — let function components have state, lifecycle, and effects without classes. The same logic that previously needed a class component could be expressed in a function. The structural complaints about classes were largely addressed.
The transition was, by framework-breaking-change standards, gentle. React supported both class and function components, and the team explicitly committed to keeping classes working indefinitely. The framework’s documentation, examples, and ecosystem migrated to hooks, but applications didn’t have to migrate to keep working.
The catch was that new React idioms diverged from old ones. A team’s existing class-component codebase still worked, but the new patterns the field developed for data fetching (useQuery, useMutation), state management (Zustand, Jotai), and side effects (useEffect patterns) were all hooks-based. Maintaining a class-component codebase past 2020 meant maintaining a codebase that was structurally different from the rest of the React ecosystem. Most teams chose to migrate, which meant rewriting every component over a period of months or years.
The hooks transition is the second canonical framework breaking change parable in this book (the first was Angular 1 to Angular 2 in Chapter 11). It’s milder than the Angular case — class components still work — but the structural pressure to rewrite was real. Anyone who ran a large React application in 2018–2022 has the experience of having rewritten a substantial fraction of their code to chase the framework’s new conventions.
The pattern is worth holding onto. A framework that decorates the platform imposes less of this pressure. A framework that replaces the platform imposes more. React is firmly on the replace side. The class-to-hooks transition is the kind of churn that doesn’t happen to code written against the platform itself.
Decoration Versus Replacement, Honestly
Section titled “Decoration Versus Replacement, Honestly”The book’s decoration-versus-replacement framing has to be applied to React directly, because it’s the canonical example.
A React-rendered <button> isn’t a <button> in the way a server-rendered HTML page produces one. It’s a React element, scheduled into a virtual DOM tree, reconciled with the real DOM through the framework’s internal scheduler, and updated whenever the surrounding component re-renders. You can target the resulting DOM node with document.querySelector and read its current state, but if you try to imperatively manipulate it — change its disabled attribute, set its textContent, move it in the DOM — React’s next render will overwrite your changes. The framework owns the rendering.
This is the canonical replacement abstraction. React has its own opinions about state management, its own opinions about lifecycle, its own opinions about how data flows through the component tree, and its own opinions about which DOM operations are safe to perform from outside the framework. The platform’s primitives still exist underneath — React eventually produces real DOM elements, which fire real events, which interact with real CSS — but the layer the developer programs against is React’s reality, not the platform’s.
This isn’t a critique of React’s engineering. The framework is exceptionally well-built; the team’s design choices are mostly defensible; the result has been a productive substrate for thousands of products. The critique is structural. A replacement abstraction creates a dependency the application can’t easily shed. Code written against React only works if React keeps working — and the React that works today is not the React that worked in 2014, and the React that will work in 2030 might not be the React that works today.
Several other frameworks in this era made different choices. Vue (Chapter 14) sits between decoration and replacement. Solid is closer to decoration. Svelte is firmly decoration (the compiler eliminates most framework abstractions before runtime). Lit (Chapter 45) is decoration as a design principle. None of these alternatives is obviously better than React in every dimension. Each one has trade-offs. The point isn’t to declare a winner; the point is to make the trade-off visible, so that teams choosing React are doing so knowingly rather than by default.
React Native and the Cross-Platform Claim
Section titled “React Native and the Cross-Platform Claim”One piece of React’s story that’s worth a brief mention is React Native.
In March 2015, Facebook released React Native at the React.js Conf in Facebook’s Menlo Park headquarters. The framework let developers write React components that rendered to actual native UI components on iOS and Android, instead of to web DOM elements. The pitch was learn once, write anywhere — develop in the React mental model, ship to mobile platforms without needing a separate native engineering team.
React Native was technically credible and saw significant adoption. Facebook (now Meta) used it for parts of the Facebook and Instagram mobile apps. Discord, Coinbase, Bloomberg, Shopify, and many other significant products built on it. The community was strong; the framework’s bridge layer between JavaScript and native components was sophisticated; the developer experience was, for teams already invested in React, much better than maintaining separate native codebases.
The trade-offs were real. React Native applications behaved a little less like fully-native apps than Apple’s or Google’s own frameworks (Swift/SwiftUI; Kotlin/Jetpack Compose) produced. Performance for complex interfaces was sometimes a concern. The bridge between JavaScript and native components added overhead. The framework went through several internal architecture rewrites over the years (the recent New Architecture and Fabric renderer) to address these issues.
For this book’s argument, React Native is significant for one specific reason. It’s another data point in the web technologies as the cross-platform UI substrate story that Chapter 18 made the central argument of. React Native ships UI written in a web-flavored model (React components, JSX, the same mental model as React on the web) to native mobile devices. It’s another vote, by the industry, for the same conclusion. The skill set built around web UI development is the skill set the field has been investing in, across every platform that runs code at all.
What React Got Right
Section titled “What React Got Right”Before the chapter ends, the credit is worth landing clearly.
UI as a function of state is a profoundly useful mental model. The class of bugs it eliminated — the I forgot to update this part of the UI when that part changed category — was real, common, and expensive. Every framework that has come since (Vue, Angular post-2, Svelte, Solid, Lit) has adopted some version of the same model. React isn’t the only path to it; it’s the path the industry took.
Components as the unit of authoring is a productive abstraction. It scales reasonably well. It supports reuse, isolation, and team coordination. The fact that it doesn’t solve all of frontend architecture (the component trap above) is a limit, not a refutation.
Declarative rendering — describing what the UI should look like, rather than the sequence of operations needed to produce it — is the right shape for most UI work. The platform itself has been moving toward declarative APIs for years (the <dialog> element, the popover API, container queries, view transitions). React was an early and influential vote for this approach, and the platform has been catching up.
Strong tooling matters. React’s developer tools, React DevTools, the documentation, the ecosystem, the conference talks, the educational content — collectively, the project produced one of the best-supported developer experiences in any frontend technology, ever. The bar React set for what good tooling looks like has influenced every framework since.
This is the reverence beat the chapter has to land. The work Walke and Hunt and Occhino and Markbåge and the React team did at Facebook was world-class engineering at the edge of what was possible with the platform of 2013. The framework they built has been the substrate of an enormous amount of real product work over the past decade. The critique the book builds on top of this work isn’t a critique of the work itself. It’s a critique of the world the work was built for, which has changed underneath it — and a description of what the architectural alternatives look like now that the platform has caught up.
What Comes Next
Section titled “What Comes Next”The next chapter is about the frameworks that pushed back on React’s defaults — Vue, Solid, Svelte, and the broader reactivity counter-argument. After that, Chapter 15 picks up the framework story with the meta-framework era, where the SPA model React popularized started running into its own limits at scale.
Exercise: Imperative vs. Render-from-State
Section titled “Exercise: Imperative vs. Render-from-State”Build the same small interactive list in two styles.
The requirements: render a list of books, add a book, select a book, delete a book, show the selected book’s details in a side panel, and disable the delete button when no book is selected.
First implementation: imperative DOM updates. State stored in local variables; DOM nodes located and updated as state changes.
Second implementation: render from state. A single state object, a render(state) function that produces the UI, and a re-render after each state change. The render function can return an HTML string, a DOM fragment, or a template literal — the technique doesn’t matter for this exercise. What matters is that the function describes the entire UI as a function of the state.
After both implementations work, answer:
- Which version was easier to write? Which was easier to read after writing?
- In which version was it easier to add a feature — say, sorting the list?
- Where did the event handling live in each version?
- Where would analytics live? Storage? Permission checks?
- What did the render-from-state model solve? What did it not solve?
The point isn’t to declare a winner. The point is to feel React’s core insight — that if the state is correct, the UI is correct — and to recognize that the insight is independent of any particular framework. You can use the render-from-state model with React, with Vue, with Lit, with platform-native templates, or with a hundred lines of vanilla JavaScript. The framework is the implementation. The insight is the architecture.