Logo of react
React 19Functional components
Logo of vue
Vue 3.5Composition API

Local state management

Local state is mutable data that belongs to a single component instance. It's the component's private memory.

Why local state needs to be scoped?

  • Isolation, Separation of Concerns — to separate UI concerns from business logic
  • Encapsulation and Reusability — to have independent state in each instance
  • Performance and Optimization — allows frameworks to optimize efficiently

Use local state for anything that affects one instance of the component (form input values, toggle states). Use shared/global state for data that multiple components genuinely need to access (user authentication, theme settings).

Difference of implementation

Logo of react React 19, Functional components

React uses an explicit, functional state model. Use setter function to change the value.

Logo of vue Vue 3.5, Composition API

Vue uses a Proxy-based reactivity system that automatically detects when you access or modify data. When you declare state with ref(), Vue wraps your data in a Proxy that intercepts reads and writes.

Non-reactive state

If data is not changed during component lifecycle — keep it non-reactive.

Logo of react React 19, Functional components
const year = new Date().getFullYear();export default function () {  return <div>{year}</div>;}
Logo of vue Vue 3.5, Composition API
<script setup lang="ts">const year = new Date().getFullYear();</script><template>  <div>{{ year }}</div></template>

If you have full control over a variable — no need to make it reactive.

Logo of react React 19, Functional components
export default function TimeoutComponent(): React.FC {  const [message, setMessage] = useState('Waiting...');  useEffect(() => {    const timeoutId = setTimeout(() => {      setMessage('Timeout finished!');    }, 1000);    return () => {      clearTimeout(timeoutId);    };  }, []);  return <div>{message}</div>;};
Logo of vue Vue 3.5, Composition API
<script setup lang="ts">const message = ref('Waiting...');let timeoutId: ReturnType<typeof setTimeout> | null = null;onMounted(() => {  timeoutId = setTimeout(() => {    message.value = 'Timeout finished!';  }, 1000);});onUnmounted(() => {  if (timeoutId) {    resetTimeout(timeoutId);    timeoutId = null;  }});</script><template>  <div>{{ message }}</div></template>

Reactive state

Logo of react React 19, Functional components

React requires you to call the setter function: setValue(newValue). This function call triggers an asynchronous re-render of the component. You must pass a new value rather than mutating the existing one. Updating state doesn't immediately change the variable — you'll still see the old value until the next render completes.

export default function Counter() {  const [count, setCount] = useState(0);  const increment = useCallback(() => {    setCount((prevCount) => prevCount + 1);  }, []);  const reset = useCallback(() => {    setCount(0);  }, []);  return (    <div>      <div>{count}</div>      <button onClick={increment}> +1 </button>      <button onClick={reset}> reset </button>    </div>  );}
Logo of vue Vue 3.5, Composition API

ref() uses a .value property to change the value of the state. ref() is deep reactivity by default.

Vue automatically unwraps refs — so, you can use {{ count }} instead of {{ count.value }} in <template>

<script setup lang="ts">const count = ref(0);function reset() {  count.value = 0;}</script><template>  <div>{{ count }}</div>  <button @click="count += 1">+1</button>  <button @click="reset()">reset</button></template>

Two-way binding

Logo of react React 19, Functional components

You store the input value in state using useState(). You set the input's value attribute to this state value. You add an onChange handler that captures the new value and updates state via setState().

The cycle: input changes → onChange fires → state updates → component re-renders → input displays new value.

import React, { useState } from 'react';export default function () {  const [message, setMessage] = useState('');  return (    <div>      <input        value={message}        onChange={(e) => setMessage(e.target.value)}      />      <p>You typed: {message}</p>    </div>  );}

You can add validation, transformation, or formatting in the onChange handler before updating state.

React also supports uncontrolled components using useRef(), where the DOM element maintains its own value and React doesn't control it. This approach has no two-way binding — React only reads the value when you explicitly call the ref.

export default function () {  // useRef is used because it's not rendered  const messageRef = useRef(null);  const handleSubmit = (e) => {    e.preventDefault();    messageRef.current.value = '';  };  return (    <div>      <form onSubmit={handleSubmit}>        <input ref={messageRef} />        <button type="submit">Submit</button>      </form>    </div>  );}
Logo of vue Vue 3.5, Composition API

Vue provides v-model as a dedicated syntax for two-way binding. Instead of writing separate value prop and @input event listener, you write <input v-model="count">.

v-model works on form elements (input, textarea, select) and custom Vue components.

<script setup lang="ts">import { ref } from 'vue';const message = ref('');</script><template>  <div>    <input v-model="message" />    <p>You typed: {{ message }}</p>  </div></template>

When you create custom components, you can implement v-model support by accepting a modelValue prop and emitting an update:modelValue event.

There is possible to have multiple v-models.

<UserName v-model:first-name="first" v-model:last-name="last" />
<script setup lang="ts">const firstName = defineModel('firstName');const lastName = defineModel('lastName');</script><template>  <input type="text" v-model="firstName" />  <input type="text" v-model="lastName" /></template>