How to Handle Null Values with Vue's ref Function: Best Practices
The best practice for handling null values with Vue's ref function is to explicitly type the ref as a union type (e.g., ref<User | null>(null)) and guard against null before accessing .value, or initialize with a non-null default to eliminate runtime checks entirely.
The ref function in the vuejs/core repository creates a reactive wrapper around any value, including null. Because the reactivity system does not provide built-in null-fallback mechanisms, handling null correctly requires intentional patterns that maintain type safety and prevent runtime errors.
Understanding How Vue Handles Null Internally
Vue's reactivity system treats null as a standard primitive value without special casing. In packages/reactivity/src/ref.ts, the ref() factory function simply forwards the incoming value to createRef(), which returns the value unchanged if it is already a ref, or constructs a new RefImpl instance otherwise.
The RefImpl class stores the raw value in _rawValue (via toRaw) and the reactive version in _value (via toReactive). When the supplied value is null, toRaw(null) returns null and toReactive(null) returns null, so the ref behaves like any other primitive. The set value accessor runs hasChanged to detect changes using strict equality, meaning assigning the same null value will not trigger effects.
Best Practices for Vue ref Null Values
Initialize with Non-Null Defaults
Guarantee that ref.value is always defined by providing a sensible initial value. This pattern removes the need for runtime null checks throughout your component logic.
import { ref } from 'vue'
const config = ref<{ theme: string; fontSize: number }>({
theme: 'light',
fontSize: 14
})
// Always safe - never null
config.value.theme = 'dark'
Use Union Types with Explicit Null Guards
When an "empty" state is semantically required, declare the ref with an explicit union type Type | null. This forces TypeScript to prompt you for null checks before accessing properties.
import { ref } from 'vue'
interface User {
id: number
name: string
}
const selectedUser = ref<User | null>(null)
// Safe access through explicit guard
if (selectedUser.value) {
console.log(`User: ${selectedUser.value.name}`)
}
Leverage Optional Chaining for Read-Only Access
For concise defensive reading when null represents an acceptable no-op state, use optional chaining to access nested properties without throwing errors.
// Returns undefined if selectedUser.value is null
const userName = selectedUser.value?.name
Use shallowRef for Large Nullable Objects
When dealing with complex objects that are replaced entirely (such as API payloads) rather than mutated, use shallowRef from packages/reactivity/src/ref.ts to avoid the overhead of deep reactivity conversion while maintaining the nullable state.
import { shallowRef } from 'vue'
interface Data {
items: any[]
meta: Record<string, unknown>
}
const payload = shallowRef<Data | null>(null)
// Replace the entire object when data arrives
fetch('/api/data')
.then(r => r.json())
.then(data => (payload.value = data))
// Access still requires null guarding
if (payload.value) {
console.log(payload.value.items.length)
}
Utilize unref and toValue Helpers
The unref and toValue helpers automatically unwrap refs, but you must still implement null guards if the underlying type can be null.
import { unref } from 'vue'
const name = unref(selectedUser)?.name
Summary
- Vue's
refimplementation storesnullas a raw primitive inRefImpl._rawValuewithout special fallback logic. - Always type nullable refs explicitly as
Ref<Type | null>to leverage TypeScript's strict type checking. - Guard against null with
ifchecks or optional chaining before accessing.valueto prevent runtime errors. - Initialize refs with non-null defaults when possible to eliminate conditional logic throughout components.
- Use
shallowReffor large nullable objects that are replaced wholesale to optimize reactivity performance.
Frequently Asked Questions
Does Vue ref treat null differently from other values?
No. According to the vuejs/core source code in packages/reactivity/src/ref.ts, the ref function forwards null to createRef() just like any other value. The RefImpl constructor stores null via toRaw(null) and toReactive(null), both of which return null, making it behave exactly like other primitives with strict equality comparison in the setter.
How do I type a ref that can be null in TypeScript?
Declare the ref with a union type: const myRef = ref<MyType | null>(null). This explicit typing ensures TypeScript warns you when accessing properties without first checking if .value is null, enforcing safe access patterns at compile time.
Should I use shallowRef or ref for nullable API data?
Use shallowRef when the nullable value is a large object that gets replaced entirely (like an API response) rather than having its individual properties mutated. This avoids the cost of deep reactivity conversion while still allowing the ref to hold null initially and be replaced with data later.
Why does my nullable ref trigger re-renders when set to null?
The RefImpl setter uses hasChanged with strict equality (===) to detect changes. If you are assigning a new null value to replace a previous non-null value, the change detection identifies this as a valid state change and triggers effects. If you assign null when the ref already contains null, no re-render occurs because the values are identical.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →