Create Adaptable Composable

Create Adaptable Composable

Create Adaptable Composable automation and integration

Category: productivity Source: vuejs-ai/skills

Create Adaptable Composable is a community skill for building reusable Vue.js composables that adapt to different component contexts, covering reactive state management, dependency injection, SSR compatibility, and composition patterns for flexible Vue logic extraction.

What Is This?

Overview

Create Adaptable Composable provides patterns for writing Vue composables that work reliably across different usage contexts. It covers reactive state encapsulation with ref and reactive for isolated composable instances, dependency injection using provide and inject for configurable composable behavior, SSR-safe patterns that avoid accessing browser APIs during server rendering, cleanup and lifecycle management with onUnmounted and watchEffect for preventing memory leaks, and TypeScript generics for type-safe composable APIs with flexible input and return types. The skill enables Vue developers to extract component logic into reusable composables that adapt to both client and server environments without modification.

Who Should Use This

This skill serves Vue.js developers extracting reusable logic from components into composables, teams building shared composable libraries for multiple Vue applications, and engineers writing SSR-compatible composables for Nuxt or custom server setups.

Why Use It?

Problems It Solves

Composables that access window or document objects fail during server-side rendering. Shared reactive state between composable instances causes unintended side effects across components. Composables without proper cleanup leak event listeners and timers when components unmount. Generic composables lack type safety when accepting different data shapes.

Core Highlights

State factory creates isolated reactive instances per component usage. SSR guard detects server context to skip browser-only operations. Lifecycle integration cleans up side effects on component unmount. Generic types enable type-safe composable APIs with flexible inputs.

How to Use It?

Basic Usage

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

export function useDebounce<T>(
  value: Ref<T>,
  delay: number = 300
): Ref<T> {
  const debounced = ref(
    value.value) as Ref<T>;
  let timer: ReturnType<
    typeof setTimeout> | null = null;

  const stop = watch(value, (val) => {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      debounced.value = val;
    }, delay);
  });

  onUnmounted(() => {
    stop();
    if (timer) clearTimeout(timer);
  });

  return debounced;
}

// Usage in component
// const search = ref('');
// const debouncedSearch =
//   useDebounce(search, 500);

Real-World Examples

import {
  ref, onMounted, onUnmounted,
  computed
} from 'vue';

interface FetchOptions<T> {
  immediate?: boolean;
  transform?: (data: unknown) => T;
}

export function useFetch<T>(
  url: string | (() => string),
  options: FetchOptions<T> = {}
) {
  const data = ref<T | null>(null);
  const error = ref<string | null>(
    null);
  const loading = ref(false);
  let controller: AbortController
    | null = null;

  const resolvedUrl = computed(() =>
    typeof url === 'function'
      ? url() : url);

  async function execute() {
    if (controller) controller.abort();
    controller = new AbortController();
    loading.value = true;
    error.value = null;
    try {
      const resp = await fetch(
        resolvedUrl.value,
        { signal: controller.signal });
      if (!resp.ok) throw new Error(
        `HTTP ${resp.status}`);
      const raw = await resp.json();
      data.value = options.transform
        ? options.transform(raw)
        : raw;
    } catch (e: unknown) {
      if (e instanceof Error &&
          e.name !== 'AbortError') {
        error.value = e.message;
      }
    } finally {
      loading.value = false;
    }
  }

  if (options.immediate !== false) {
    onMounted(execute);
  }
  onUnmounted(() => {
    controller?.abort();
  });

  return { data, error, loading,
    execute };
}

Advanced Tips

Use getCurrentInstance to detect SSR context and conditionally skip browser-only side effects. Return cleanup functions from composables that need explicit teardown beyond component lifecycle. Accept MaybeRef parameters using toValue to support both raw values and reactive refs as inputs.

When to Use It?

Use Cases

Build a shared composable library with data fetching, form validation, and debounce utilities for multiple Vue applications. Create an SSR-safe composable that manages browser storage with server fallbacks. Implement a real-time composable that handles WebSocket connections with automatic reconnection and cleanup.

Related Topics

Vue Composition API, reactive programming, TypeScript generics, SSR compatibility, and code reuse patterns.

Important Notes

Requirements

Vue 3 with Composition API support enabled. TypeScript for type-safe generic composables. Understanding of Vue reactivity system and lifecycle hooks.

Usage Recommendations

Do: clean up timers, event listeners, and subscriptions in onUnmounted to prevent memory leaks. Use ref for primitive values and reactive for objects to follow Vue reactivity conventions. Return readonly refs when consumers should not modify composable state directly.

Don't: access window or document objects without checking for SSR context first. Share mutable state between composable instances unless explicitly designing a global store. Use composables outside setup functions where lifecycle hooks are not available.

Limitations

Composables must be called inside setup functions or other composables to access lifecycle hooks. Reactive state from composables loses reactivity when destructured without toRefs. Complex composable dependency chains can become difficult to debug without clear documentation.