Local state is mutable data that belongs to a single component instance. It's the component's private memory.
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).
React uses an explicit, functional state model. Use setter function to change the value.
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.
If data is not changed during component lifecycle — keep it non-reactive.
const year = new Date().getFullYear();export default function () { return <div>{year}</div>;}<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.
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>;};<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>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> );}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>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> );}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>