Skip to main content
Practice Problems

Composables in Vue.js

What are Composables?

Composables are functions that use Composition API to encapsulate and reuse stateful logic between components.

Composables in Vue are similar to custom hooks in React.


Basic Example

javascript
// useCounter.js import { ref } from 'vue' export function useCounter(initialValue = 0) { const count = ref(initialValue) function increment() { count.value++ } function decrement() { count.value-- } function reset() { count.value = initialValue } return { count, increment, decrement, reset } }

Usage:

javascript
<template> <div> <p>Count: {{ count }}</p> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="reset">Reset</button> </div> </template> <script setup> import { useCounter } from './useCounter' const { count, increment, decrement, reset } = useCounter(10) </script>

Naming Conventions

Composables are usually named with use prefix:

javascript
// Good useCounter() useMouse() useFetch() useLocalStorage() // Bad counter() mouse() fetch()

useMouse

Track mouse position:

javascript
// useMouse.js import { ref, onMounted, onUnmounted } from 'vue' export function useMouse() { const x = ref(0) const y = ref(0) function update(event) { x.value = event.pageX y.value = event.pageY } onMounted(() => { window.addEventListener('mousemove', update) }) onUnmounted(() => { window.removeEventListener('mousemove', update) }) return { x, y } }

useFetch

Fetch data from API:

javascript
// useFetch.js import { ref } from 'vue' export function useFetch(url) { const data = ref(null) const error = ref(null) const loading = ref(false) async function fetch() { loading.value = true error.value = null try { const response = await window.fetch(url) data.value = await response.json() } catch (e) { error.value = e } finally { loading.value = false } } fetch() return { data, error, loading, refetch: fetch } }

useLocalStorage

Sync with localStorage:

javascript
// useLocalStorage.js import { ref, watch } from 'vue' export function useLocalStorage(key, defaultValue) { const value = ref(defaultValue) // Read from localStorage on init const stored = localStorage.getItem(key) if (stored) { try { value.value = JSON.parse(stored) } catch (e) { console.error('Error parsing localStorage value:', e) } } // Save on change watch(value, (newValue) => { localStorage.setItem(key, JSON.stringify(newValue)) }, { deep: true }) return value }

Composing Composables

Composables can be combined:

javascript
// useUser.js import { useFetch } from './useFetch' import { useLocalStorage } from './useLocalStorage' export function useUser() { const userId = useLocalStorage('userId', null) const url = computed(() => userId.value ? `/api/users/${userId.value}` : null ) const { data: user, loading, error } = useFetch(url) function login(id) { userId.value = id } function logout() { userId.value = null } return { user, loading, error, login, logout } }

Best Practices

Return refs, not reactive

javascript
// Good export function useCounter() { const count = ref(0) return { count } } // Bad export function useCounter() { const state = reactive({ count: 0 }) return state }

Reason: refs preserve reactivity when destructured.

Cleanup in onUnmounted

javascript
export function useEventListener(target, event, callback) { onMounted(() => { target.addEventListener(event, callback) }) onUnmounted(() => { target.removeEventListener(event, callback) }) }

Use toValue for flexibility

javascript
import { toValue } from 'vue' export function useFetch(url) { // url can be ref, computed or plain value const actualUrl = toValue(url) // ... }

Composables vs Mixins

Mixins (Vue 2)

javascript
// Bad - mixins const counterMixin = { data() { return { count: 0 } }, methods: { increment() { this.count++ } } }

Mixins problems:

  • Unclear property source
  • Name conflicts
  • Implicit dependencies

Composables (Vue 3)

javascript
// Good - composables export function useCounter() { const count = ref(0) function increment() { count.value++ } return { count, increment } }

Composables advantages:

  • Explicit source
  • No name conflicts
  • Better typing
  • Easier to test

Common Mistakes

Calling composable outside setup

javascript
// Wrong export default { data() { const { x, y } = useMouse() // Error! return { x, y } } } // Correct export default { setup() { const { x, y } = useMouse() return { x, y } } }

Conditional composable call

javascript
// Wrong if (condition) { const { count } = useCounter() // Error! } // Correct const { count } = useCounter() if (condition) { // use count }

Forgetting cleanup

javascript
// Wrong - memory leak export function useMouse() { const x = ref(0) function update(event) { x.value = event.pageX } window.addEventListener('mousemove', update) // Forgot removeEventListener! return { x } } // Correct export function useMouse() { const x = ref(0) function update(event) { x.value = event.pageX } onMounted(() => { window.addEventListener('mousemove', update) }) onUnmounted(() => { window.removeEventListener('mousemove', update) }) return { x } }

Conclusion

Composables:

  • Reusable logic with Composition API
  • Better than mixins
  • Named with use prefix
  • Return refs
  • Require cleanup in onUnmounted
  • Can be combined
  • Support TypeScript

In interviews:

Important to be able to:

  • Explain what composables are and why they're needed
  • Show examples of creating composables
  • Describe advantages over mixins
  • Explain best practices (naming, cleanup, returning refs)
  • Give examples of popular composables

Content

What are Composables?Basic ExampleNaming ConventionsPopular Composables ExamplesuseMouseuseFetchuseLocalStorageComposing ComposablesBest PracticesReturn refs, not reactiveCleanup in onUnmountedUse toValue for flexibilityComposables vs MixinsMixins (Vue 2)Composables (Vue 3)Common MistakesCalling composable outside setupConditional composable callForgetting cleanupConclusion

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems