📖 Scenario: You are building a simple Vue 3 app that tracks a counter. You want to reuse the counter logic in multiple components. To do this, you will create a custom composable function that manages the counter state and provides functions to change it.
🎯 Goal: Build a custom composable called useCounter that holds a counter value and provides increment and decrement functions. Then use this composable inside a Vue component to display and change the counter.
📋 What You'll Learn
Create a reactive counter variable using ref
Create a composable function named useCounter
Inside useCounter, define increment and decrement functions that update the counter
Return the counter and the two functions from useCounter
Use the useCounter composable inside a Vue component's setup function
Display the counter value and add buttons to call increment and decrement
💡 Why This Matters
🌍 Real World
Custom composables help you reuse logic like counters, form handling, or data fetching across many Vue components without repeating code.
💼 Career
Understanding composables is essential for modern Vue development and is a common skill required in frontend developer roles.
Progress0 / 4 steps
1
Setup the reactive counter variable
Import ref from vue and create a reactive variable called counter initialized to 0.
Vue
Hint
Use ref(0) to create a reactive number starting at zero.
2
Create the useCounter composable function
Define a function called useCounter that returns an object with counter, increment, and decrement. Inside it, define increment and decrement functions that add or subtract 1 from counter.value.
Vue
Hint
Return the reactive counter and the two functions inside an object.
3
Use the composable inside a Vue component
Create a Vue component with a setup function. Inside setup, call useCounter and get counter, increment, and decrement. Return these from setup.
Vue
Hint
Destructure the returned object from useCounter() inside setup().
4
Add template to display and control the counter
Add a template section that shows the counter value inside a <span>. Add two buttons labeled + and - that call increment and decrement on click.
Vue
Hint
Use @click on buttons to call the functions. Show counter inside {{ }}.
Practice
(1/5)
1. What is the main purpose of creating a custom composable in Vue?
easy
A. To define global CSS styles
B. To reuse reactive logic across multiple components
C. To create new Vue directives
D. To register components globally
Solution
Step 1: Understand what composables do
Custom composables are functions that encapsulate reactive logic to be reused.
Step 2: Identify the purpose in Vue components
They help share logic without repeating code in multiple components.
Final Answer:
To reuse reactive logic across multiple components -> Option B
function useData() { const count = ref(0); return { count }; } declares a function named with 'use' prefix and returns reactive refs.
Step 2: Validate reactive usage and return object
function useData() { const count = ref(0); return { count }; } returns an object with the reactive ref, which is correct pattern.
Final Answer:
function useData() { const count = ref(0); return { count }; } -> Option A
Quick Check:
Composable returns reactive refs in object = D [OK]
Hint: Composable must return reactive data inside an object [OK]
Common Mistakes:
Not returning reactive data
Returning reactive primitive directly instead of object
Missing 'use' prefix in function name
3. Given this composable code, what will be logged when calling increment() twice?
import { ref } from 'vue';
export function useCounter() {
const count = ref(0);
function increment() {
count.value++;
console.log(count.value);
}
return { count, increment };
}
medium
A. Undefined and then 1
B. 0 and then 1
C. 2 and then 3
D. 1 and then 2
Solution
Step 1: Understand initial count value
The count starts at 0 as a ref.
Step 2: Trace increment calls and console logs
Each increment increases count.value by 1 and logs it immediately.
Final Answer:
1 and then 2 -> Option D
Quick Check:
Increment twice logs 1 then 2 [OK]
Hint: Ref value increments and logs immediately [OK]
Common Mistakes:
Confusing ref.value with ref itself
Assuming count starts at 1
Thinking console logs before increment
4. Identify the error in this composable code:
import { ref } from 'vue';
export function useToggle() {
const state = ref(false);
function toggle() {
state = !state;
}
return { state, toggle };
}
medium
A. ref should be reactive instead
B. Missing return statement
C. Cannot reassign a const ref variable directly
D. toggle function should be async
Solution
Step 1: Check how ref is updated
The code tries to assign 'state = !state', which reassigns the const variable.
Step 2: Correct way to update ref value
Should update 'state.value = !state.value' instead of reassigning 'state'.
Final Answer:
Cannot reassign a const ref variable directly -> Option C
Quick Check:
Update ref.value, not ref variable [OK]
Hint: Update ref.value, never reassign ref variable [OK]
Common Mistakes:
Reassigning ref variable instead of its value
Forgetting to use .value to access ref
Assuming reactive is needed instead of ref
5. You want to create a composable that fetches user data and exposes loading state. Which approach correctly combines reactive state and async fetch inside a composable?
hard
A. function useUser() {
const user = ref(null);
const loading = ref(true);
async function fetchUser() {
loading.value = true;
user.value = await fetch('/api/user').then(r => r.json());
loading.value = false;
}
fetchUser();
return { user, loading };
}
B. function useUser() {
const user = reactive(null);
const loading = false;
async function fetchUser() {
loading = true;
user = await fetch('/api/user').then(r => r.json());
loading = false;
}
fetchUser();
return { user, loading };
}
C. function useUser() {
const user = ref(null);
const loading = ref(false);
function fetchUser() {
loading.value = true;
fetch('/api/user').then(r => r.json()).then(data => {
user.value = data;
loading.value = false;
});
}
return { user, loading, fetchUser };
}
D. function useUser() {
const user = ref(null);
const loading = ref(true);
fetch('/api/user').then(r => r.json()).then(data => {
user = data;
loading.value = false;
});
return { user, loading };
}
Solution
Step 1: Check reactive state initialization
function useUser() {
const user = ref(null);
const loading = ref(true);
async function fetchUser() {
loading.value = true;
user.value = await fetch('/api/user').then(r => r.json());
loading.value = false;
}
fetchUser();
return { user, loading };
} correctly uses ref for user and loading states.
Step 2: Verify async fetch and state updates
function useUser() {
const user = ref(null);
const loading = ref(true);
async function fetchUser() {
loading.value = true;
user.value = await fetch('/api/user').then(r => r.json());
loading.value = false;
}
fetchUser();
return { user, loading };
} uses async function to set loading true, await fetch, then set loading false.