React in 2026, what changed and what follows
React remains the most widely used library for building interfaces, while the way you write code in it has changed more over the past two years than over the previous five. Two changes matter here, and both remove work previously done by hand.
The first is a compiler that itself ensures components do not recompute needlessly. The second is components executing on the server that never send their code to the browser.
This text covers what those changes mean for the code you write today, and what deserves fixing in code written earlier.
The version situation
Start by sorting this out, since online material blends several things at once.
The stable version today is nineteen on the 19.2 line, with patches also shipping in parallel to the 19.1 and 19.0 lines. Work on the next release runs in preview versions numbered 19.3, so material describing twenty as released runs ahead of the facts.
The compiler is a separate project with its own numbering and reached version one in autumn 2025. It works alongside nineteen and is ready for production use, confirmed by large scale deployments.
Server components require a framework that supports them, since the library provides a mechanism rather than a finished solution. In practice that means Next.js or another framework supporting this model.
The compiler, or the end of manual optimisation
This is the change most affecting day to day coding, and understanding exactly what it lifts from your shoulders pays off.
For years the standard was wrapping values and functions in mechanisms caching their result, so a child component would not recompute on every parent render. Code full of such wrappers was ugly, easy to get wrong, and omitting one dependency produced a bug that was hard to trace.
const filtered = useMemo(
() => products.filter((p) => p.category === category),
[products, category]
)
const handleClick = useCallback(
(id: string) => addToCart(id),
[addToCart]
)The compiler analyses code at build time and adds those optimisations itself, based on what actually happens. The fragment above becomes ordinary code, without wrappers.
const filtered = products.filter((p) => p.category === category)
function handleClick(id: string) {
addToCart(id)
}Two caveats deserve stating. First: the compiler works correctly only on code respecting the library's rules, so a component modifying props passed from outside or mutating a variable beyond its scope gets skipped. Additional rules in the code checking tool flag such places.
Second: existing code with manual optimisations still works and needs no removal. The compiler copes with it, so cleanup deserves treating as work done in passing rather than as a separate task.
Roll it out in an existing project gradually, enabling the compiler for selected directories first. That lets you spot places breaking the library's rules without halting all other work.
Server components
The second large change concerns where a component executes, and it runs deeper than it first appears.
A server component executes on the server alone and sends the browser a description of the result rather than its code. That means a library used inside such a component, a date handling tool or a text renderer for instance, never reaches the bundle sent to the user.
async function ArticleList() {
const articles = await db.article.findMany({ take: 20 })
return (
<ul>
{articles.map((a) => (
<li key={a.id}>{a.title}</li>
))}
</ul>
)
}Note the database query performed directly inside the component. That is possible because the code never leaves the server, so credentials stay safe.
Splitting into server and client components requires a decision on every interface element, and that is where misunderstanding arises most. The rule is simple: a component needing state, event handling, or access to browser objects must be a client component. The rest can stay on the server.
In practice that means an arrangement where the server layer fetches data and builds structure, while islands of interactivity are client side and as small as possible. The button opening a menu is a client component; the whole page around it need not be.
The most common mistake is marking a component high in the tree as client side, since everything below then becomes client side too and the whole benefit disappears.
Actions and forms
A third change, quieter than the two above, concerns handling forms and operations that modify data. It deserves knowing, since it removes a fair amount of hand written code.
Previously, handling a form submission required your own state for field values, your own state for whether a submission was in flight, your own error handling, and your own button disabling. Four things to write on every form and four places to miss something.
Now you pass the submission handling function directly to the form, and the library exposes whether the operation is in flight. Disabling the button and showing a pending state become one line rather than separate state.
function SubmitButton() {
const { pending } = useFormStatus()
return (
<button type="submit" disabled={pending}>
{pending ? 'Saving' : 'Save'}
</button>
)
}A separate mechanism handles updating the view immediately, before the server confirms. An added comment appears in the list at once, and if the write fails the library restores the previous state itself. That is a pattern previously written by hand which regularly left the interface inconsistent after an error.
Watch one thing. Immediate updating suits operations that almost always succeed, liking something or adding a comment for instance. For a payment or a booking, waiting for the response is better, since showing a success that did not happen is worse than a second of waiting.
What to fix in existing code
Three patterns worth changing regardless of whether you enable the compiler, since all three cause bugs.
The first is a side effect computing a value from state and props. If a value can be computed during rendering, it needs neither an effect nor separate state. That pattern causes double rendering and a moment where the displayed value is stale.
The second is a side effect synchronising state with props. It usually means the state sits in the wrong place, and the answer is moving it up or computing it in place.
The third is passing props down through many levels. A value handed through five components, none of which uses it, points to a missing context layer or a poor component split.
All three are caught by the tool covered in the piece on React Doctor, which scans a project and returns a list of such places with concrete locations.
Application state, a decision made once
State management is an area where React deliberately imposes no solution, so the decision is yours and deserves making deliberately rather than by momentum.
The basic distinction concerns where the data comes from. Server state is everything arriving from the backend: a product list, user data, search results. Client state is things existing only in the browser: an open menu, the contents of an unsubmitted form, the selected tab.
That distinction matters more than it looks, since the two kinds have entirely different needs. Server state requires fetching, caching, refreshing, and handling network errors. Client state requires none of that and breaks when held in a tool designed for the first.
The most common mistake in projects is dumping everything into one global store. That produces a layer manually reimplementing caching and refreshing, meaning things a server state library does itself and better.
A sensible default arrangement runs like this. Data from the server is handled by a library built for it, covered in the piece on TanStack. State shared across the application, a theme or a cart for instance, goes into a lightweight store such as Zustand. Everything else stays local to the component and never leaves it.
That last part gets skipped and matters most. Whether a dialog is open need not be global, and lifting it out of the component gives nothing beyond another thing to maintain.
React against the alternatives
| Option | Strength | Weakness | Pick it when |
|---|---|---|---|
| React | Largest ecosystem, people availability, the compiler | Needs a framework for full use | A commercial project, a team to grow |
| Vue | Gentler entry curve, fewer decisions | Smaller job market | A team starting from scratch |
| SvelteKit | Least output code, no intermediate layer | Smaller library ecosystem | A project sensitive to bundle size |
| Solid | Fine grained reactivity without memoisation, similar syntax | A considerably smaller community | An interface with heavy update rates |
| Qwik | No hydration, code fetched only on click | Version 2 still in beta, a narrow ecosystem | A content site with few interactions |
The first criterion on a commercial project is mundane and decisive: people availability. A library most of the market knows means easier hiring and a better chance somebody picks the project up after you.
The second is the ecosystem. For typical needs, from forms through tables to charts, ready solutions here are more numerous and more mature than anywhere else.
The third, arguing against, is complexity. Using today's capabilities fully requires a framework, an understanding of the server and client split, and awareness of what the compiler does. On a simple site that is surplus.
Where to start learning
The learning order changed along with the library, and some material online now teaches things that stopped being necessary.
Start with components and passing props, since that is the foundation and it has not changed. Then local state and event handling, meaning everything needed to build a working interface.
Push side effects further back than most courses do. It is the most overused mechanism in the whole library, and a good share of the uses older material teaches now have better answers: data fetching is handled by a separate library or a server component, and computing values happens during rendering.
Skip manual optimisation entirely at first. The compiler handles it, so time spent learning result caching mechanisms is better spent understanding when a component rerenders and why.
Leave the server and client split until you reach for a framework. Without one the mechanism will not work anyway, and concepts introduced too early confuse the picture more than they help.
Check the date on any material you pick. A course from three years ago teaches the basics correctly and everything above them out of date, and recognising that boundary without experience is hard.
Common mistakes
The first is a side effect where a computation during rendering suffices. The most common pattern in React code and a source of double renders.
The second is marking a component high in the tree as client side. Everything below then becomes client side and the benefit of server components disappears.
The third is adding manual optimisations with the compiler enabled. It does no harm while being redundant work the compiler performs itself.
The fourth is breaking the library's rules, such as modifying passed props. The compiler skips such components, so you lose optimisation exactly where the code is most suspect.
The fifth is holding in state a value that can be computed. Every extra piece of state is another place where things can drift apart.
The sixth is relying on material describing a version that has not been released. The stable version today is nineteen on the 19.2 line, and the compiler carries its own numbering.
FAQ
Which React version is current?
Nineteen is stable on the 19.2 line, with patches also going out to 19.1 and 19.0. Further work runs in preview versions numbered 19.3, so there is no twenty. The compiler is a separate project with its own numbering that reached version one in autumn 2025.
Does the compiler replace manual optimisation?
Yes, in new code you no longer need to wrap values or functions in result caching mechanisms. Existing code with such wrappers still works and needs no removal, so clean it up alongside other changes.
Do server components require a framework?
Yes. The library supplies a mechanism, and supporting it falls to the framework. In practice that means Next.js or another solution supporting this rendering model.
When must a component be client side?
When it needs state, event handling, or access to browser objects. The rest can stay on the server. Mark the lowest possible element in the tree as client side, since everything below inherits that marking.
Is React worth starting a new project with?
On a commercial project usually yes, mainly for people availability and ecosystem maturity. On a simple informational site or a project sensitive to bundle size, lighter options deserve consideration.
Documentation sits on the project site, and the release history in the versions list.