---
title: Follow Composable Naming Convention and Return Pattern
impact: MEDIUM
impactDescription: Inconsistent composable patterns lead to confusing APIs and reactivity issues when destructuring
type: best-practice
tags: [vue3, composables, composition-api, naming, conventions, refs]
---

# Follow Composable Naming Convention and Return Pattern

**Impact: MEDIUM** - Vue composables should follow established conventions: prefix names with "use" and return plain objects containing refs (not reactive objects). Returning reactive objects causes reactivity loss when destructuring, while inconsistent naming makes code harder to understand.

## Task Checklist

- [ ] Name composables with "use" prefix (e.g., `useMouse`, `useFetch`, `useAuth`)
- [ ] Return a plain object containing refs, not a reactive object
- [ ] Allow both destructuring and object-style access
- [ ] Document the returned refs for consumers

**Incorrect:**

```javascript
// WRONG: No "use" prefix - unclear it's a composable
export function mousePosition() {
  const x = ref(0);
  const y = ref(0);
  return { x, y };
}

// WRONG: Returning reactive object - destructuring loses reactivity
export function useMouse() {
  const state = reactive({
    x: 0,
    y: 0,
  });
  // When consumer destructures: const { x, y } = useMouse()
  // x and y become plain values, not reactive!
  return state;
}

// WRONG: Returning single ref directly - inconsistent API
export function useCounter() {
  const count = ref(0);
  return count; // Consumer must use .value everywhere
}
```

**Correct:**

```javascript
// CORRECT: "use" prefix and returns plain object with refs
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 plain object containing refs
  return { x, y };
}

// Consumer can destructure and keep reactivity
const { x, y } = useMouse();
watch(x, (newX) => console.log('x changed:', newX)); // Works!

// Or use as object if preferred
const mouse = useMouse();
console.log(mouse.x.value);
```

## Using reactive() Wrapper for Auto-Unwrapping

If consumers prefer auto-unwrapping (no `.value`), they can wrap the result:

```javascript
import { reactive } from 'vue';
import { useMouse } from './composables/useMouse';

// Wrapping in reactive() links the refs
const mouse = reactive(useMouse());

// Now access without .value
console.log(mouse.x); // Auto-unwrapped, still reactive

// But DON'T destructure from this!
const { x } = reactive(useMouse()); // WRONG: loses reactivity again
```

## Pattern: Returning Both State and Actions

```javascript
// Composable with state AND methods
export function useCounter(initialValue = 0) {
  const count = ref(initialValue);
  const doubleCount = computed(() => count.value * 2);

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

  function decrement() {
    count.value--;
  }

  function reset() {
    count.value = initialValue;
  }

  // Return all refs and functions in plain object
  return {
    count,
    doubleCount,
    increment,
    decrement,
    reset,
  };
}

// Usage
const { count, doubleCount, increment, reset } = useCounter(10);
```

## Naming Convention Examples

| Good Name           | Bad Name        | Reason                           |
| ------------------- | --------------- | -------------------------------- |
| `useFetch`          | `fetch`         | Conflicts with native fetch      |
| `useAuth`           | `authStore`     | "Store" implies Pinia/Vuex       |
| `useLocalStorage`   | `localStorage`  | Conflicts with native API        |
| `useFormValidation` | `validateForm`  | Sounds like a one-shot function  |
| `useWindowSize`     | `getWindowSize` | "get" implies synchronous getter |

## Reference

- [Vue.js Composables - Conventions and Best Practices](https://vuejs.org/guide/reusability/composables.html#conventions-and-best-practices)
- [Vue.js Composables - Return Values](https://vuejs.org/guide/reusability/composables.html#return-values)
