Understanding refs() - A Beginner's Guide
Quick Start (30 seconds)
Need to create multiple reactive values at once? Here's how:
// 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-runThat'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:
const count = ref(0);
const message = ref('Hello');
const isActive = ref(true);Into this:
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
// 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:
// 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:
// 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 declarationWhere 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:
// 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:
- Takes an object with initial values
- Creates a
ref()for each property - Returns an object containing all the refs
- 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:
// 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():
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:
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-runsWorking with Multiple Refs
Independent Reactivity
Each ref created by refs() is completely independent:
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:
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:
// 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); // truerefs() 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
// 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
// Better for single values
const count = ref(0);Quick Comparison
// ❌ 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
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
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
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
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
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
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:
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:
const { count, message } = refs({ count: 0, message: '' });
// Direct access
console.log(count.value);
console.log(message.value);Pitfall #2: Forgetting .value
❌ Wrong:
const { count } = refs({ count: 0 });
count = 5; // ERROR: Trying to reassign const!✅ Correct:
const { count } = refs({ count: 0 });
count.value = 5; // Updates the value inside the refPitfall #3: Destructuring Wrong Names
❌ Wrong:
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 undefinedThe property names must match!
✅ Correct:
const { count, message, isActive } = refs({
count: 0,
message: '',
isActive: true
});Pitfall #4: Trying to Add Refs Later
❌ Wrong:
const myRefs = refs({ count: 0 });
// Can't add new refs to the result
myRefs.message = ref('Hello'); // This adds a ref, but it's awkwardrefs() creates all refs at once. Adding more later isn't the intended pattern.
✅ Correct:
// 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:
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:
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:
// 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 bRemember: 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!