When to use composables vs provide/inject in Vue 3
Vue 3 introduced composables and provide/inject, two powerful patterns for managing state and logic. Both have their place, but choosing the right one at the right time can make your code more scalable, readable, and maintainable. In this article, I'll break down when to use composables and when to use provide/inject. Composables: the power of reusable logic A composable is a function that encapsulates reusable logic using Vue's Composition API. It helps keep components clean by extracting logic into separate functions that can be used across multiple components. When to use composables ✅ You need to reuse logic in multiple components. ✅ You're handling side effects (API calls, event listeners, etc.). ✅ You want to keep your components small and focused. Example: A useFetch composable Let’s create a composable to fetch data from an API: // composables/fetch.ts export function useFetch(url: string) { const data = ref(null); const error = ref(null); const fetch = async () => { try { const res = await fetch(url); data.value = await res.json(); } catch (err) { error.value = err; } }; return { data, error, fetchData }; } //components/MyComponent.vue ... import { useFetch } from '@/composables/useFetch'; const { data, error, fetch } = useFetch('https://api.example.com/posts'); Another good examples can be found in: VueUse GitHub repo: Link Vuetify GitHub repo: Link TanStack VueQuery GitHub repo: Link Provide/inject: simplifying state sharing provide/inject is Vue's dependency injection system. It allows you to pass state from a parent component to deeply nested components, avoiding prop drilling (passing props down multiple levels). When to use provide/inject ✅ You need to share state across deeply nested components. ✅ You want to avoid prop drilling. ✅ You need to provide global configurations (e.g., themes, authentication state). Example: Pass theme to deeply nested components // Parent.vue (Providing the theme) provide('theme', ref('dark')); // Child.vue (Injecting the theme) const theme = inject('theme', 'light'); // Default to 'light' if no provider Another good examples can be found in (search for provide key): Vuetify GitHub repo: Link Vue i18n GitHub repo: Link Pinia GitHub repo: Link Composables vs. provide/inject: how to choose? Use composables when You need to reuse logic across multiple components (e.g., fetching data, event listeners, form validation). You are handling side effects like API calls, local storage interactions, or WebSocket connections. You want to keep components lean by extracting complex logic into separate files. You need to keep the state local to a specific component tree, not globally shared. You want better testability – composables make it easier to isolate logic for unit testing. Example: Fetching data (useFetch) Detecting screen size (useWindowSize) Debouncing user input (useDebounce) Use provide/inject when You need to share state between deeply nested components without prop drilling. You have a single source of truth (e.g., a global theme or authentication). You need to pass dependencies down the component tree, such as configurations or event buses. You don't need the state to be reactive globally, just within a specific component tree. You want to improve performance by avoiding excessive prop passing and reactivity. Example: Providing a theme or locale (provide('theme', ref('dark'))) Sharing form state between parent and child components Dependency injection for plugins or services Summary Use composables when you need reusable logic across multiple components, such as API calls, event listeners, or computed values. They help keep components clean, modular, and testable. Use provide/inject when you need to share state between deeply nested components, avoiding prop drilling. It's great for passing global configs or themes within a specific component tree. How to choose? Reusable logic → Composables Deeply shared state → Provide/Inject

Vue 3 introduced composables and provide/inject, two powerful patterns for managing state and logic. Both have their place, but choosing the right one at the right time can make your code more scalable, readable, and maintainable.
In this article, I'll break down when to use composables and when to use provide/inject.
Composables: the power of reusable logic
A composable is a function that encapsulates reusable logic using Vue's Composition API. It helps keep components clean by extracting logic into separate functions that can be used across multiple components.
When to use composables
✅ You need to reuse logic in multiple components.
✅ You're handling side effects (API calls, event listeners, etc.).
✅ You want to keep your components small and focused.
Example: A useFetch
composable
Let’s create a composable to fetch data from an API:
// composables/fetch.ts
export function useFetch(url: string) {
const data = ref(null);
const error = ref(null);
const fetch = async () => {
try {
const res = await fetch(url);
data.value = await res.json();
} catch (err) {
error.value = err;
}
};
return { data, error, fetchData };
}
//components/MyComponent.vue
<template>
...
</template>
<script setup lang="ts">
import { useFetch } from '@/composables/useFetch';
const { data, error, fetch } = useFetch('https://api.example.com/posts');
</script>
Another good examples can be found in:
Provide/inject: simplifying state sharing
provide/inject
is Vue's dependency injection system. It allows you to pass state from a parent component to deeply nested components, avoiding prop drilling (passing props down multiple levels).
When to use provide/inject
✅ You need to share state across deeply nested components.
✅ You want to avoid prop drilling.
✅ You need to provide global configurations (e.g., themes, authentication state).
Example: Pass theme to deeply nested components
// Parent.vue (Providing the theme)
provide('theme', ref('dark'));
// Child.vue (Injecting the theme)
const theme = inject('theme', 'light'); // Default to 'light' if no provider
Another good examples can be found in (search for provide key):
Composables vs. provide/inject: how to choose?
Use composables when
- You need to reuse logic across multiple components (e.g., fetching data, event listeners, form validation).
- You are handling side effects like API calls, local storage interactions, or WebSocket connections.
- You want to keep components lean by extracting complex logic into separate files.
- You need to keep the state local to a specific component tree, not globally shared.
- You want better testability – composables make it easier to isolate logic for unit testing.
Example:
- Fetching data (
useFetch
) - Detecting screen size (
useWindowSize
) - Debouncing user input (
useDebounce
)
Use provide/inject when
- You need to share state between deeply nested components without prop drilling.
- You have a single source of truth (e.g., a global theme or authentication).
- You need to pass dependencies down the component tree, such as configurations or event buses.
- You don't need the state to be reactive globally, just within a specific component tree.
- You want to improve performance by avoiding excessive prop passing and reactivity.
Example:
- Providing a theme or locale (
provide('theme', ref('dark'))
) - Sharing form state between parent and child components
- Dependency injection for plugins or services
Summary
- Use composables when you need reusable logic across multiple components, such as API calls, event listeners, or computed values. They help keep components clean, modular, and testable.
- Use provide/inject when you need to share state between deeply nested components, avoiding prop drilling. It's great for passing global configs or themes within a specific component tree.
How to choose?
- Reusable logic → Composables
- Deeply shared state → Provide/Inject