Hook — 05 : 59 a.m., Santo Domingo ↔ Bogotá sprint retro
The Caribbean sunrise backlit my balcony when Camilo in Bogotá sighed over Zoom: “Why does every feature toggle fetch user prefs separately?” We’d copied the same watcher into five components. Coffee in hand, I spiked the duplication by extracting a tiny useFeatureFlags() composable, published it to our internal npm registry, and watched bundle size drop while dev velocity soared. Camilo pushed his ticket ahead of schedule—and still made his cycling ride up Monserrate. That win kicks off today’s deep dive into building, packaging, and sharing composable utilities in Vue 3.


Why Composables Are the Secret Sauce


Anatomy of a Simple Composable

tsCopyEdit// composables/useNow.ts
import { ref, onMounted, onUnmounted } from 'vue';

export function useNow(interval = 1000) {
  const now = ref(new Date());

  let id: number;
  onMounted(() => (id = window.setInterval(() => (now.value = new Date()), interval)));
  onUnmounted(() => clearInterval(id));

  return { now };
}

Consuming the Composable

vueCopyEdit<script setup lang="ts">
import { useNow } from '@/composables/useNow';
const { now } = useNow(500);
</script>

<template>
  <time :datetime="now.toISOString()">{{ now.toLocaleTimeString() }}</time>
</template>

One line imports the clock anywhere—from dashboards to kiosks.


Building a Fetch-and-Cache Utility

tsCopyEdit// composables/useFetchOnce.ts
import { ref } from 'vue';

const cache = new Map<string, unknown>();

export function useFetchOnce<T = unknown>(url: string) {
  const data = ref<T | null>(null);
  const error = ref<Error | null>(null);
  const loading = ref(false);

  if (cache.has(url)) data.value = cache.get(url) as T;
  else {
    loading.value = true;
    fetch(url)
      .then(r => r.json())
      .then(j => { cache.set(url, j); data.value = j; })
      .catch(e => (error.value = e))
      .finally(() => (loading.value = false));
  }
  return { data, error, loading };
}

Now every component calling /api/settings shares the same promise and avoids duplicate network chatter—handy for users on prepaid 3 G in Costa Rica.


Remote-Work Insight Box

During a sprint in Panama City, flaky Wi-Fi forced us offline every few minutes. We wrapped Firebase listeners in a useOfflineQueue() composable that queued writes and replayed them when navigator.onLine flipped. QA in Medellín thanked us, and product managers never saw a lost update.


Packaging & Publishing Internal Composables

  1. Create a workspace packages/vue-utils/.
  2. Add package.json: jsonCopyEdit{ "name": "@acme/vue-utils", "version": "1.0.0", "exports": "./dist/index.js", "types": "./dist/index.d.ts", "peerDependencies": { "vue": "^3.3.0" } }
  3. Bundle with Vite + Library mode: tsCopyEdit// vite.config.ts export default defineConfig({ build: { lib: { entry: 'src/index.ts', name: 'VueUtils' } } });
  4. Publish to GitHub Packages or Verdaccio: npm publish --access public.
  5. Consume: npm i @acme/vue-utils.

Common Pitfalls & How to Dodge Them

PitfallSymptomFix
Reactive refs leak across appsSingleton state imported twiceExport factory functions, not singletons
💥 SSR hydration warningsWindow API used unguardedGate in if (process.client) or onMounted
Bundle bloatIncluding entire lodashImport only needed helpers or use native APIs
Side-effects not cleanedMemory leaks on route changePair every addEventListener with removal in onUnmounted

Performance & Accessibility Checkpoints


Call-Out Table

Concept / CLIOne-liner purpose
defineStore + composablesShare Pinia logic independent of UI
provide / injectGlobal context fallbacks
useEventListenerAbstract DOM events with auto-cleanup
npm version patchBump util package semver
changesetsAutomate release notes

Diagram Description

Components A + B → import useFetchOnce() → composable checks Map cache → single fetch → shared reactive ref updates both components.


Wrap-Up

Composable utilities turn scattered snippets into battle-tested building blocks. Keep each hook single-purpose, clean up side-effects, type everything, and ship them via a private registry. Your future self—and every teammate coding from cafés in Santo Domingo, Bogotá, or Florianópolis—will thank you.

Drop your favorite composable patterns or questions below; I’ll respond between flights and late-night arepa sessions.

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x