Medium15 minVue.js Fundamentals
UpdatedAug 5, 2026
Edit

Reusable logic with composables

CONCEPTS:Vue ComposablesVue Composition API

Question Variations

  • "How do composables differ from mixins?"
  • "How do you clean up an event listener created in a composable?"
  • "How can a composable intentionally share state across components?"
  • "What makes a composable easy to test?"

Why This Is Asked

Composables are the standard Vue 3 pattern for sharing stateful logic. Interviewers want to know whether you can extract a focused feature while preserving lifecycle cleanup, keeping per-component state isolated by default, and presenting a small typed API to callers.

Key Concepts

  • Composition: A composable groups related reactive state and operations in a function.
  • Lifecycle scope: Hooks called inside a composable are attached to the component that invokes it during setup.
  • State ownership: State created in the function is per call; module-level state is deliberately shared.
  • API design: Return only the refs, computed values, and functions callers need.

Question Variations

  • “How do composables differ from mixins?”
  • “How do you clean up an event listener created in a composable?”
  • “How can a composable intentionally share state across components?”
  • “What makes a composable easy to test?”

Answers by Technology

+ Add Variant
Vue.jsImprove this answer ✏️

Expected Answer (Vue.js 3.5.40)

A composable is a regular TypeScript function, normally named with use, that calls Composition API functions and returns a focused reactive API. It packages behavior rather than a component template: for example, a useEventListener composable can register an event listener and clean it up, while callers decide how to render the result. The function should return the refs, computed values, and actions consumers need instead of exposing its internal implementation.

State declared inside the composable function is created for each caller, so two components using it receive independent state. State declared at module scope is shared by every caller; that can implement a small global store intentionally, but accidental module-level state causes cross-component leakage. Lifecycle hooks used by a composable attach to the component that calls it, so invoke lifecycle-dependent composables synchronously during component setup.

In Vue 3.5.40, composables are the preferred alternative to mixins for reusable stateful logic because their inputs, outputs, and dependencies are explicit and naturally typed with TypeScript.

Why It Matters

Composables prevent repeated event cleanup, request state, and derived-state code across components. A poorly scoped composable can instead leak state between users or leave listeners active after unmount, causing incorrect UI and memory leaks.

Example Code

import { onUnmounted, readonly, ref, type Ref } from 'vue';

interface MousePosition {
  x: number;
  y: number;
}

interface UseMousePosition {
  position: Readonly<Ref<MousePosition>>;
}

export function useMousePosition(): UseMousePosition {
  const position = ref<MousePosition>({ x: 0, y: 0 });
  const updatePosition = (event: MouseEvent): void => {
    position.value = { x: event.clientX, y: event.clientY };
  };

  window.addEventListener('mousemove', updatePosition);
  onUnmounted(() => window.removeEventListener('mousemove', updatePosition));

  return { position: readonly(position) };
}

Common Mistakes

  • Creating state at module scope unintentionally: Every caller then reads and writes the same ref, so one component can unexpectedly change another component’s state.
  • Forgetting lifecycle cleanup: A listener, timer, or subscription continues after unmount and can leak memory or update a component that no longer exists.
  • Calling a lifecycle-dependent composable after an await: The active component instance is no longer guaranteed, so lifecycle hooks may not register with the intended component.

Follow-up Questions

  • How can a composable intentionally share state? (Answer: Define the shared ref at module scope or use a dedicated store, then make the ownership explicit.)
  • How would you test this composable? (Answer: Invoke it in a mounted test component or effect scope, dispatch a mouse event, assert the returned ref, and unmount to verify cleanup.)

References