composables
Teaches Vue composables for reusable stateful logic with the Composition API. Use when extracting shared reactive state, side effects, or computed logic that multiple components need.
What this skill does
# Composables
## Table of Contents
- [When to Use](#when-to-use)
- [Instructions](#instructions)
- [Details](#details)
- [Source](#source)
Composables are functions that encapsulate and reuse stateful logic using Vue's Composition API. They replace the Options API's fragmented code organization with clean, composable functions that can be shared across components.
## When to Use
- Use this when you need to share stateful logic across multiple components without duplication
- This is helpful for extracting concerns like data fetching, event listeners, or timers into reusable functions
## Instructions
- Name composable functions with the `use` prefix by convention (e.g., `useCounter`, `useWidth`)
- Use `ref()`, `reactive()`, and lifecycle hooks (`onMounted`, `onBeforeUnmount`) inside composables
- Return reactive state and methods from composables for use in component templates
- Prefer composables over the Options API for better code organization, reusability, and TypeScript support
## Details
### Options API
Before the introduction of the Composition API in Vue, developers relied on the **Options API** to organize component logic which include reactive data, lifecycle methods, computed properties, and more. The Options API allowed defining these aspects within specific options, as shown in the example below:
```html
<!-- Template -->
<script>
export default {
name: "MyComponent",
props: {
// props
},
data() {
// data
},
computed: {
// computed properties
},
watch: {
// properties to watch
},
methods: {
// methods
},
created() {
// lifecyle methods like created
},
// ...
};
</script>
<!-- Styles -->
```
While this approach served its purpose and is still applicable in Vue v3, it can become challenging to manage and maintain as components grow larger and more complex. Defining component logic within specific options can make it harder to read and understand the code, especially when dealing with extensive components. Extracting and reusing common logic between components can also be difficult in this setup.
Let's take a look at a simple example of an `App` component that renders two individual child components — `Count` and `Width`.
```html
<template>
<div class="App">
<Count :count="count" :increment="increment" :decrement="decrement" />
<div id="divider" />
<Width :width="width" />
</div>
</template>
<script>
import Count from "./components/Count.vue";
import Width from "./components/Width.vue";
export default {
name: "App",
data() {
return {
count: 0,
width: 0,
};
},
mounted() {
this.handleResize();
window.addEventListener("resize", this.handleResize);
},
beforeUnmount() {
window.removeEventListener("resize", this.handleResize);
},
methods: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
handleResize() {
this.width = window.innerWidth;
},
},
components: {
Count,
Width,
},
};
</script>
```
The code snippet above represents a Vue single-file component (SFC) named `App`.
The `<template>` section defines the markup of the component. In this case, it contains a `<div>` element with the class "App" that wraps two child components: `<Count>` and `<Width>`. These child components are passed certain properties using Vue's attribute binding syntax (`:count`, `:increment`, `:decrement`, and `:width`).
The `<script>` section contains the JavaScript code for the component. Within the component definition, we have:
- The `data` method which returns an object containing the initial data properties of the component, which are `count` and `width` initialized to 0.
- The `mounted()` lifecycle hook is used to execute code after the component has been mounted in the DOM. In this case, it calls the `handleResize()` method and adds an event listener for the resize event.
- The `beforeUnmount()` lifecycle hook is used to execute code before the component is unmounted and destroyed. Here, it removes the event listener for the resize event.
- The `methods` object contains the component's methods. It defines `increment()`, `decrement()`, and `handleResize()` methods that manipulate the count and width data properties based on certain events or actions.
When the app is run, the current count and the window's inner width are displayed in real-time. The user can interact with the component by incrementing and decrementing the count using the buttons in the `<Count>` component. Similarly, the width is automatically updated whenever the window is resized.
Even though this component is small in size, the logic inside it is already intertwined. Some parts are dedicated to the functionality of the counter, while others pertain to the width logic. As the component grows, organizing and locating related logic within the component would become more challenging.
To address these challenges, the Vue team introduced the Composition API in Vue v3.
### Composition API
The Composition API can be seen as an **API that provides standalone functions representing Vue's core capabilities**. These functions are primarily used within a single `setup()` option which serves as the entry point for utilizing the Composition API.
```html
<!-- Template -->
<script>
export default {
name: "MyComponent",
setup() {
// the setup function
},
};
</script>
<!-- Styles -->
```
The `setup()` function is executed before a component is created and when the props of the component are available.
With the Composition API, we can import standalone functions to help us access Vue's core capabilities within our component. Let's rewrite the counter and width example we've seen above while relying on the Composition API syntax.
```html
<template>
<div class="App">
<Count :count="count" :increment="increment" :decrement="decrement" />
<div id="divider" />
<Width :width="width" />
</div>
</template>
<script>
import { ref, onMounted, onBeforeUnmount } from "vue";
import Count from "./components/Count.vue";
import Width from "./components/Width.vue";
export default {
name: "App",
setup() {
const count = ref(0);
const width = ref(0);
const increment = () => {
count.value++;
};
const decrement = () => {
count.value--;
};
const handleResize = () => {
width.value = window.innerWidth;
};
onMounted(() => {
handleResize();
window.addEventListener("resize", handleResize);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", handleResize);
});
return {
count,
width,
increment,
decrement,
};
},
components: {
Count,
Width,
},
};
</script>
```
The `<template>` of our component remains the same but in the `<script>` section of our component, we now utilize the Composition API with the `setup()` function.
Inside the `setup()` function, we:
- Define the `count` and `width` reactive variables using the `ref()` function — the function that accepts a single primitive value (e.g. string, number, etc.) and returns a reactive/mutable object.
- We also define the custom functions `increment()`, `decrement()`, and `handleResize()`. These functions are similar to the methods we defined in our previous Options API example.
- We use the `onMounted()` lifecycle function to call the custom `handleResize()` function and add an event listener for the resize event when the component is mounted. Similarly, we use the `onBeforeUnmount()` lifecycle function to remove the event listener for the resize event before the component is unmounted.
- The reactive variables and functions defined in the `setup()` function are then returned, making them accessible in the component Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.