Bird
Raised Fist0
Vueframework~15 mins

Composable with reactive state in Vue - Deep Dive

Choose your learning style10 modes available
Overview - Composable with reactive state
What is it?
A composable with reactive state in Vue is a reusable function that manages and shares reactive data and logic across components. It uses Vue's reactive system to keep data in sync automatically when it changes. This helps organize code better by separating concerns and avoiding repetition. Composables make it easy to build flexible and maintainable Vue applications.
Why it matters
Without composables managing reactive state, Vue components would have duplicated logic and scattered state management, making apps harder to maintain and scale. Composables solve this by letting developers write stateful logic once and reuse it everywhere. This leads to cleaner code, faster development, and fewer bugs, improving the user experience and developer happiness.
Where it fits
Before learning composables with reactive state, you should understand Vue's basic reactivity system, including ref and reactive, and how Vue components work. After mastering composables, you can explore advanced state management solutions like Pinia or Vuex, and learn about server-side rendering and Vue's Composition API in depth.
Mental Model
Core Idea
A composable with reactive state is like a smart toolbox that holds live data and logic, which updates automatically and can be shared across different parts of your app.
Think of it like...
Imagine a shared whiteboard in a classroom where everyone can write and erase notes. When one student updates the notes, everyone else instantly sees the change without needing to ask. The composable is the whiteboard, and the reactive state is the live notes.
┌─────────────────────────────┐
│       Composable Function    │
│ ┌─────────────────────────┐ │
│ │ Reactive State (ref/reactive) │
│ └─────────────────────────┘ │
│                             │
│ ┌─────────────────────────┐ │
│ │ Logic & Methods          │
│ └─────────────────────────┘ │
└─────────────┬───────────────┘
              │
              ▼
┌─────────────────────────────┐
│ Vue Components Using Composable │
│ - Access reactive state          │
│ - Call logic methods            │
└─────────────────────────────┘
Build-Up - 7 Steps
1
FoundationUnderstanding Vue Reactivity Basics
🤔
Concept: Learn how Vue tracks changes in data using reactive and ref.
Vue provides reactive() to make an object reactive and ref() to create a reactive primitive value. When these reactive values change, Vue automatically updates the parts of the UI that depend on them. For example, ref(0) creates a reactive number that updates the UI when changed.
Result
You can create reactive data that Vue tracks and updates in the UI automatically.
Understanding Vue's reactivity is essential because composables rely on this system to keep shared state live and synchronized.
2
FoundationWhat Is a Composable Function?
🤔
Concept: A composable is a plain function that uses Vue's reactive APIs to encapsulate state and logic.
A composable function returns reactive state and methods. It can be imported and used in any component to share logic. For example, a useCounter composable might return a count ref and increment method.
Result
You can reuse logic and state across components by calling the composable function.
Seeing composables as simple functions that bundle reactive state and logic helps demystify their power and simplicity.
3
IntermediateCreating Reactive State Inside Composables
🤔Before reading on: do you think reactive() or ref() is better for simple numbers? Commit to your answer.
Concept: Use ref for simple values and reactive for objects inside composables to create reactive state.
Inside a composable, you can create reactive state like this: import { ref, reactive } from 'vue'; function useCounter() { const count = ref(0); // reactive number const user = reactive({ name: 'Alice' }); // reactive object function increment() { count.value++; } return { count, user, increment }; } This state updates automatically wherever used.
Result
The composable holds live reactive data that updates all components using it.
Choosing the right reactive API inside composables ensures efficient and clear state management.
4
IntermediateSharing Reactive State Across Components
🤔Before reading on: if two components use the same composable, do they share the same state or have separate copies? Commit to your answer.
Concept: By default, calling a composable creates a new reactive state instance for each component, but you can share state by defining it outside the function.
Example of separate state: import { ref } from 'vue'; function useCounter() { const count = ref(0); return { count }; } Each component calling useCounter() gets its own count. Example of shared state: import { ref } from 'vue'; const sharedCount = ref(0); function useSharedCounter() { return { count: sharedCount }; } Now all components share the same count.
Result
You control whether state is isolated or shared by where you declare reactive variables.
Knowing how to share or isolate reactive state in composables is key to designing predictable app behavior.
5
IntermediateUsing Computed and Watch Inside Composables
🤔Before reading on: can you use computed properties and watchers inside composables? Commit to your answer.
Concept: Composables can include computed properties and watchers to derive or react to state changes.
Inside a composable: import { ref, computed, watch } from 'vue'; function useCounter() { const count = ref(0); const double = computed(() => count.value * 2); watch(count, (newVal) => { console.log('Count changed to', newVal); }); function increment() { count.value++; } return { count, double, increment }; } This adds reactive derived data and side effects.
Result
Composables can react to and compute new values from reactive state, making them powerful logic containers.
Using computed and watch inside composables lets you encapsulate complex reactive behavior cleanly.
6
AdvancedHandling Lifecycle and Cleanup in Composables
🤔Before reading on: do you think composables can access component lifecycle hooks? Commit to your answer.
Concept: Composables can use Vue lifecycle hooks like onMounted and onUnmounted to manage side effects and cleanup.
Example: import { ref, onMounted, onUnmounted } from 'vue'; function useWindowWidth() { const width = ref(window.innerWidth); function updateWidth() { width.value = window.innerWidth; } onMounted(() => window.addEventListener('resize', updateWidth)); onUnmounted(() => window.removeEventListener('resize', updateWidth)); return { width }; } This composable tracks window width reactively and cleans up listeners.
Result
Composables can manage side effects safely tied to component lifecycles.
Lifecycle awareness in composables prevents memory leaks and unexpected bugs in real apps.
7
ExpertOptimizing Composables with Dependency Injection
🤔Before reading on: can composables accept parameters to customize their behavior? Commit to your answer.
Concept: Composables can accept parameters and dependencies to be more flexible and testable, enabling dependency injection patterns.
Example: import { ref } from 'vue'; function useApiClient(apiClient) { const data = ref(null); async function fetchData() { data.value = await apiClient.get('/items'); } return { data, fetchData }; } You can pass different apiClient implementations for testing or different environments. This pattern improves composable reusability and decouples logic from implementation details.
Result
Composable functions become highly flexible and easier to maintain or test.
Injecting dependencies into composables unlocks advanced patterns for scalable and testable Vue apps.
Under the Hood
Vue's reactivity system uses proxies to track when reactive data is accessed or changed. When a composable creates reactive state with ref or reactive, Vue wraps the data in proxies that notify dependent components to update automatically. Computed properties track their dependencies and recalculate only when needed. Watchers observe reactive changes and run side effects. Lifecycle hooks inside composables register callbacks tied to component lifecycles, ensuring proper setup and cleanup.
Why designed this way?
Vue's Composition API and composables were designed to solve the limitations of the Options API, which scattered related logic across component options. By using functions that return reactive state and logic, Vue enables better code reuse, organization, and type inference. The proxy-based reactivity system is efficient and intuitive, allowing automatic UI updates without manual DOM manipulation. This design balances developer ergonomics with performance.
┌───────────────────────────────┐
│ Vue Reactivity System          │
│ ┌───────────────┐             │
│ │ Proxy Wrapper │◄────────────┤
│ └───────────────┘             │
│         ▲                     │
│         │ Tracks dependencies  │
│ ┌───────────────┐             │
│ │ Composable    │             │
│ │ (ref/reactive)│             │
│ └───────────────┘             │
│         ▲                     │
│         │ Returns reactive state│
│ ┌───────────────┐             │
│ │ Vue Component │             │
│ └───────────────┘             │
└───────────────────────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does calling a composable multiple times share the same reactive state by default? Commit to yes or no.
Common Belief:Calling a composable multiple times always shares the same reactive state across components.
Tap to reveal reality
Reality:Each call to a composable creates a new reactive state instance unless the state is declared outside the function to be shared.
Why it matters:Assuming shared state by default can cause bugs where components do not sync as expected or accidentally share state when isolation was intended.
Quick: Can you mutate reactive state directly without .value when using ref? Commit to yes or no.
Common Belief:You can treat ref variables like normal variables and assign to them directly without .value.
Tap to reveal reality
Reality:Ref variables require accessing or assigning their value via the .value property to maintain reactivity.
Why it matters:Forgetting .value leads to bugs where UI does not update because Vue cannot track changes properly.
Quick: Do composables automatically clean up side effects when components unmount? Commit to yes or no.
Common Belief:Composables automatically clean up any side effects like event listeners when components unmount.
Tap to reveal reality
Reality:Composables must explicitly use lifecycle hooks like onUnmounted to clean up side effects; otherwise, leaks occur.
Why it matters:Ignoring cleanup causes memory leaks and unexpected behavior in long-running apps.
Quick: Is it best practice to put all app state inside composables? Commit to yes or no.
Common Belief:All application state should be managed inside composables for simplicity.
Tap to reveal reality
Reality:While composables are great for reusable logic, large or global state is better managed with dedicated state management libraries like Pinia.
Why it matters:Misusing composables for all state can lead to tangled code and poor scalability.
Expert Zone
1
Composables can be combined and nested to build complex reactive logic while keeping code modular and testable.
2
Reactive state inside composables can be proxied multiple times if not careful, causing subtle bugs; understanding Vue's reactivity internals helps avoid this.
3
Using dependency injection in composables allows swapping implementations for testing or environment-specific behavior without changing component code.
When NOT to use
Avoid using composables for managing large global state or complex cross-component communication; instead, use state management libraries like Pinia or Vuex. Also, do not use composables to replace simple local state that belongs only inside a single component.
Production Patterns
In production, composables are used to encapsulate API calls, form handling, authentication logic, and shared UI state. Teams often create libraries of composables for common tasks to ensure consistency and reduce bugs. Dependency injection patterns in composables enable easier unit testing by mocking dependencies.
Connections
Functional Programming
Composable functions in Vue follow the functional programming principle of building complex behavior by combining small pure functions.
Understanding composability in functional programming helps grasp how Vue composables promote modular, reusable, and testable code.
Observer Pattern
Vue's reactive system underlying composables implements the observer pattern, where reactive data notifies dependents on change.
Recognizing this pattern clarifies how Vue tracks dependencies and updates UI automatically.
Shared Workspace Collaboration
Sharing reactive state across components via composables is like multiple people collaborating on a shared document in real time.
This connection highlights the importance of managing shared state carefully to avoid conflicts and ensure consistency.
Common Pitfalls
#1Forgetting to use .value when accessing or updating ref variables.
Wrong approach:const count = ref(0); count = 5; // wrong, does not update reactive state
Correct approach:const count = ref(0); count.value = 5; // correct, updates reactive state
Root cause:Misunderstanding that ref wraps the value and requires .value to access or mutate.
#2Declaring reactive state inside composable but expecting it to be shared across components.
Wrong approach:function useSharedCounter() { const count = ref(0); return { count }; } // Each component gets separate count
Correct approach:const count = ref(0); function useSharedCounter() { return { count }; } // All components share the same count
Root cause:Not realizing that reactive state declared inside the function is recreated per call.
#3Not cleaning up side effects like event listeners in composables.
Wrong approach:function useResize() { window.addEventListener('resize', () => { /* update state */ }); } // No cleanup on component unmount
Correct approach:import { onUnmounted } from 'vue'; function useResize() { const handler = () => { /* update state */ }; window.addEventListener('resize', handler); onUnmounted(() => window.removeEventListener('resize', handler)); } // Proper cleanup
Root cause:Ignoring Vue lifecycle hooks inside composables leads to memory leaks.
Key Takeaways
Composables with reactive state are reusable functions that bundle live data and logic for Vue components.
Vue's reactivity system uses proxies to track and update UI automatically when reactive state changes.
By controlling where reactive state is declared, you decide if it is shared or isolated across components.
Lifecycle hooks inside composables help manage side effects and prevent memory leaks.
Advanced composables use dependency injection to increase flexibility and testability in real-world apps.

Practice

(1/5)
1. What is the main purpose of a composable function in Vue 3?
easy
A. To create new Vue components automatically
B. To define global CSS styles for the app
C. To replace the Vue router functionality
D. To reuse reactive state and logic across multiple components

Solution

  1. Step 1: Understand composable function role

    Composable functions are designed to share reactive state and logic so multiple components can use them without repeating code.
  2. Step 2: Eliminate unrelated options

    Creating new Vue components automatically, defining global CSS styles for the app, and replacing the Vue router functionality are all unrelated to composables.
  3. Final Answer:

    To reuse reactive state and logic across multiple components -> Option D
  4. Quick Check:

    Composable = reusable reactive logic [OK]
Hint: Composable = reusable reactive logic across components [OK]
Common Mistakes:
  • Confusing composables with components
  • Thinking composables handle styling or routing
  • Assuming composables create UI elements
2. Which of the following is the correct way to create a reactive state inside a composable using Vue 3 Composition API?
easy
A. const count = reactive(0)
B. const count = ref(0)
C. const count = reactive({ value: 0 })
D. const count = ref({ value: 0 })

Solution

  1. Step 1: Recall ref and reactive usage

    ref is used to create a reactive primitive like a number, string, or boolean. reactive is for objects.
  2. Step 2: Analyze options

    const count = ref(0) correctly uses ref(0) for a reactive number. const count = reactive(0) is invalid because reactive expects an object, not a primitive. const count = reactive({ value: 0 }) wraps a number in an object but is less common for simple primitives. const count = ref({ value: 0 }) uses ref with an object, which works but is less idiomatic than reactive for objects.
  3. Final Answer:

    const count = ref(0) -> Option B
  4. Quick Check:

    Primitive reactive state uses ref() [OK]
Hint: Use ref() for primitives, reactive() for objects [OK]
Common Mistakes:
  • Using reactive() with primitive values
  • Using ref() for complex objects instead of reactive()
  • Confusing ref({}) and reactive({}) usage
3. Given this composable code, what will be logged when increment() is called twice and then count.value is logged?
import { ref } from 'vue';
export function useCounter() {
  const count = ref(0);
  function increment() {
    count.value++;
  }
  return { count, increment };
}

const { count, increment } = useCounter();
increment();
increment();
console.log(count.value);
medium
A. 2
B. 0
C. 1
D. undefined

Solution

  1. Step 1: Understand initial state and increments

    The count starts at 0. Each call to increment() increases count.value by 1.
  2. Step 2: Calculate final value after two increments

    After two calls, count.value = 0 + 1 + 1 = 2.
  3. Final Answer:

    2 -> Option A
  4. Quick Check:

    Two increments add 2 to count.value [OK]
Hint: Increment ref.value to update reactive count [OK]
Common Mistakes:
  • Forgetting to access .value on ref
  • Assuming count is a plain number, not ref
  • Confusing initial value with updated value
4. Identify the error in this composable code snippet:
import { reactive } from 'vue';
export function useUser() {
  const user = reactive({ name: 'Alice', age: 30 });
  function updateName(newName) {
    user.name = newName;
  }
  return { user, updateName };
}

const { user, updateName } = useUser();
updateName('Bob');
console.log(user.name);
medium
A. No error, code works correctly
B. Cannot assign to reactive object properties directly
C. updateName function is not returned
D. Missing import of ref

Solution

  1. Step 1: Check reactive object usage

    The user object is created with reactive and its properties can be updated directly.
  2. Step 2: Verify function and return

    The updateName function updates user.name correctly and is returned for use.
  3. Final Answer:

    No error, code works correctly -> Option A
  4. Quick Check:

    Reactive objects allow direct property assignment [OK]
Hint: reactive objects allow direct property changes [OK]
Common Mistakes:
  • Thinking reactive properties are read-only
  • Forgetting to return functions from composable
  • Confusing ref and reactive usage
5. You want to create a composable that tracks a list of tasks reactively and provides a function to add a new task only if it is not empty. Which of the following implementations correctly achieves this?
hard
A. import { ref } from 'vue'; export function useTasks() { const tasks = ref([]); function addTask(task) { tasks.push(task); } return { tasks, addTask }; }
B. import { reactive } from 'vue'; export function useTasks() { const tasks = reactive([]); function addTask(task) { if (task) tasks.value.push(task); } return { tasks, addTask }; }
C. import { ref } from 'vue'; export function useTasks() { const tasks = ref([]); function addTask(task) { if (task) tasks.value.push(task); } return { tasks, addTask }; }
D. import { reactive } from 'vue'; export function useTasks() { const tasks = reactive([]); function addTask(task) { if (task.length > 0) tasks.value.push(task); } return { tasks, addTask }; }

Solution

  1. Step 1: Choose correct reactive type for array

    Arrays as reactive state are best handled with ref([]) so we access the array via tasks.value.
  2. Step 2: Validate addTask function logic

    import { ref } from 'vue'; export function useTasks() { const tasks = ref([]); function addTask(task) { if (task) tasks.value.push(task); } return { tasks, addTask }; } checks if task is truthy (not empty) before pushing to tasks.value, which is correct. import { reactive } from 'vue'; export function useTasks() { const tasks = reactive([]); function addTask(task) { if (task) tasks.value.push(task); } return { tasks, addTask }; } uses reactive([]) but incorrectly accesses tasks.value.push(task) since reactive does not have a value property. import { ref } from 'vue'; export function useTasks() { const tasks = ref([]); function addTask(task) { tasks.push(task); } return { tasks, addTask }; } pushes without checking if task is empty. import { reactive } from 'vue'; export function useTasks() { const tasks = reactive([]); function addTask(task) { if (task.length > 0) tasks.value.push(task); } return { tasks, addTask }; } incorrectly tries to access tasks.value when tasks is reactive, which does not have a value property.
  3. Final Answer:

    use ref([]) with if (task) tasks.value.push(task) -> Option C
  4. Quick Check:

    Use ref([]) for arrays and check input before push [OK]
Hint: Use ref([]) for reactive arrays and check input before push [OK]
Common Mistakes:
  • Using reactive([]) for arrays instead of ref([])
  • Forgetting to check if task is empty
  • Accessing .value on reactive arrays
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard