React vs Vue 3: state, rendering and team decisions
React fits a team with established React components and delivery practices. Vue 3 can fit a team that works more effectively with templates and tracked reactive dependencies. For an existing product, integration and migration costs usually outweigh syntax preference; the worked scenarios below explain when the choice changes.
React function components and Vue 3 Composition API, covering state updates, asynchronous cleanup, server rendering, integration and migration.
Both build component-based interfaces. React exposes state snapshots for each render; Vue tracks reactive dependencies. Compare those mechanisms alongside the application and team that will maintain them.
Compare the practical consequences
Scroll sideways to compare all columns on a smaller screen.
React and Vue 3 by decision criterion
Criterion
React
Vue 3
What changes the decision
Team fit
Can the team reason about render snapshots, state ownership, keys, and Effects?
Can the team reason about refs, proxy reactivity, computed values, and watcher cleanup?
Pair on the same representative feature; review the code after a few days, not only during a demo.
State and effects
Are effects tied to external synchronization? Can derived values be calculated during render?
Are computed getters pure? Are watchers narrow and cleaned up?
Trace one update, one stale async request, and one unmount.
Rendering and delivery
Which React framework and server APIs meet the route, caching, and deployment needs?
Which Vue framework or SSR setup meets those same needs?
Render a real route on the server, hydrate it, and check failure and cache behavior.
Integration
Do existing components, design systems, routing, and data clients fit?
Can Vue use the existing page shell and services without a second system boundary?
Build the integration seam, not a standalone counter.
Testing
Can behavior be tested without depending on hook implementation details?
Can behavior be tested without depending on proxy internals or watcher timing?
Test user-visible behavior, async cleanup, keyboard use, and error states.
Migration
What existing React state and behavior must remain stable?
What needs a formal boundary if Vue is introduced alongside it?
Estimate parity work, dual-runtime support, and rollback at the route level.
Performance
What is slow in a representative React build and runtime?
What is slow in a representative Vue build and runtime?
Use identical data and interactions; measure real builds and user paths. No result is assumed here.
How state changes: render snapshots and reactive dependencies
React handlers read the state snapshot from the render that created them; use updater functions when queued updates depend on one another. Vue 3 computed getters track reactive reads and recompute lazily after a dependency changes.
The two systems both re-render affected UI from state, but they expose different programming models. Understanding the model matters more than translating one API name into another.
Both expressions read the same count captured by that render. Starting from zero, the next render receives 1, not 2. If each update depends on the result of the previous update, use the updater form: setCount(current => current + 1) twice; the next render then receives 2. React documents state as a snapshot: calling a setter requests another render; it does not mutate the variable in the running handler. State is associated with a component’s position and identity in the UI tree, so moving or replacing a component can preserve or reset state depending on that tree position and its key.
Useful when: the team wants state transitions to be explicit in event handlers and render functions, and is comfortable tracing closures, component identity, and queued updates.
Failure to watch for: code that assumes a setter immediately changes a captured variable, or code that stores duplicate derived state and then synchronizes it with an Effect. React’s guidance is to calculate values during render when they can be derived from props and state, reserving Effects for synchronization with external systems.
Vue 3’s reactive objects use proxies, and ref() uses a reactive .value property. Reading reactive state inside a computed getter tracks dependencies; writing to a tracked property triggers dependent work. A computed property is for a pure derived value and is cached until its dependencies change. A watcher is for a side effect that should react to a specific state change, such as aborting or starting a request. They are not interchangeable concepts: React’s Effect and Vue’s watcher have different dependency rules and lifecycle semantics. For example, React Effects declare dependencies from the values they read and return cleanup; Vue watch() names the watched source and accepts cleanup as a callback argument. With an async request, either system must prevent an old response from overwriting newer state.
Useful when: a team wants dependencies collected from reactive reads and likes Vue’s template-plus-script organization. The Composition API also lets related state and behavior live together in composables.
Failure to watch for: destructuring a primitive property out of a reactive object into a plain local variable can lose the reactive connection. Also, a deep watcher over a large object can traverse too much; watch a specific getter or ref when that is the intended dependency.
The trace shows why two value-form React updates from the same render produce 1, while two updater-form calls produce 2. The Vue computed value first reads 24, then after quantity changes its next read is 36. The model matters more than translating API names.
Illustrative, not executed. React and Vue runtimes were not available for this prototype.
Follow-up: Should every update go in a reducer or global store, or should API work go in a computed getter?
No. Keep state near its owner until coordination needs a shared source of truth. Keep computed getters pure and use a watcher or React Effect to synchronize with an external system, with cleanup and stale-result protection.
How to prevent an older request overwriting a newer one
Only the newest query may publish results; aborting is best-effort, so guard publication as well.
Suppose a search box requests results for "rea", then quickly changes to "react". The first request may finish last. The correctness rule is the same in both frameworks: only the newest query may publish results. The cleanup mechanism differs.
In React, an Effect can return cleanup that aborts an AbortController or marks that request generation as stale. In Vue, a watch(query, callback) can register cleanup through its onCleanup argument, then abort the previous controller before the watcher reruns. Vue 3.5 also offers onWatcherCleanup() with a synchronous-registration constraint. Aborting is best-effort: a response may already have arrived or a non-fetch operation may not support cancellation, so make the publication check explicit as well. Keep event-driven submission in the event handler when the request belongs to a specific submit action rather than to every query change.
text · Worked example
query changes to "rea" start request A
query changes to "react" invalidate A; start request B
request B finishes publish B
request A finishes late ignore A (and abort it if supported)
A newer query may finish before an older one. Cleanup should abort when possible and publication must still check that the result belongs to the current query. React Effect cleanup and Vue watcher cleanup have different dependency rules.
Illustrative trace, not executed.
Follow-up: Which cleanup model is safer?
Neither by API name alone. Match the trigger and lifetime to the interaction, then test out-of-order completion.
A greenfield operations console with an experienced React team
With an experienced React team, established React integrations, and no special rendering constraint, keep React as the starting choice. Validate that fit on a real filtering and editing path, then switch only if ownership or integration evidence favors Vue.
The team already maintains React applications, relies on React-specific component and data tools, and has an established build and release path. The new console needs interactive filtering and editing, but no requirement forces a different rendering architecture.
Recommendation: start with React. The reason is the team’s existing ability to build and support it, not an assumed runtime advantage. Prototype one representative screen that includes loading, empty, error, keyboard, and edit states. Check that derived values stay derived, that Effects synchronize only with real external systems, and that component state remains correctly owned.
What could reverse the choice? If the maintainers who will own this console have much deeper Vue 3 experience, or if it must be embedded into a Vue application with shared state and routing, Vue may lower the ongoing integration cost. If the React team’s preferred libraries are not compatible with the deployment or accessibility needs, the familiarity argument weakens.
Failure case: choosing React because “we already know it,” then discovering that the team does not know the chosen router, data-fetching strategy, server-rendering mode, or test setup. Framework familiarity does not substitute for a complete application architecture.
Interview follow-up, answered. What would you prototype before committing? A real user path with one form, asynchronous data, validation, a failure/retry state, and keyboard interaction. Ask the same people to build and review both slices only if they can do so competently. Compare implementation clarity, integration fit, accessibility, and maintenance tasks. Do not compare unrepresentative counter demos.
A public editorial site that needs useful HTML before client code runs
Treat initial HTML and delivery operations as the requirement, then select an SSR or static-generation setup that meets it. React and Vue can both participate; framework choice alone does not provide data loading, caching, or deployment behavior.
The product’s first page view should expose meaningful content to users and crawlers before the client application finishes loading. The team is deciding between React and Vue, not between SSR and no SSR.
Recommendation: decide rendering and deployment requirements first, then compare SSR-capable frameworks that meet them. React provides server-rendering APIs, and React’s client hydrateRoot attaches to server-generated markup. Vue documents SSR and hydration as well. A framework-level comparison must include data loading, routing, caching, streaming or static generation where relevant, and how the deployment runs the server. A React or Vue choice by itself does not supply the full content-delivery architecture.
What could reverse the choice? If these pages are mostly stable documentation or marketing content, static generation may be simpler than rendering each request. If only a small content section needs server-rendered output, a server-owned page with a bounded interactive widget can be enough. If every visitor sees different personalized data, request-time rendering and cache boundaries matter more.
Failure case: adding a client-side loading spinner and assuming a crawler or a slow-device user will wait for a later API response. Conversely, adopting SSR for a small internal tool with no first-content constraint can add server operations and hydration problems without meaningful benefit.
Interview follow-up, answered. What causes hydration mismatches? The server and client render different initial trees or values. Common causes include browser-only APIs used during server rendering, random values, time-zone differences, or invalid HTML that the browser reparses. Make the initial server and client state agree, and isolate browser-specific work to client lifecycle points.
A mature React product with a proposed Vue rewrite
Keep the mature React application while you identify the actual source of pain. If a platform constraint or measured ownership case supports Vue, prove it in one bounded route with a parity gate before expanding.
The application has years of React behavior, tests, design components, and integrations. A team member proposes a rewrite because Vue feels easier for a new screen.
Recommendation: do not rewrite the product for syntax preference alone. First identify the concrete problem: slow delivery, repeated state bugs, inaccessible components, hard-to-test boundaries, or an operational limitation. Fix it in the existing architecture if the root cause is local. If a new independently deployed surface can be isolated, a Vue slice may be a reasonable experiment, but define its route, data contract, styling boundary, error reporting, and ownership before mixing runtimes.
What could reverse the choice? A long-lived team transition, a verified reduction in delivery or maintenance cost from a bounded pilot, or a platform constraint that React cannot meet may justify migration. The evidence should come from a representative slice and include migration and support costs, not just initial authoring time.
Failure case: counting rewritten components but ignoring feature parity, regressions, accessibility review, data migration, dual-build tooling, duplicated dependencies, and the period when two frameworks must be maintained. A migration that never reaches parity increases cost while leaving both systems in production.
Interview follow-up, answered. How would you plan a migration if there is a real reason? Write down the desired outcome and a measurable stop condition; map routes and state boundaries; choose one low-risk, representative slice; preserve behavior with tests; measure build, runtime, accessibility, and maintenance; then decide whether to expand. Keep the old path available until the new one meets the agreed acceptance checks.
Run the same customer-search exercise in both frameworks
Compare a small feature with deliberately awkward timing: two overlapping searches, a cleared input and navigation away before the response arrives. Judge the observable behavior and the maintenance work, rather than lines of code.
Use the same mocked endpoint, customer rows and keyboard requirements in both implementations. Start search A, then search B. Resolve B first and A last. The screen must keep B’s results. Clear the input while a third request is pending; its eventual response must not refill the list. Navigate away and verify that pending work cannot publish into the abandoned view.
In React, inspect the Effect’s dependencies, cleanup and request identity. In Vue, inspect the watcher’s source, cleanup registration and request identity. In both, distinguish asking the transport to abort from preventing a stale response from publishing. Test a transport that ignores cancellation so the ownership guard has to do its job.
Add loading, empty and failed states without replacing the labelled search input or losing keyboard focus. Have another engineer change the result shape and add a retry action. Record which state transitions were missed, how easy the tests were to understand and whether the existing component library covered the interaction. These observations are useful evidence for this team and feature; they do not establish a universal framework winner.
Stay with the existing framework when the exercise reveals a repairable state-ownership or testing problem. Reconsider when a required integration cannot be supported, the responsible team cannot maintain the implementation, or a measured product constraint remains unmet after a fair pilot. Include migration and dual-runtime costs in that decision.
Behavior trace · Worked example
Input A → request 1 starts
Input B → request 2 starts
Response 2 → display B
Response 1 → keep B
Clear input → display empty state
Leave page → pending responses cannot update the view
The test controls response order instead of relying on network speed. Both implementations must satisfy the same visible contract before their ergonomics are compared.
Illustrative acceptance trace, not an executed browser test.
Follow-up: What evidence would justify reversing the initial choice?
A required integration, support responsibility or measured product constraint that the pilot cannot satisfy. A shorter demo or a personal syntax preference alone does not account for migration, testing and maintenance costs.
These are Hacker News job posts that mention these technologies, not employee earnings or a measure of what a skill adds to pay. The amounts summarize annualized advertised minimums in approximate USD. Groups can overlap and differ in roles, locations and dates.
Approximate USD using ECB daily reference rates as of 2026-10-09. Original currencies and offer ranges remain on the source listings. These figures cover posts tagged React, rather than every job in the broader technology family.
Approximate USD using ECB daily reference rates as of 2026-10-09. Original currencies and offer ranges remain on the source listings. These figures cover posts tagged Vue.js, rather than every job in the broader technology family.
Middle-half salary ranges on one shared scale
The band runs from the 25th to 75th percentile; the vertical marker is the median. Both rows use approximate USD per year.
React $150,000 median
Vue.js $120,000 median
$90,000$170,000
7 posts mention both technologies. That is 1.7% of React posts and 13.7% of Vue.js posts. A post can belong to both groups. This overlap does not show that employers prefer one technology, and the two salary groups are not independent samples.