# Refs, Computed, and Reactivity This note explains the most important Vue state concepts for a Python developer. ## `ref`: reactive storage for one value A `ref` wraps a value so Vue can track it. ```ts import { ref } from 'vue'; const count = ref(0); const username = ref('mathias'); const isLoading = ref(false); ``` In script code, you read and write using `.value`. ```ts count.value += 1; username.value = 'new name'; isLoading.value = true; ``` In templates, Vue unwraps refs automatically. ```vue ``` You write `count`, not `count.value`, in the template. ## Python comparison Python: ```python count = 0 count += 1 ``` Vue script: ```ts const count = ref(0); count.value += 1; ``` The extra `.value` exists because `count` is a reactive wrapper object, not the raw number. ## Example: loading state ```vue ``` The button label and disabled state both react to `isLoading`. ## `computed`: derived state A `computed` value is calculated from other reactive values. ```ts import { ref, computed } from 'vue'; const firstName = ref('Ada'); const lastName = ref('Lovelace'); const fullName = computed(() => { return `${firstName.value} ${lastName.value}`; }); ``` Use `computed` when a value can be derived instead of stored manually. Bad pattern: ```ts const firstName = ref('Ada'); const lastName = ref('Lovelace'); const fullName = ref('Ada Lovelace'); ``` Now you must remember to update `fullName` yourself every time. Better: ```ts const fullName = computed(() => `${firstName.value} ${lastName.value}`); ``` ## Python comparison This is similar to a property. ```python class Person: def __init__(self, first_name, last_name): self.first_name = first_name self.last_name = last_name @property def full_name(self): return f"{self.first_name} {self.last_name}" ``` Vue `computed` plays a similar role. ## Real example from your app In FlexibilityTable, this pattern appears: ```ts const hasValidPeriodRange = computed(() => { if (!selectedPeriodStartFrom.value || !selectedPeriodStartUntil.value) { return true; } return new Date(selectedPeriodStartFrom.value).getTime() <= new Date(selectedPeriodStartUntil.value).getTime(); }); ``` This means: - if one boundary is missing, the range is treated as valid - if both are set, start must be before or equal to end The component does not store `hasValidPeriodRange` manually. It derives it from the two selected dates. ## Another example: filtered list ```ts const searchText = ref(''); const users = ref(['Alice', 'Bob', 'Charlie']); const filteredUsers = computed(() => { return users.value.filter((user) => user.toLowerCase().includes(searchText.value.toLowerCase()), ); }); ``` If `searchText` changes, `filteredUsers` updates automatically. ## Rule of Thumb Use `ref` when: - the value changes over time - the UI should react to that change - the value is primary state Use `computed` when: - the value is calculated from other reactive values - you do not want to duplicate state ## Common Beginner Mistakes ### forgetting `.value` in script Wrong: ```ts count += 1; ``` Right: ```ts count.value += 1; ``` ### using `computed` for side effects Bad: ```ts const result = computed(() => { console.log('side effect'); return count.value * 2; }); ``` A computed should mainly calculate and return a value. ### storing what can be derived Bad: ```ts const selectedFirst = ref('2026-01-01'); const selectedSecond = ref('2026-01-10'); const isValid = ref(true); ``` Better: ```ts const isValid = computed(() => selectedFirst.value <= selectedSecond.value); ``` ## Tiny Exercise What should be `ref` and what should be `computed`? Scenario: - user types first name - user types last name - screen shows full name - submit button disabled when first name is empty Answer: ```ts const firstName = ref(''); const lastName = ref(''); const fullName = computed(() => `${firstName.value} ${lastName.value}`.trim()); const isSubmitDisabled = computed(() => !firstName.value.trim()); ``` ## Related Notes - [[00-Vue for Python Developers]] - [[02-Templates, v-model, and Events]] - [[04-FlexibilityTable Filter Flow]]