Why Frontend Is Hard After React: State, Effects, Boundaries and AI Tools
React made UIs easier to build, not easier to design. Where state lives, tangled effects, system boundaries and AI-generated code are the real frontend work.
This article is published in English.
Sources: React Isn’t the Hardest Part of Frontend Development
Some frontend developers can stand up a React component in twenty minutes, then lose the next three hours working out why it's now impossible to maintain. The culprit isn't JSX. It isn't useState, and it isn't really React either. What makes frontend work hard is the stuff around the framework: knowing how a product ought to behave, choosing where state lives, coordinating async calls, drawing component boundaries, coping with quirks of the browser, keeping interfaces accessible and fast, and leaving code that still makes sense half a year later.
React made building interfaces easier. It never made frontend engineering easy, and the field has spent too long acting as if it had.
What React fixed, and what it left alone
Before React took over, the difficulty was of another kind. Developers edited the DOM by hand and wired up event handlers one at a time, and they struggled to keep the screen in step with application data. As apps grew, tracing how user actions, data and rendered elements related to each other got harder and harder.
React offered a helpful mental model: the UI is a function of state. Instead of instructing the browser on each DOM change, you describe what the interface looks like for a given state. Components set boundaries, props gave explicit inputs, state represented data that changes over time, and declarative rendering stripped out a lot of accidental complexity. That was a real step forward.
Still, a framework only solves what it was built to solve. React has no opinion on whether an app needs five pieces of global state or fifty. It won't say when a component is doing too much, whether your API design is sensible, whether spinners sit in the right places, or whether a checkout flow makes sense to a shopper. And it certainly can't decide what the product should do when the connection drops mid-request. All of those are frontend problems too.
An ordinary feature meets reality
Take a plain dashboard. A user opens it, the app fetches data, they change a filter, results refresh, they pick an item, a modal opens, they edit and save, the server replies and the dashboard updates. Nothing about that sounds hard.
Now add what actually happens. The first request fails. The filter changes while an earlier request is still in flight, and the older response lands after the newer one. The modal gets opened from a deep link. The page is refreshed mid-edit. Two tabs are open. The session expires. The API hands back partial data. The server accepts a mutation but the client never sees the reply, and the user clicks save twice. The list has 10,000 rows, the connection is slow and mobile, the interface has to work from a keyboard, and the error message must tell people what they can do next.
At that point, the React code is the least interesting part. All of this can be built with React, and all of it can be built badly. Many developers miss that difference.
The job is managing complexity
A frontend developer's work isn't mainly writing components. It's absorbing complexity so the user sees something simple, which is much harder. A well-made component might run fifty lines, but getting to fifty and not five hundred takes a series of decisions:
- Where the data comes from, who owns it, how long it lives and who may change it
- What happens when a request fails
- Whether the state should survive navigation or a refresh
- Whether it belongs in the URL, in local component state or in a server cache
- Whether two components should share the data itself or just compute similar values on their own
React answers none of these. The developer does, and the answers compound. A weak decision rarely stays inside one component. It turns into a pattern, other developers copy it, and before long the app has a consistent architecture that no one ever chose. That's how frontend codebases become painful: decisions accumulate, and React's complexity has little to do with it.
State as a modeling problem
A quick way to spot an immature frontend design is to look at its state. Heavy use of useState isn't wrong in itself. The trouble is treating state as a place to put values and not as something you model. A developer sees a value that changes and reaches for the hook.
const [value, setValue] = useState(...)
It feels finished, yet how to store the value isn't the real question. Whether the value should exist at all is. Look at this trio:
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [fullName, setFullName] = useState("");
Three pieces of state now hold information that could be derived from two. React isn't to blame here. The mental model is.
The same thing happens at scale. Teams keep API responses, filtered copies of those responses, loading flags, derived counts, selected records, modal flags and duplicate versions of one entity in several places. In time nobody can say which copy is authoritative, and bugs follow. One component changes a copy while another reads a different one. A synchronization effect tries to align them, and another effect reacts to the first. Suddenly the browser contains a small distributed system. React didn't build that architecture, and the people writing the code did.
Effects expose the design
If one React feature takes more blame than the rest, it's useEffect, and the criticism is often fair. But an effect is usually where an architectural problem shows up, not where it starts.
Picture a component with a handful of them: one fetching data, one syncing state, one reacting to a prop change, one writing to local storage, one firing analytics and one resetting something when a condition flips. Each looks reasonable alone. Together they form a dependency graph that's hard to follow. The developer adds dependencies until the linter goes quiet, or drops some because the effect fires too often, or adds a boolean ref to stop a double run. Eventually the component works for the scenario being tested, and nobody is quite sure why.
That's not a React flaw. It's unclear ownership and tangled side effects, and React just gives them somewhere to live.
The browser is one node in a bigger system
Frontend work keeps widening. A modern app talks to APIs and databases, auth systems and analytics, plus feature flags, payment providers and other third-party services. Add edge functions, server components, background jobs and, increasingly, AI-backed services. The browser is just one participant in a far bigger system, so a frontend developer must grasp more than rendering, including what happens before and after a request leaves the page.
That doesn't mean everyone must become a backend specialist. It means the old frontend/backend line is getting less useful. Someone who doesn't know HTTP will eventually fight caching. Someone unfamiliar with databases will struggle to reason about consistency, and someone who doesn't understand authentication will stumble over session behavior. Race conditions and retries confuse developers with no distributed-systems background, and those who don't understand users will ship elegant interfaces that nobody enjoys. The framework is a single layer.
AI changes the economics, not the hard part
AI coding assistants have shifted what frontend work costs. Components, forms, design-to-JSX conversion, hooks and tests all get cheaper to produce. That can sound like a threat. It's better seen as a test of where the value ever was. If your main contribution is typing JSX fast, AI is a problem. If it's an understanding of systems, architecture, product behavior, tradeoffs and users, AI alters your workflow and leaves the difficult part in place, and probably raises its importance.
An assistant can draft three plausible versions of a feature in seconds. Choosing among them is an architectural call and not a syntax one. It can produce a state management setup, but should that state be global? It can write an optimistic update, but should the app really behave optimistically? It can add a cache, but what's the invalidation strategy? It can write an effect, but should the effect exist? The faster code appears, the more judgment is worth, and that will likely define frontend work over the next few years.
Strong frontend developers tend to think less about React and more about boundaries. They ask where responsibility belongs and notice when a component is turning into a small application. They challenge duplicated state, plan for failure before the happy path, know what the API actually guarantees and care about unexpected user behavior. They can tell when an abstraction helps and when it only hides complexity. They understand the browser, tune performance without optimizing everything prematurely, and, above all, can explain why the system works as it does. That's a far harder skill to automate, and it lasts longer than knowing the newest React API.
Framework knowledge expires
Developers don't discuss this enough: framework knowledge has a shelf life. The React API learned five years ago isn't quite today's. Best practices shift, libraries vanish, build tools change, rendering strategies evolve, server capabilities grow and new frameworks arrive. Syntax moves, yet the underlying engineering problems stay remarkably steady:
- Modeling state and moving data through a system
- Handling failure and managing asynchronous work
- Avoiding needless coupling
- Making a system observable and an interface accessible
- Keeping complexity under control
These problems outlasted jQuery, Angular and Backbone, and survived the early React ecosystem. They'll outlast whatever follows. So learning frontend purely through frameworks is a fragile plan. Learn the framework, but don't mistake it for the discipline.
React may someday stop being the center of the conversation. Something might replace it, several approaches might coexist, or it might stay dominant far longer than expected. That doesn't matter much. Developers whose identity rests on React APIs will have to rebuild it, while those who learned how interfaces really work will adapt.
React is the tool, not the craft
Frontend development was never fundamentally about React. React is a very good tool for expressing frontend applications, and it's still a tool. The hard part is deciding what to express: how pieces interact, what happens when things break, where state lives, what runs in the browser versus on the server, how the system grows with the product, and how to hide all of that from the person using it. That's the job, and JSX is just how part of it gets typed.
As tools get better at producing code, producing code is worth less. That doesn't end software development, though it changes what developers are paid to think about. Someone who can generate a component quickly is useful, and someone who can judge whether it should exist is more so. The same goes for API integrations: generating one is useful, but spotting that the contract itself is wrong is worth more. A developer who can ask AI to refactor a codebase has value, and one who can tell whether the proposed refactor improves the system has more.
So nobody should fret over React getting easier. That's the aim, because a framework's purpose is cutting needless work, and an experienced developer uses the time saved on problems that count.