# How to Handle Null Values with Vue's ref Function: Best Practices

> Learn best practices for handling null values with Vue's ref function. Discover how to type refs and avoid runtime checks for robust Vue applications.

- Repository: [Vue/core](https://github.com/vuejs/core)
- Tags: best-practices
- Published: 2026-02-16

---

**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`](https://github.com/vuejs/core/blob/main/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.

```typescript
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.

```typescript
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.

```typescript
// 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`](https://github.com/vuejs/core/blob/main/packages/reactivity/src/ref.ts) to avoid the overhead of deep reactivity conversion while maintaining the nullable state.

```typescript
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.

```typescript
import { unref } from 'vue'

const name = unref(selectedUser)?.name

```

## Summary

- Vue's `ref` implementation stores `null` as a raw primitive in `RefImpl._rawValue` without special fallback logic.
- Always type nullable refs explicitly as `Ref<Type | null>` to leverage TypeScript's strict type checking.
- Guard against null with `if` checks or optional chaining before accessing `.value` to prevent runtime errors.
- Initialize refs with non-null defaults when possible to eliminate conditional logic throughout components.
- Use `shallowRef` for 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`](https://github.com/vuejs/core/blob/main/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.