Skip to content

Sponsor

Sponsor

Understanding refs() - A Beginner's Guide

Quick Start (30 seconds)

Need to create multiple reactive values at once? Here's how:

js
// Create multiple reactive refs in one call
const { count, message, isActive } = refs({
  count: 0,
  message: 'Hello',
  isActive: true
});

// Each is an independent reactive ref
effect(() => {
  console.log(count.value);    // Access with .value
});

// Update any ref - only its effects re-run
count.value = 5;        // Only count effects re-run
message.value = 'Hi';   // Only message effects re-run

That's it! The refs() function creates multiple reactive refs from a single object definition. Each ref is independent and reactive.

What is refs()?

refs() is a convenience function for creating multiple ref() values at once. Instead of calling ref() multiple times, you can create many refs from a single object.

In simple terms: It's a shortcut that turns this:

js
const count = ref(0);
const message = ref('Hello');
const isActive = ref(true);

Into this:

js
const { count, message, isActive } = refs({
  count: 0,
  message: 'Hello',
  isActive: true
});

Think of it as bulk ref creation - you define all your reactive values in one object, and get back individual refs for each property.

Syntax

js
// Using the shortcut
refs(definitions)

// Using the full namespace
ReactiveUtils.refs(definitions)

Both styles are valid! Choose whichever you prefer:

  • Shortcut style (refs()) - Clean and concise
  • Namespace style (ReactiveUtils.refs()) - Explicit and clear

Parameters:

  • definitions - An object where each property becomes a separate ref

Returns:

  • An object containing individual refs for each property

Why Does This Exist?

Two Approaches to Creating Multiple Reactive References

The Reactive library offers flexible ways to create reactive primitive values, each suited to different coding styles.

Individual ref() Declarations

When you prefer explicit, one-at-a-time declarations and want each reference clearly visible:

javascript
// Create refs individually
const count = ref(0);
const message = ref('Hello');
const isActive = ref(true);
const userId = ref(null);
const loading = ref(false);
const error = ref(null);

This approach is great when you need: ✅ Explicit declaration of each reactive value ✅ Clear visibility of individual refs ✅ Flexibility to add refs incrementally ✅ Standard one-declaration-per-line pattern

When Grouped Ref Creation Fits Your Style

In scenarios where you want to define multiple refs together with less repetition, refs() provides a more direct approach:

javascript
// Create multiple refs in one declaration
const { count, message, isActive, userId, loading, error } = refs({
  count: 0,
  message: 'Hello',
  isActive: true,
  userId: null,
  loading: false,
  error: null
});

This method is especially useful when:

refs() Creation:
┌──────────────────┐
│  refs({          │
│    count: 0,     │
│    message: '',  │
│    isActive: true│
│  })              │
└────────┬─────────┘


   Creates multiple
   refs at once


  ✅ Grouped declaration

Where refs() shines:Grouped initialization - All refs defined in one object ✅ Less repetition - One refs() call instead of multiple ref() calls ✅ At-a-glance visibility - See all reactive values in one structure ✅ Destructuring syntax - Clean extraction of individual refs ✅ Logical grouping - Related refs declared together

The Choice is Yours:

  • Use individual ref() calls when you prefer explicit, incremental declarations
  • Use refs() when you want to group multiple ref declarations together
  • Both create identical reactive references

Benefits of the refs approach:Single function call - Create multiple refs at once ✅ Reduced boilerplate - Less repetitive ref() calls ✅ Organized structure - All refs in one declaration ✅ Easy scanning - See all reactive primitives together ✅ Clean syntax - Destructure to get individual refs

Mental Model

Think of refs() like a vending machine that dispenses individual containers:

Multiple ref() Calls (Manual):
┌──────┐ ┌──────┐ ┌──────┐
│ ref  │ │ ref  │ │ ref  │
│ (0)  │ │ ('') │ │(true)│
└──┬───┘ └──┬───┘ └──┬───┘
   │        │        │
   ▼        ▼        ▼
  Get each container
  one at a time

refs() (Bulk):
┌────────────────────────┐
│   Vending Machine      │
│   ┌────────────────┐   │
│   │ count: 0       │   │
│   │ message: ''    │   │
│   │ isActive: true │   │
│   └────────────────┘   │
│                        │
│   Press one button ──→ │
└────────────────────────┘


   Get all containers
   at once!

Key Insight: Just like a vending machine that can dispense multiple items with one selection, refs() creates multiple reactive refs with one function call. You get back individual refs, but they're created together.

How Does It Work?

The Magic: Looping and Creating

When you call refs(), here's what happens behind the scenes:

javascript
// What you write:
const { count, message } = refs({
  count: 0,
  message: 'Hello'
});

// What actually happens (simplified):
const result = {};
result.count = ref(0);
result.message = ref('Hello');

// Then you destructure:
const { count, message } = result;

In other words: refs() is a convenience wrapper that:

  1. Takes an object with initial values
  2. Creates a ref() for each property
  3. Returns an object containing all the refs
  4. You destructure to get individual refs

Under the Hood

refs({ count: 0, message: 'Hello' })


┌───────────────────────┐
│  Loop over properties │
└──────────┬────────────┘


┌───────────────────────┐
│  For 'count':         │
│  Create ref(0)        │
└──────────┬────────────┘


┌───────────────────────┐
│  For 'message':       │
│  Create ref('Hello')  │
└──────────┬────────────┘


┌───────────────────────┐
│  Return {             │
│    count: ref(0),     │
│    message: ref('..') │
│  }                    │
└───────────────────────┘

What happens:

1️⃣ refs() receives an object with properties 2️⃣ For each property, it creates a ref() with that value 3️⃣ All refs are collected in a result object 4️⃣ You get back multiple independent refs

Each ref is completely independent - changing one doesn't affect the others!

Basic Usage

Creating Multiple Refs

The simplest way to use refs() is with primitive values:

js
// Using the shortcut style
const { count, message, isActive } = refs({
  count: 0,
  message: 'Hello',
  isActive: true
});

// Or using the namespace style
const { count, message, isActive } = ReactiveUtils.refs({
  count: 0,
  message: 'Hello',
  isActive: true
});

That's it! Now count, message, and isActive are all individual reactive refs.

Accessing Values

Each ref works exactly like a normal ref():

js
const { count, message } = refs({
  count: 0,
  message: 'Hello'
});

// Read values with .value
console.log(count.value);    // 0
console.log(message.value);  // "Hello"

// Update values with .value
count.value = 5;
message.value = 'Hi';

console.log(count.value);    // 5
console.log(message.value);  // "Hi"

Using in Effects

Each ref can be used independently in effects:

js
const { count, message } = refs({
  count: 0,
  message: 'Hello'
});

// Effect only watches count
effect(() => {
  console.log('Count:', count.value);
});

// Effect only watches message
effect(() => {
  console.log('Message:', message.value);
});

count.value = 5;    // Only first effect re-runs
message.value = 'Hi'; // Only second effect re-runs

Working with Multiple Refs

Independent Reactivity

Each ref created by refs() is completely independent:

js
const { x, y, z } = refs({
  x: 0,
  y: 0,
  z: 0
});

// Three separate effects
effect(() => {
  console.log('X:', x.value);
});

effect(() => {
  console.log('Y:', y.value);
});

effect(() => {
  console.log('Z:', z.value);
});

// Only the first effect re-runs
x.value = 10;  // Logs: "X: 10"

// Only the second effect re-runs
y.value = 20;  // Logs: "Y: 20"

Combining Refs in Effects

You can watch multiple refs in one effect:

js
const { width, height } = refs({
  width: 100,
  height: 50
});

// Effect watches BOTH refs
effect(() => {
  const area = width.value * height.value;
  console.log('Area:', area);
});

// Effect re-runs when EITHER changes
width.value = 200;   // Logs: "Area: 10000"
height.value = 100;  // Logs: "Area: 20000"

Partial Destructuring

You don't have to destructure all refs:

js
// Create many refs
const allRefs = refs({
  count: 0,
  message: 'Hello',
  isActive: true,
  userId: null,
  loading: false
});

// Only destructure what you need
const { count, message } = allRefs;

// Or access directly
console.log(allRefs.isActive.value); // true

refs() vs Multiple ref() Calls

When to Use refs()

Use refs() when you need multiple related reactive values:

✅ Form fields (name, email, password) ✅ Coordinates (x, y, z) ✅ Loading states (loading, error, data) ✅ Settings (theme, language, fontSize) ✅ Any group of related primitive values

js
// Perfect for grouped values
const { name, email, password } = refs({
  name: '',
  email: '',
  password: ''
});

When to Use Individual ref()

Use individual ref() calls when you need one or two unrelated values:

✅ Single counter ✅ One toggle ✅ Occasional reactive value

js
// Better for single values
const count = ref(0);

Quick Comparison

javascript
// ❌ Using refs() for one value (overkill)
const { count } = refs({ count: 0 });

// ✅ Using ref() for one value (clean)
const count = ref(0);

// ❌ Using multiple ref() for many values (verbose)
const count = ref(0);
const message = ref('');
const isActive = ref(true);
const userId = ref(null);
const loading = ref(false);

// ✅ Using refs() for many values (organized)
const { count, message, isActive, userId, loading } = refs({
  count: 0,
  message: '',
  isActive: true,
  userId: null,
  loading: false
});

Simple Rule:

  • One value? Use ref()
  • Multiple related values? Use refs()

Common Patterns

Pattern: Form Fields

js
const { username, email, password } = refs({
  username: '',
  email: '',
  password: ''
});

// Auto-update display
effect(() => {
  document.getElementById('usernameDisplay').textContent = username.value;
});

// Handle input
document.getElementById('usernameInput').addEventListener('input', (e) => {
  username.value = e.target.value;
});

Pattern: 2D Coordinates

js
const { x, y } = refs({
  x: 0,
  y: 0
});

// Track position
effect(() => {
  console.log(`Position: (${x.value}, ${y.value})`);
});

// Update coordinates
document.addEventListener('mousemove', (e) => {
  x.value = e.clientX;
  y.value = e.clientY;
});

Pattern: Async Loading State

js
const { loading, data, error } = refs({
  loading: false,
  data: null,
  error: null
});

// Show loading indicator
effect(() => {
  const spinner = document.getElementById('spinner');
  spinner.style.display = loading.value ? 'block' : 'none';
});

// Fetch data
async function fetchData() {
  loading.value = true;
  error.value = null;

  try {
    const response = await fetch('/api/data');
    data.value = await response.json();
  } catch (err) {
    error.value = err.message;
  } finally {
    loading.value = false;
  }
}

Pattern: Settings Panel

js
const { theme, fontSize, language } = refs({
  theme: 'light',
  fontSize: 16,
  language: 'en'
});

// Apply theme
effect(() => {
  document.body.className = theme.value;
});

// Apply font size
effect(() => {
  document.body.style.fontSize = fontSize.value + 'px';
});

// Update settings
function changeTheme() {
  theme.value = theme.value === 'light' ? 'dark' : 'light';
}

Pattern: Counter with Stats

js
const { count, increments, decrements } = refs({
  count: 0,
  increments: 0,
  decrements: 0
});

// Display stats
effect(() => {
  document.getElementById('count').textContent = count.value;
  document.getElementById('increments').textContent = increments.value;
  document.getElementById('decrements').textContent = decrements.value;
});

function increment() {
  count.value++;
  increments.value++;
}

function decrement() {
  count.value--;
  decrements.value++;
}

Pattern: RGB Color Picker

js
const { r, g, b } = refs({
  r: 0,
  g: 0,
  b: 0
});

// Update color display
effect(() => {
  const color = `rgb(${r.value}, ${g.value}, ${b.value})`;
  document.getElementById('colorBox').style.backgroundColor = color;
  document.getElementById('colorCode').textContent = color;
});

// Connect sliders
document.getElementById('rSlider').addEventListener('input', (e) => {
  r.value = parseInt(e.target.value);
});

document.getElementById('gSlider').addEventListener('input', (e) => {
  g.value = parseInt(e.target.value);
});

document.getElementById('bSlider').addEventListener('input', (e) => {
  b.value = parseInt(e.target.value);
});

Common Pitfalls

Pitfall #1: Forgetting to Destructure

Wrong:

js
const myRefs = refs({ count: 0, message: '' });

// Have to access through object
console.log(myRefs.count.value);
console.log(myRefs.message.value);

This works, but it's awkward. You're adding an extra layer of nesting.

Correct:

js
const { count, message } = refs({ count: 0, message: '' });

// Direct access
console.log(count.value);
console.log(message.value);

Pitfall #2: Forgetting .value

Wrong:

js
const { count } = refs({ count: 0 });

count = 5; // ERROR: Trying to reassign const!

Correct:

js
const { count } = refs({ count: 0 });

count.value = 5; // Updates the value inside the ref

Pitfall #3: Destructuring Wrong Names

Wrong:

js
const { x, y, z } = refs({
  count: 0,
  message: '',
  isActive: true
});

// x, y, z are undefined!
console.log(x.value); // ERROR: Cannot read property 'value' of undefined

The property names must match!

Correct:

js
const { count, message, isActive } = refs({
  count: 0,
  message: '',
  isActive: true
});

Pitfall #4: Trying to Add Refs Later

Wrong:

js
const myRefs = refs({ count: 0 });

// Can't add new refs to the result
myRefs.message = ref('Hello'); // This adds a ref, but it's awkward

refs() creates all refs at once. Adding more later isn't the intended pattern.

Correct:

js
// Define all refs upfront
const { count, message } = refs({
  count: 0,
  message: 'Hello'
});

// Or create separate ref
const anotherRef = ref('Extra');

Pitfall #5: Expecting Object Reactivity

Wrong:

js
const myRefs = refs({ count: 0, message: '' });

// Expecting the container object to be reactive
effect(() => {
  console.log(myRefs); // This doesn't track individual refs
});

refs() creates individual refs, not a reactive object of refs.

Correct:

js
const { count, message } = refs({ count: 0, message: '' });

// Track individual refs
effect(() => {
  console.log(count.value, message.value);
});

Summary

What is refs()?

refs() creates multiple reactive refs from a single object definition. It's a convenience function that reduces boilerplate.

Why use refs() instead of multiple ref() calls?

  • Less repetitive code
  • Better organization
  • All refs defined in one place
  • Cleaner destructuring syntax
  • Easier to see all reactive values

Key Points to Remember:

1️⃣ Creates multiple independent refs - each is separate 2️⃣ Destructure the result - extract individual refs 3️⃣ Each ref uses .value - same as regular ref() 4️⃣ Define all refs upfront - not designed for adding refs later 5️⃣ Perfect for grouped values - form fields, coordinates, settings

Mental Model: Think of refs() as a vending machine - you define all the items upfront, press one button, and get back individual containers for each item.

Quick Reference:

js
// Create multiple refs
const { count, message, isActive } = refs({
  count: 0,
  message: 'Hello',
  isActive: true
});

// Access values
console.log(count.value);

// Update values
count.value = 5;
message.value = 'Hi';
isActive.value = false;

// Use in effects
effect(() => {
  console.log(count.value);
});

// Partial destructuring
const allRefs = refs({ a: 1, b: 2, c: 3 });
const { a, b } = allRefs; // Only get a and b

Remember: refs() is perfect when you need multiple reactive values that are logically grouped together. It's cleaner than multiple ref() calls and makes your code more organized and maintainable!