React's Identity Crisis: Library or Framework?
Ask a React developer whether React is a framework and you will get the ritual answer: “Actually, React is a library.” A library for building user interfaces. It renders components. Everything else is your problem.
For years that line has worked like a get-out-of-jail card. State management is clunky? Not our job, we’re a library. No real styling story? Not our job. Performance falls over unless you memoise everything by hand? Well, here’s another hook. Every gap in React gets handed to the community to fill, and then the community’s patch becomes “the React way.”
Meanwhile, React has quietly been acting like a framework anyway. Its own docs now tell you to start new projects with a framework like Next.js. It retired Create React App. It ships a compiler. So which is it? A library that is not responsible for anything, or a platform that dictates how you build? React wants the authority of a framework with the accountability of a library. That is the identity crisis.
Svelte made the opposite choice. It owns the whole developer experience, and it shows.
State: outsourced from day one
React gives you useState for local state and useContext for passing things down. The moment your app has shared state that changes often, you discover the limits. Context re-renders every consumer when its value changes, so it is fine for a theme and painful for a shopping cart.
So the community built the missing piece. Then built it again. Redux, with its actions, reducers, dispatchers and boilerplate. Then Redux Toolkit to hide the boilerplate. Then MobX, Recoil, Jotai, Zustand, Valtio, and whatever launches next month. Each one is a fix for a problem React declined to solve.
That is not a thriving ecosystem. It is a symptom. When every serious React project has to pick a third-party state library, state management is not optional. It is a missing feature.
Styling: React never had a plan
CSS has one hard problem at scale: everything is global. React’s answer has always been, essentially, a shrug. You get a className prop and an inline style object, and good luck.
There is no way to write a component’s styles in the same file and have them scoped to that component. So the community stepped in, again. CSS Modules. Styled-components and Emotion, which put your CSS inside JavaScript strings and paid a runtime cost for it. Then, as server components made runtime CSS-in-JS awkward, a big swing toward Tailwind.
Tailwind is not a React project, and plenty of people like it for its own sake. But its popularity in React-land is no accident. When your framework offers no scoped styles, stuffing utility classes into your markup is the path of least resistance. Your component ends up with a className longer than its logic, not because that is good design, but because React never gave you anything better.
The re-render model and the hook explosion
This is the root of most of React’s pain. When state changes, React re-runs your whole component function, and by default every child below it too. It then diffs a virtual DOM to work out what actually changed.
That design has consequences, and each consequence got a hook:
- Recalculating expensive values on every render?
useMemo. - Functions recreated every render and breaking child memoisation?
useCallback. - Children re-rendering for no reason?
React.memo. - Need a value that survives renders without causing one?
useRef. - Side effects that must run after render, with a dependency array you must keep perfectly in sync by hand?
useEffect, the single most misused API in frontend. - Then
useLayoutEffect,useReducer,useTransition,useDeferredValue,useSyncExternalStore,useOptimistic,useActionState…
Many of these exist not to add features, but to work around the cost of re-rendering. And the latest fix proves the point. React Compiler, which reached 1.0 in October 2025, automatically inserts the memoisation developers were told to write by hand for years. It is a genuinely impressive piece of engineering. It is also a build step whose main job is to paper over the runtime model.
Svelte takes responsibility
Svelte calls itself what it is: a framework with a compiler. And because it owns the whole stack, it solves these problems at the source instead of outsourcing them.
Here is the same small component in both. First, React:
import { useState, useMemo } from 'react';
import styles from './Counter.module.css'; // separate file for scoped styles
export function Counter() {
const [count, setCount] = useState(0);
const doubled = useMemo(() => count * 2, [count]);
return (
<button className={styles.button} onClick={() => setCount((c) => c + 1)}>
{count} x 2 = {doubled}
</button>
);
}
Now Svelte 5:
<script>
let count = $state(0);
let doubled = $derived(count * 2);
</script>
<button onclick={() => count++}>
{count} x 2 = {doubled}
</button>
<style>
button {
padding: 0.5rem 1rem;
} /* scoped to this component automatically */
</style>
The differences add up fast:
- Fine-grained reactivity. Svelte does not re-run your component. When
countchanges, only the bits of the DOM that depend on it update. No virtual DOM, no dependency arrays, nothing to memoise. - State that scales. The same
$staterune works in a plain.svelte.jsmodule, so shared state is just an exported object. No Redux, no provider tree. - Scoped CSS in the same file. Write normal CSS in a
<style>block and it only applies to that component. No extra library, no class-name soup. - The batteries are included. Transitions, animations, stores and two-way binding ship with the framework. SvelteKit is the official, first-party app framework for routing and server rendering.
Svelte is not magic. It simply decided that the developer experience is the framework’s job, not the community’s.
To be fair
React is not going anywhere, and it has real strengths. Its ecosystem is enormous, the job market is huge, React Native lets teams share skills with mobile, and almost any problem you hit has already been answered somewhere. Its minimal core also means large companies can build exactly the architecture they want.
Svelte has had its own churn, too. The move from Svelte 4’s implicit let reactivity to Svelte 5’s runes was a real migration for existing apps. And its ecosystem, while growing, is smaller.
But none of that changes the core point. React’s gaps are not accidents of youth. They are the result of a decade of saying “that’s not our job.”
Pick a lane
There is nothing wrong with being a small library. There is nothing wrong with being a full framework. What is frustrating is being both whenever it is convenient: a framework when it comes to telling you how to structure your app, a library the moment someone asks why state, styling and performance are still someone else’s problem.
Svelte picked a lane. It took responsibility for the whole developer experience, and the result is less code, less tooling and fewer arguments about which of twelve state libraries to install. React could learn something from that. Frankly, with a compiler and a recommended framework already in hand, it is halfway there. It just needs to admit what it is.