Vue Composition API vs. React Hooks

Summarize this blog post with:

TL;DR: Vue’s Composition API provides automatic dependency tracking through its reactive system, making it ideal for template-driven applications. React Hooks use explicit dependency arrays within function components, offering more control and a JavaScript-first approach. Choose Vue for simplicity and automatic reactivity, and React for explicit state management and functional patterns.

Why React Hooks and Vue Composition API behave differently in practice

React and Vue both solved the same problem: how to manage state and side effects in function-based components, but they took fundamentally different paths.

React Hooks were introduced in React version 16.8 and have since become the standard pattern for managing state and side effects in function components. With Vue 3, Vue.js responded with its own Composition API, a built-in, function-based pattern that extracts reactive logic and shares it across components using composables.

This article compares how each API works, where each excels, and the practical trade-offs that affect maintainability and debugging. The goal is a clear, actionable guide for teams to decide how to design and share reusable logic.

What is the Vue Composition API ?

The Vue Composition API is a built-in pattern in Vue 3 that structures component logic inside a setup() function. It is built on top of Vue’s reactivity core, which tracks dependencies automatically and triggers updates when reactive sources change.

How does it work?

  • The setup() function runs once per component instance and returns refs, computed values, and methods for the template.
  • The ref and reactive hold state; computed derives values; watch and watchEffect control side effects.
  • Vue tracks reads to reactive sources and re-runs dependents automatically, no manual dependency declaration needed.

Where it’s used: Template-first applications, teams migrating away from Vue Options API or mixins, and projects where two-way binding and declarative templates are central.

Syncfusion React UI components are the developers’ choice to build user-friendly web applications. You deserve them too.

What are React Hooks?

React Hooks are function-based primitives for managing state, lifecycle, and side effects inside function components. Stateful logic is shared through custom hooks, plain JavaScript functions that call built-in hooks.

How does it work?

  • Function components re-execute on every render; hooks are called in a consistent order to preserve state associations.
  • The useState stores local state; useEffect schedules side effects after render; useMemo and useCallback handle memoization.
  • The Custom hooks rely on JavaScript closures and explicit dependency arrays to avoid stale state.

Where it’s used: Render-driven apps, teams that prefer explicit control over effect timing, and projects that share logic through plain functions across diverse build setups.

Vue Composition API vs React Hooks: Technical comparison

FeatureVue Composition APIReact Hooks
Execution modelThe setup() function runs once per instance.Component re-runs on every render.
Reactive primitivesref, reactive, computed — auto dependency tracking.useState, useReducer, useMemo — explicit triggers.
Side effectswatch (explicit) and watchEffect (auto-tracking)useEffect with dependency arrays and cleanup.
ReusabilityComposables (framework-aware)Custom hooks (plain JS/TS functions)
Template bindingv-model and direct ref binding.JSX with explicit state and callback wiring.
Dependency trackingInferred automatically by the reactivity system.Declared manually in dependency arrays.
TypeScriptTyped refs and composables.Typed custom hooks with explicit return shapes.

Way of rendering

The most fundamental difference between these two APIs is their execution model, and understanding it explains most of the behavior differences that follow.

React: Re-render re-executes the component

React re-executes the entire function component on every render. The following example demonstrates how useState and useEffect work together, with useEffect updating the document title on every render.

import React, { useState, useEffect } from 'react';

function Example() {

const [count, setCount] = useState(0);
 useEffect(() => {  
    document.title = You clicked ${count} times ; 
 });

return (
  <div><p>You clicked {count} times</p>
   <button onClick={() => setCount(count + 1)}>
     Click me
   </button>
  </div>
);

/**
 * when you click 10 times,
 *  document title and paragraph will be displayed as "You clicked 10 times."
 */
 
}

export default Example;

Way of rendering in React Hooks

VueThe setup() function runs once, reactivity handles updates

Vue executes the setup() function only once per component instance. It acts as a one-time entry point, not a function that re-runs on updates. The following example defines a reactive price value; watchEffect logs it whenever it changes without re-running the entire setup.

<script>
 import { watchEffect, ref, defineComponent } from "vue"; 
 export default defineComponent({ 
   name: "Store",
   setup() { 
    const price = ref(10); 
    watchEffect(() => console.log(price.value)); 
    return { 
     price 
    }; 
   },
 });
</script>

<template>
  <h3>Price</h3>
  <input v-model="price" id="price" placeholder="edit me">
</template>

Way of Rendering price value

React enforces two rules to keep hook execution predictable:

  • Hooks must be called at the top level.
  • They should not be called inside loops, conditions, or nested functions.

Vue’s setup() function sidesteps this entirely; it runs once, before the component is created.

State declaration and binding

Declaring state

React’s useState function returns a two-member array: the current value and a setter function as shown in the below code example:

function Example() { 
 
  const [count, setCount] = useState(0);
  return ( 
    <div>
       <p>You clicked {count} times</p>
       <button onClick={() => setCount(count + 1)}>
         Click me
       </button>
    </div> 
  );

}

A to Z about Syncfusion’s versatile React components and their feature set.

Vue uses ref inside setup(), no setter function needed, you can update the value directly. Vue also groups multiple state values into a single return, making it easier to scan as the component grows.

Here’ s the code example for quick integration:

<template>
 <p>You clicked {{count}} times</p>
 <button @click="count += 1">
     Click me
 </button>
</template>

<script>
import { ref } from "vue";

export default {
  name: "Hooks",
  setup() {
    let count = ref(0);
    return {
     count,
    };
  },
};
</script>

Implementing Reactive Objects

Scaling to multiple values

For three variables, the difference in verbosity is already visible, as illustrated in the code example below.

React often becomes more verbose because each state value has its own setter.

// React — three separate declarations, each with a setter
const [name,setName] = useState('Apple');
const [price,setPrice] = useState(20);
const [quantity,setQuantity] = useState(100);

Vue commonly groups refs in a single return:

// Vue — grouped in one return
setup() {
  return {
    name:ref('Apple'),
    price:ref(20),
    quantity:ref(100)
  };
}

Updating state

React uses controlled inputs: an onChange event reads the input and explicitly calls the setter.

Refer to the following example code.

function Example() {
  const [name,setName] = useState('Apple');
  return (
    <form>
      <input type="text" value={name} onChange={e => setName(e.target.value)} />
      <h2>My favorite fruit is {name}</h2>
    </form>
  );
}

Vue uses v-model, a single directive that handles both reading and writing. This removes the explicit wiring and makes templates more concise.

Try this in your code:

<script>
import { ref } from 'vue';
export default {
  setup() { return { name: ref('Apple') }; }
}
</script>

<template>
  <form>
    <input type="text" v-model="name" />
    <h2>My favorite fruit is {{ name }}</h2>
  </form>
</template>

Handling side effects

React: useEffect

The useEffect function controls when an effect runs through its dependency array:

useEffect(() => { }); // Runs on every render
useEffect(() => { }, []); // Runs only on first render
useEffect(() => { }, [value]); // Runs on first render and when `value` changes

Vue: watch and watchEffect

Vue provides two separate primitives with distinct behaviors:

  1. watch() is explicit and lazy. It runs only when the declared source changes.
    watch(refA, () => {
      console.log(refA.value); // Runs only when refA changes
    });
  2. watchEffect() is automatic and immediate. It runs on mount and re-runs whenever any reactive value read inside it changes, with no manual dependency declaration.
    watchEffect(()=>{
      console.log(refA.value); // Runs immediately and on every refA or refB change
      console.log(refB.value);
    });

The watchEffect starts immediately; watch runs lazily. Use watch when you need the previous value or want to control exactly when effects fire.

Reusable logic: Composables vs. custom hooks

Both APIs support extracting shared logic into reusable units. The usage pattern is nearly identical, the difference is the reactive primitive and template vs. JSX binding.

Vue composable:

// useCounter.js
import { ref } from 'vue';
export function useCounter(initial = 0) {
  const count = ref(initial);
  function inc() { count.value++; }
  return { count, inc };
}
<!-- Counter.vue -->
<script setup>
import { useCounter } from './useCounter';
const { count,inc } = useCounter(0);
</script>

<template>
  <button @click="inc">Count: {{ count }}</button>
</template>

React custom hook:

// useCounter.js
import { useState } from 'react';
export function useCounter(initial = 0) {
  const [count, setCount] = useState(initial);
  const inc = () => setCount(c => c + 1);
  return { count, inc };
}
// Counter.jsx
import { useCounter } from './useCounter';
export default function Counter() {
  const { count,inc } = useCounter(0);
  return <button onClick={inc}>Count: {count}</button>;
}

Common pitfalls

  • Stale closures (React): If a value used inside the useEffect is not listed in the dependency array, the effect reads an outdated version of it. Always include every value the effect depends on. Wrap callbacks passed as props in useCallback to keep references stable.
  • Misusing .value (Vue): The ref values must be accessed with .value in JavaScript. Templates unwrap refs automatically, but scripts do not. Forgetting .value in a composable is the most common Vue Composition API mistake.

Testing and debugging

Test composables and hooks as isolated units using Vue Test Utils or React Testing Library. Mock reactive sources where needed.

For debugging:

  • You can use Vue Devtools to inspect reactive dependency graphs and watch triggers since the setup() function runs once, initialization bugs are easy to isolate.
  • Use React DevTools to inspect hook state and render frequency. Stale closure bugs are the most common issue, so check dependency arrays first.

When to prefer one over the other

Use caseRecommendedReason
Template-rich apps with two-way bindingVue Composition APIv-model and direct ref binding reduce boilerplate.
JS-first teams preferring functional patternsReact HooksHooks are plain functions that integrate with any JS tooling.
Predictable one-time initializationVue Composition APIThe setup() function runs once, simplifying init logic.
Explicit control over effect timingReact HooksThe useEffect with dependency arrays gives precise control.
Large, shared logic surfaceEither, with conventionsBoth scale; success depends on patterns and tests.
Migrating from class-based componentsFollow your frameworkUse Hooks in React, Composition API in Vue.

Frequently Asked Questions

What’s the main difference between React Hooks and Vue Composition API?

React Hooks are re-invoked on every render and rely on explicit dependency arrays, whereas Vue’s Composition API initializes logic once in setup() and tracks reactive dependencies automatically for effects.

Is Vue Composition API easier than React Hooks?

Vue Composition API tends to feel simpler thanks to automatic reactivity, while React Hooks provide more explicit control but require careful dependency management.

How do they handle side effects differently?

React manages side effects via useEffect and dependency arrays. Vue handles them through watch for explicit tracking and watchEffect for automatic tracking.

Are composables and custom hooks the same?

Both Vue composables and React custom hooks enable the reuse of logic. Composables are reactive and framework-aware, while React hooks depend on re-renders and closures.

Which scales better for large applications?

Both can handle large applications. Vue works best for complex template-driven interfaces, while React excels in projects that need intricate state handling and more explicit architectural control.

See the possibilities for yourself with live demos of Syncfusion React components.

Final takeaway: React Hooks vs Vue Composition API

Thanks for reading! The execution model is the key difference. Vue’s setup() function runs once, state initializes, reactivity takes over, and updates happen automatically. React re-executes the component on every render, state persists between runs, and effects fire based on what you explicitly declared has changed.

Vue’s model is easier to reason about for initialization and template binding. React’s model gives finer-grained control over effect scheduling at the cost of more careful dependency management. Neither is better; they reflect the design philosophy of their respective frameworks.

The practical choice is usually made for you:

  • If you’re in a Vue project, use composables.
  • If you’re in a React project, use hooks.

What matters more is the consistency and testability of the patterns your team adopts within that choice.

Before you decide: Speed up with ready-made UI components

Building in React or Vue?

Syncfusion offers 145+ high-performance UI components for both frameworks, charts, grids, schedulers, and more.

Bonus: Built-in AI coding assistance.
🎯 Saves weeks of UI development time.

Already using Syncfusion? Download the latest setup from your license and downloads page. New here? Start a free 30-day trial and capabilities and experience in firsthand.

For questions, you can contact us through our support forumsupport portal, or feedback portal. We are always happy to assist you!

Be the first to get updates

Kavindu GunathilakeKavindu Gunathilake profile icon

Meet the Author

Kavindu Gunathilake

Passionate writer and analytical software developer with experience in software design and development. Contributing to the AWS serverless category as an AWS community builder since 2022.

Leave a comment