📖 Scenario: You are building a simple Vue 3 app that tracks a counter. You want to create a custom ref that logs a message every time the counter value changes.
🎯 Goal: Create a Vue 3 component that uses customRef to make a reactive counter. The counter should log a message to the console whenever its value changes.
📋 What You'll Learn
Create a reactive counter using customRef
Log a message to the console whenever the counter value changes
Display the counter value in the template
Add buttons to increment and decrement the counter
💡 Why This Matters
🌍 Real World
Custom refs let you create reactive data with special behaviors, useful for logging, debouncing, or validation in real Vue apps.
💼 Career
Understanding customRef helps you build advanced Vue components and manage reactivity beyond simple refs or reactive objects.
Progress0 / 4 steps
1
Set up the Vue component with a basic template
Create a Vue 3 component with a <template> that shows a <div> containing a <span> with the text Counter: 0. Also add two buttons labeled Increment and Decrement. Use the <script setup> block but do not add any reactive code yet.
Vue
Hint
Start by writing the HTML structure inside the <template> and add an empty <script setup> block.
2
Import customRef and create a custom reactive counter
Inside the <script setup>, import customRef from vue. Then create a variable called counter using customRef that starts at 0. For now, just return the value and a dummy setter without logging.
Vue
Hint
Use customRef with a function that returns get and set methods. Initialize the internal value to 0.
3
Add console logging inside the setter of the custom ref
Modify the set method inside the customRef so that it logs "Counter changed to: " followed by the new value to the console every time the counter changes.
Vue
Hint
Use console.log inside the set method to print the new value.
4
Bind the counter to the template and add button click handlers
Update the template so the <span> shows the reactive counter value using interpolation. Add @click handlers to the Increment and Decrement buttons that increase or decrease counter.value by 1.
Vue
Hint
Use Vue interpolation {{ counter }} to show the value. Use @click on buttons to update counter.value.
Practice
(1/5)
1. What is the main purpose of using customRef in Vue?
easy
A. To create reactive references with custom get and set behavior
B. To define a component's template
C. To register global components
D. To handle lifecycle hooks automatically
Solution
Step 1: Understand what customRef does
customRef allows you to create reactive references where you control how the value is read and written.
Step 2: Compare with other options
Options A, C, and D relate to other Vue features, not customRef.
Final Answer:
To create reactive references with custom get and set behavior -> Option A
2. Which of the following is the correct way to import customRef in a Vue 3 component?
easy
A. import { CustomRef } from 'vue';
B. import customRef from 'vue';
C. import { customRef } from 'vue';
D. import * as customRef from 'vue';
Solution
Step 1: Recall Vue 3 import syntax
Vue exports customRef as a named export, so it must be imported with curly braces.
Step 2: Check each option
import { customRef } from 'vue'; uses correct named import syntax. import customRef from 'vue'; tries default import which is incorrect. import { CustomRef } from 'vue'; uses wrong capitalization. import * as customRef from 'vue'; imports everything as an object, which is not typical for this usage.
Final Answer:
import { customRef } from 'vue'; -> Option C
Quick Check:
Named import uses curly braces [OK]
Hint: Use curly braces for named imports like customRef [OK]
Common Mistakes:
Using default import instead of named import
Wrong capitalization of customRef
Importing everything instead of named import
3. Given the following code, what will be logged when myRef.value = 10 is called?
import { customRef } from 'vue';
const myRef = customRef((track, trigger) => {
let value = 0;
return {
get() {
track();
return value;
},
set(newValue) {
if (newValue !== value) {
value = newValue;
trigger();
console.log('Value updated');
}
}
};
});
myRef.value = 10;
medium
A. Value updated
B. No output
C. Error: track is not defined
D. Value updated twice
Solution
Step 1: Understand the set function behavior
The set function checks if the new value differs from the current one. Since 10 !== 0, it updates value, triggers reactivity, and logs 'Value updated'.
Step 2: Confirm no repeated logs or errors
There is no repeated assignment or error in the code, so only one log occurs.
Final Answer:
Value updated -> Option A
Quick Check:
Set logs once on value change [OK]
Hint: Set logs only when value changes [OK]
Common Mistakes:
Expecting no output because of track confusion
Thinking trigger causes multiple logs
Assuming error due to track usage
4. Identify the error in this customRef implementation:
import { customRef } from 'vue';
const myRef = customRef((track, trigger) => {
let value = 0;
return {
get() {
return value;
},
set(newValue) {
value = newValue;
trigger();
}
};
});
medium
A. customRef cannot be used with arrow functions
B. Missing call to trigger() inside get()
C. Value should be declared with ref()
D. Missing call to track() inside get()
Solution
Step 1: Check get() method for reactivity tracking
The get() method must call track() to register dependencies. Here, it returns value without calling track(), so reactivity won't work properly.
Step 2: Verify other parts
Trigger is correctly called in set(). Value does not need to be a ref inside customRef. Arrow functions are allowed.
Final Answer:
Missing call to track() inside get() -> Option D
Quick Check:
track() needed in get() for reactivity [OK]
Hint: Always call track() inside get() [OK]
Common Mistakes:
Forgetting to call track() in get()
Thinking trigger() is needed in get()
Assuming value must be a ref inside customRef
5. You want to create a customRef that debounces updates by 300ms before triggering reactivity. Which code snippet correctly implements this behavior?
hard
A. const debouncedRef = customRef((track, trigger) => {
let value;
return {
get() {
track();
return value;
},
set(newValue) {
value = newValue;
trigger();
}
};
});
B. const debouncedRef = customRef((track, trigger) => {
let value;
let timeout;
return {
get() {
track();
return value;
},
set(newValue) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
trigger();
}, 300);
}
};
});
C. const debouncedRef = customRef((track, trigger) => {
let value;
let timeout;
return {
get() {
return value;
},
set(newValue) {
clearTimeout(timeout);
timeout = setTimeout(() => {
value = newValue;
trigger();
}, 300);
}
};
});
D. const debouncedRef = customRef((track, trigger) => {
let value;
let timeout;
return {
get() {
track();
return value;
},
set(newValue) {
value = newValue;
clearTimeout(timeout);
timeout = setTimeout(() => {
trigger();
}, 300);
}
};
});
Solution
Step 1: Understand debounce logic in set()
Debounce means delaying the update and trigger until 300ms after the last set call. The correct code clears previous timeout, sets new timeout to update value and trigger after 300ms.
Step 2: Check get() calls track()
The correct code calls track() in get() to register dependencies. Missing track() in get() breaks reactivity. The remaining options either lack track(), do not debounce properly, or have incorrect debouncing logic.
Final Answer:
Correctly implements debounced updates with track() in get() -> Option B