Unlock Vue Composables: An Introduction to VueUse


Unlock Vue Composables: An Introduction to VueUse

The landscape of frontend development is constantly evolving, and the Vue.js ecosystem is no exception. With the advent of Vue 3 and its powerful Composition API, developers gained a new, more flexible way to organize and reuse logic within their applications. This shift opened the door for a pattern known as “composables” – functions that encapsulate and manage stateful logic. While the Composition API provides the foundation, writing robust, reusable, and well-tested composables for common tasks can still be time-consuming. Enter VueUse, a community-driven collection of essential Vue Composition Utilities that acts like a Swiss Army knife for Vue developers.

VueUse provides hundreds of ready-to-use composable functions covering a vast range of functionalities, from tracking sensor data (like mouse position or network status) and managing browser APIs (like clipboard or fullscreen) to handling state, animations, and common utilities like debouncing and throttling. It empowers developers to write cleaner, more maintainable, and feature-rich Vue applications with significantly less boilerplate code.

This article serves as a comprehensive introduction to VueUse. We’ll explore why composables are beneficial, delve into the core concepts and advantages of VueUse, walk through installation and basic usage, showcase various categories of functions with practical examples, discuss advanced patterns and best practices, and ultimately demonstrate how VueUse can dramatically accelerate your Vue development workflow. Whether you’re new to the Composition API or an experienced Vue developer looking to streamline your code, understanding VueUse is key to unlocking the full potential of modern Vue development.

The “Why”: Understanding the Need for Composables and VueUse

Before diving into VueUse itself, it’s crucial to understand the problems it solves and the foundation it builds upon: the Vue Composition API.

Limitations of the Options API and Mixins

In Vue 2, the primary way to structure components was the Options API. While intuitive for simpler components, it presented challenges as applications grew in complexity:

  1. Logic Fragmentation: Features often required logic spread across different options (data, methods, computed, watch, mounted, etc.). Tracking all pieces related to a single feature could become difficult in large components.
  2. Reusability Challenges (Mixins): The primary mechanism for reusing logic across components was Mixins. However, mixins suffer from several drawbacks:
    • Source Obscurity: It’s not immediately clear which properties or methods injected by a mixin are being used within a component, or where they originated.
    • Namespace Collisions: Multiple mixins might define properties or methods with the same name, leading to conflicts that are hard to debug. Overwriting is often silent.
    • Implicit Dependencies: Mixins can rely on properties or methods existing in the component they are mixed into, creating tight coupling that isn’t explicit.
    • Configuration Difficulty: Passing configuration to mixins is cumbersome, often requiring conventions or relying on shared data properties.

These limitations often led to components that were hard to refactor, test, and maintain, especially in large-scale applications or team environments.

Enter the Composition API

Vue 3 introduced the Composition API as an alternative, additive way to organize component logic. Its core idea is to allow developers to group code by logical concern rather than by option type. This is primarily achieved through the setup() function (or the more concise <script setup> syntax).

Key benefits of the Composition API include:

  • Logical Collocation: Code related to a specific feature can be kept together, making it easier to read, understand, and maintain.
  • Improved Reusability: Logic can be extracted into standalone JavaScript/TypeScript functions called composables.
  • Better Type Inference: The functional nature of the Composition API lends itself well to TypeScript, providing more robust type checking and autocompletion.
  • Increased Flexibility: It offers more powerful ways to manage reactivity and component lifecycle.

What is a Composable Function?

A composable function (or simply “composable”) is essentially a function that leverages Vue’s Composition API (specifically its reactivity system using ref, reactive, computed, watch, etc.) to encapsulate and manage stateful logic.

A simple example could be a counter:

“`javascript
// composables/useCounter.js
import { ref, readonly } from ‘vue’;

export function useCounter(initialValue = 0) {
const count = ref(initialValue);

function increment() {
count.value++;
}

function decrement() {
count.value–;
}

// Expose the state and methods
// Use readonly to prevent external modification of the count ref directly
return {
count: readonly(count), // Expose as read-only
increment,
decrement,
};
}
“`

This useCounter function can then be easily used within any Vue component’s setup context:

“`vue


“`

This pattern is incredibly powerful. It allows for:

  • Clear Source: You know exactly where count, increment, and decrement come from (useCounter).
  • No Namespace Collisions: Variables are locally scoped within setup. If names clash, you can easily rename them during destructuring (const { count: counterValue } = useCounter();).
  • Explicit Dependencies: Composables clearly define what they return. Any dependencies (like props or other refs passed in) are explicit arguments.
  • Easy Configuration: Pass arguments to the composable function for configuration.
  • Testability: Composables are just functions; they can often be tested independently of any specific component.

The Need for VueUse

While creating your own composables is fundamental, developers quickly realized that many common patterns and browser API interactions were being reimplemented across different projects. Tasks like:

  • Tracking mouse coordinates.
  • Listening for keyboard shortcuts.
  • Persisting state to localStorage.
  • Detecting network online/offline status.
  • Managing element visibility within the viewport.
  • Debouncing or throttling function calls.
  • Fetching data.

Writing these from scratch every time involves boilerplate, potential edge cases, and maintenance overhead. This is precisely where VueUse comes in. It provides a comprehensive, high-quality, community-vetted library of pre-built composables for these common tasks and many, many more.

Introducing VueUse: The Swiss Army Knife for Vue Composables

VueUse, created by Anthony Fu and maintained by a vibrant community, describes itself as a “Collection of essential Vue Composition Utilities.” Think of it as Lodash/Underscore, but specifically designed for Vue’s reactivity system and common frontend development needs.

Its core mission is to make developers’ lives easier by providing ready-made solutions for recurring problems, allowing them to focus on the unique logic of their applications.

Key Benefits of Using VueUse

  1. Extensive Collection: VueUse offers hundreds of functions covering a wide spectrum of functionalities. From browser APIs and sensors to state management and animations, chances are if you need a common utility, VueUse has it.
  2. Community-Driven and Well-Maintained: Being an open-source project with a large number of contributors ensures that the library stays up-to-date, bugs are fixed promptly, and new useful functions are continuously added.
  3. Tree-Shakable: You only bundle the functions you actually use. Thanks to ES Modules, unused composables are automatically removed during the build process, keeping your application bundle size optimized.
  4. TypeScript First: Written entirely in TypeScript, VueUse provides excellent type safety and autocompletion out-of-the-box, significantly improving the development experience, especially in larger projects.
  5. Consistent API: VueUse functions generally follow consistent patterns, making them predictable and easy to learn. Many return reactive refs or objects containing refs, integrating seamlessly with Vue’s reactivity system.
  6. Excellent Documentation: The official VueUse website offers clear documentation, interactive demos, and usage examples for every function.
  7. Works with Vue 2 & 3: While designed with Vue 3’s Composition API in mind, VueUse can also be used in Vue 2 projects via the @vue/composition-api plugin.
  8. Server-Side Rendering (SSR) Compatible: Most functions are designed to be SSR-friendly, checking for the existence of browser-specific APIs before attempting to use them.

Using VueUse means writing less code, reducing potential bugs, leveraging community expertise, and accelerating development cycles.

Getting Started with VueUse

Integrating VueUse into your Vue project is straightforward.

Installation

You can install VueUse using your preferred package manager:

“`bash

Using npm

npm install @vueuse/core

Using yarn

yarn add @vueuse/core

Using pnpm

pnpm add @vueuse/core
“`

The @vueuse/core package contains the main collection of composables. There are also other add-on packages like @vueuse/head, @vueuse/motion, etc., for more specialized functionalities, which we’ll touch upon later.

Basic Usage

Using a VueUse function is as simple as importing it and calling it within your component’s setup function (or <script setup>).

Let’s replace our manual localStorage handling with VueUse’s useLocalStorage composable.

Example: Using useLocalStorage

Imagine you want to store a user’s theme preference (‘light’ or ‘dark’) in localStorage and keep it synchronized with a reactive variable.

“`vue


“`

In this example, useLocalStorage('theme', 'light') does all the heavy lifting:

  1. It retrieves the value associated with the key 'theme' from localStorage.
  2. If the key doesn’t exist, it initializes the localStorage item with the provided default value ('light').
  3. It returns a ref (theme) whose value is automatically synchronized with the localStorage item.
  4. It listens for storage events, so if the value changes in another browser tab, the theme ref updates automatically.

This single line replaces several lines of manual setup and watching, making the component code much cleaner and more focused on its primary purpose. This is the core value proposition of VueUse – abstracting away common boilerplate into reusable, reliable functions.

Exploring Key VueUse Categories and Functions

VueUse organizes its vast collection of functions into logical categories. Let’s explore some of the most important categories and highlight a few popular functions within each. This is by no means an exhaustive list, but it provides a good overview of the library’s breadth.

(Note: Code examples below assume <script setup> syntax for brevity.)

1. Sensors

These composables react to changes in device sensors or user input.

  • useMouse: Tracks the mouse coordinates reactively.
    vue
    <script setup>
    import { useMouse } from '@vueuse/core';
    const { x, y, sourceType } = useMouse(); // sourceType: 'mouse', 'touch', etc.
    </script>
    <template>Mouse position: {{ x }}, {{ y }}</template>
  • useScroll: Tracks the scroll position of an element or the window.
    vue
    <script setup>
    import { ref } from 'vue';
    import { useScroll } from '@vueuse/core';
    const el = ref(null); // Ref attached to a scrollable element
    const { x, y, isScrolling, arrivedState, directions } = useScroll(el);
    // arrivedState: { left, right, top, bottom } - boolean flags
    // directions: { left, right, top, bottom } - boolean flags indicating scroll direction
    </script>
    <template>
    <div ref="el" style="width: 300px; height: 200px; overflow: auto; border: 1px solid #ccc;">
    <!-- Long content here -->
    </div>
    <p>Scroll position: X={{ x.toFixed(0) }}, Y={{ y.toFixed(0) }}</p>
    <p>Is Scrolling: {{ isScrolling }}</p>
    <p>Arrived Top: {{ arrivedState.top }}</p>
    </template>
  • useNetwork: Tracks the network connection status (online/offline, effective type, etc.).
    vue
    <script setup>
    import { useNetwork } from '@vueuse/core';
    const { isOnline, offlineAt, downlink, effectiveType, rtt } = useNetwork();
    </script>
    <template>
    <p>Network Status: {{ isOnline ? 'Online' : 'Offline' }}</p>
    <p v-if="!isOnline">Offline since: {{ offlineAt }}</p>
    <p v-if="isOnline">Effective Type: {{ effectiveType }}</p>
    </template>
  • useKeyboardJs / onKeyStroke: Detects keyboard combinations or specific key presses. onKeyStroke is simpler for basic cases.
    vue
    <script setup>
    import { ref } from 'vue';
    import { onKeyStroke } from '@vueuse/core';
    const message = ref('');
    // Listen for 'Enter' key press on the whole document
    onKeyStroke('Enter', (e) => {
    e.preventDefault(); // Prevent default form submission, etc.
    message.value = 'Enter key pressed!';
    });
    // Listen for 'Ctrl+S' or 'Cmd+S'
    onKeyStroke(['Control+s', 'Meta+s'], (e) => {
    e.preventDefault();
    message.value = 'Save shortcut triggered!';
    }, { eventName: 'keydown' }); // Specify event type if needed
    // Listen only when an input is focused
    const inputEl = ref(null);
    onKeyStroke('Escape', () => {
    message.value = 'Escape pressed inside input!';
    inputEl.value?.blur();
    }, { target: inputEl });
    </script>
    <template>
    <p>{{ message }}</p>
    <input ref="inputEl" type="text" placeholder="Press Esc inside me" />
    </template>
  • Other Sensors: useDeviceMotion, useDeviceOrientation, useGeolocation, useIdle (detects user inactivity), useMediaQuery (reactively tracks CSS media queries), useWindowSize, useElementSize, etc.

2. State Management

Composables focused on managing reactive state, often involving persistence or sharing.

  • useLocalStorage / useSessionStorage: Reactive synchronization with browser storage (shown earlier).
  • useStorage: A generic version that can work with localStorage, sessionStorage, or even custom storage drivers.
  • refWithHistory: Creates a ref with undo/redo history tracking.
    vue
    <script setup>
    import { refWithHistory } from '@vueuse/core';
    const count = ref(0); // Can use existing ref
    const { history, undo, redo, canUndo, canRedo, commit, clear } = refWithHistory(count, { capacity: 10 });
    // commit() manually saves a snapshot if needed (often automatic)
    </script>
    <template>
    <p>Count: {{ count }}</p>
    <button @click="count++">Increment</button>
    <button @click="count--">Decrement</button>
    <br>
    <button @click="undo" :disabled="!canUndo">Undo</button>
    <button @click="redo" :disabled="!canRedo">Redo</button>
    <p>History ({{ history.length }} items):</p>
    <pre>{{ history }}</pre>
    </template>
  • createGlobalState: Easily create state that can be shared across multiple component instances without prop drilling or complex stores.
    “`javascript
    // store/counter.js
    import { createGlobalState, useStorage } from ‘@vueuse/core’;

    export const useGlobalCounter = createGlobalState(
    // The factory function to create the state
    () => {
    // Example: Persist the global counter using useStorage
    const count = useStorage(‘global-counter’, 0);

    const increment = () => count.value++;
    const decrement = () => count.value--;
    
    return { count, increment, decrement };
    

    }
    );
    vue



    vue



    * **`computedAsync`**: Creates a computed property whose value is derived from an asynchronous operation. Handles loading/error states.vue


    “`

3. Elements

Functions for interacting with DOM elements reactively.

  • useElementVisibility: Tracks whether an element is currently visible within the viewport or another scrollable container.
    “`vue


    * **`useElementSize`**: Reactively tracks the dimensions (width/height) of an element.vue


    * **`onClickOutside`**: Triggers a handler function when a click occurs outside a specified element. Perfect for closing dropdowns or modals.vue


    * **`useEventListener`**: A convenient way to add and automatically remove DOM event listeners. Handles cleanup automatically on component unmount.vue


    ``
    * **Other Element Utilities:**
    useElementBounding(reactivegetBoundingClientRect),useDraggable(make elements draggable),useDropZone(detect files dropped onto an element),useIntersectionObserver(wrapper forIntersectionObserver` API).

4. Browser APIs

Composables that provide reactive wrappers around native browser APIs.

  • useClipboard: Interact with the clipboard (copy text).
    “`vue


    * **`useFetch`**: A powerful wrapper around the `fetch` API for making HTTP requests. Handles state (loading, error, data), cancellation, and provides convenience options.vue


    * **`useFullscreen`**: Toggle fullscreen mode for an element or the entire page.vue


    * **`usePermission`**: Query browser permissions status reactively (e.g., `geolocation`, `notifications`, `camera`).vue


    ``
    * **Other Browser API Wrappers:**
    useTitle(reactively control document title),useFavicon(change favicon dynamically),useBroadcastChannel(communication between tabs/windows),useWebWorker/useWebWorkerFn(run scripts in background threads),useShare(Web Share API),useUrlSearchParams,useWebSocket`.

5. Animation

Utilities for creating animations and managing time-based events.

  • useTransition: Eases a numeric value or array/object of numeric values over time. Great for smooth animations without CSS transitions.
    “`vue


    * **`useRafFn` (RequestAnimationFrame)**: Call a function on every animation frame, optimized for performance. Automatically pauses/resumes based on page visibility.vue


    “`

6. Utilities

A broad category for common programming patterns and helpers.

  • useDebounceFn / useThrottleFn: Wrap a function to limit its execution rate (debouncing waits for a pause, throttling executes at most once per interval).
    “`vue


    * **`watchDebounced` / `watchThrottled`**: Reactive `watch` or `watchEffect` with debouncing/throttling built-in.vue


    * **`promiseTimeout`**: Wraps a promise with a timeout. Rejects if the promise doesn't resolve/reject within the specified time.
    * **`until`**: A utility to create a promise that resolves when a reactive condition becomes true.
    vue


    ``
    * **Other Utilities:**
    tryOnMounted,tryOnUnmounted(safely run lifecycle hooks),useVModel(simplifiesv-modellogic for custom components),syncRef(keep multiple refs in sync),logicAnd,logicOr,logicNot(reactive logical operations),templateRef(alternative toref()` for template refs with stricter typing).

Deep Dive into Specific Composables (Illustrative Examples)

Let’s take a closer look at a few commonly used composables to better understand their usage patterns, configuration options, and returned values.

1. useMouse – Tracking Cursor Position

Problem: You need to display the mouse coordinates or create effects that react to cursor movement.

Solution:

“`vue


“`

Explanation:

  • useMouse() returns an object containing reactive refs: x, y, and sourceType.
  • x and y update automatically whenever the mouse (or touch point) moves over the target (window by default).
  • sourceType indicates whether the last event was from a ‘mouse’ or ‘touch’.
  • Options allow customizing the target element, coordinate system (client, page, screen), whether to include touch, and reset behavior.
  • It seamlessly integrates with Vue’s reactivity, allowing direct use in templates or computed properties.

2. useFetch – Simplified Data Fetching

Problem: Fetching data from an API involves handling loading states, errors, response parsing, and potentially re-fetching when parameters change.

Solution:

“`vue


“`

Explanation:

  • useFetch takes the URL (can be a string or a ref) and an optional options object.
  • It returns a rich object with reactive refs for data, error, isFetching, statusCode, etc.
  • Chaining .json(), .text(), .blob(), etc., simplifies response parsing.
  • refetch: true makes it incredibly easy to create dynamic data fetching that reacts to parameter changes (like userId in the example).
  • immediate: false prevents fetching on mount, requiring a manual call to execute.
  • Lifecycle hooks (beforeFetch, afterFetch, onFetchError) provide fine-grained control over the request/response cycle.
  • Built-in abort functionality (canAbort, abort) helps manage request cancellation.

3. onClickOutside – Detecting Clicks Outside an Element

Problem: You need to close a dropdown menu, modal, or popup when the user clicks anywhere else on the page.

Solution:

“`vue


“`

Explanation:

  • onClickOutside takes the target element ref (or array of refs) and a handler function.
  • The handler is called only when a click/pointerdown event occurs outside the specified target(s).
  • The crucial ignore option allows specifying elements (using refs or CSS selectors) whose clicks should not trigger the handler. This is essential for preventing the dropdown from closing when its own trigger button is clicked.
  • It handles event listener setup and cleanup automatically.

Advanced Concepts and Best Practices

While basic usage of VueUse is straightforward, understanding some advanced concepts and best practices will help you leverage it more effectively.

1. Combining Composables

The true power of the composable pattern shines when you combine multiple functions (either from VueUse or your own custom ones) to build more complex features.

Example: Debounced Search Input with Loading State

“`vue