Custom Composables in Vue

Introduction

Welcome to the first lesson of our course on Modern Vue Architecture and Composables. In this course, you will learn how to build scalable and maintainable Vue applications using modern patterns. One of the most important patterns in Vue 3 is the use of custom composables. These are special functions that help you organize and reuse your state and logic across different components.

In traditional Vue development, you might find your components getting larger and harder to manage as your app grows. Custom composables offer a solution by letting you extract logic and state into separate, reusable functions. This lesson will introduce you to the concept of custom composables, show you how they work, and guide you through a real-world example. By the end, you will see how composables can make your code cleaner and your components easier to understand.

What Are Custom Composables?

A custom composable in Vue is simply a function — usually starting with the word use — that lets you share reactive state and logic between components. Unlike the old way of mixing all your logic inside a single component, composables help you separate concerns. This means you can write your logic once and use it in many places, making your code more organized and easier to test.

For example, if you have logic for managing a list of tasks, you can put all that logic into a composable. Then, any component that needs to work with tasks can use that composable. This approach leads to smaller, cleaner components that focus only on displaying data and handling user interactions.

The main benefits of custom composables are reusability and clarity. You avoid repeating yourself, and your components become much easier to read and maintain. This is especially helpful as your application grows and you want to keep things simple.

Deep Dive into the useTodos Composable

Let’s look at a real example: the useTodos composable. This function manages a list of to-do items, including adding, toggling, and deleting tasks. Here is the code for src/composables/useTodos.js:

import { ref, computed } from 'vue';

// A composable is a function that can be used to share reactive state and logic.
export function useTodos() {
  const todos = ref([
    { id: 1, text: 'Learn Composables', completed: true },
    { id: 2, text: 'Set up Vue Router', completed: false },
  ]);
  const nextTodoId = ref(3);
  const filter = ref('all');

  const filteredTodos = computed(() => {
    switch (filter.value) {
      case 'active': return todos.value.filter(t => !t.completed);
      case 'completed': return todos.value.filter(t => t.completed);
      default: return todos.value;
    }
  });

  const activeCount = computed(() => todos.value.filter(t => !t.completed).length);

  function addTodo(todoText) {
    todos.value.push({ id: nextTodoId.value++, text: todoText, completed: false });
  }

  function handleToggleComplete(todoId) {
    const todo = todos.value.find(t => t.id === todoId);
    if (todo) todo.completed = !todo.completed;
  }

  function handleDeleteTodo(todoId) {
    todos.value = todos.value.filter(t => t.id !== todoId);
  }

  // Expose the state and methods to be used by components.
  return { todos, filter, filteredTodos, activeCount, addTodo, handleToggleComplete, handleDeleteTodo };
}

Let’s break down what is happening here. The todos variable is a reactive array that holds the list of tasks. The filter variable lets you choose which tasks to show: all, active, or completed. The filteredTodos computed property returns the list of tasks based on the current filter. The activeCount computed property tells you how many tasks are not yet completed.

There are also three methods: addTodo adds a new task, handleToggleComplete switches a task between complete and incomplete, and handleDeleteTodo removes a task. All of these are returned from the function, so any component that uses this composable can access them.

If you were to use this composable and call addTodo('Write documentation'), the todos array would now include a new item:

[
  { id: 1, text: 'Learn Composables', completed: true },
  { id: 2, text: 'Set up Vue Router', completed: false },
  { id: 3, text: 'Write documentation', completed: false }
]

This makes it easy to manage your tasks in a single place, and you can reuse this logic in any component that needs it.

Integrating the Composable in a Vue Component (App.vue)

Now, let’s see how you can use the useTodos composable inside a Vue component. Here is the code for src/App.vue:

<script setup>
import TodoItem from './components/TodoItem.vue';
import TodoForm from './components/TodoForm.vue';
import { useTodos } from './composables/useTodos.js';

// All the logic is now encapsulated in the composable.
// The component just consumes the state and methods.
const { 
  todos, 
  filter, 
  filteredTodos, 
  activeCount, 
  addTodo, 
  handleToggleComplete, 
  handleDeleteTodo 
} = useTodos();
</script>

<template>
  <div class="todo-app">
    <h1>Task Manager</h1>
    <TodoForm @add-todo="addTodo" />
    <div class="filter-controls">
      <button @click="filter = 'all'">All</button>
      <button @click="filter = 'active'">Active</button>
      <button @click="filter = 'completed'">Completed</button>
      <span>{{ activeCount }} items left</span>
    </div>
    <ul>
      <TodoItem
        v-for="todo in filteredTodos"
        :key="todo.id"
        :todo="todo"
        @toggle-complete="handleToggleComplete"
        @delete-todo="handleDeleteTodo"
      />
    </ul>
  </div>
</template>

In this example, the component imports the useTodos composable and calls it. This gives the component access to all the state and methods it needs: the list of todos, the filter, the filtered list, the count of active tasks, and the functions to add, toggle, and delete tasks.

Notice how the component itself does not contain any logic for managing the tasks. Instead, it just uses the state and methods provided by the composable. This keeps the component simple and focused on rendering the UI and handling user events.

For example, when you click the "Add" button in the form, the addTodo method from the composable is called. When you click to complete or delete a task, the corresponding methods from the composable are used. This pattern makes your code much easier to read and maintain.

Summary and Prep for Hands-on Practice

To recap, you have learned what custom composables are and how they help you organize and reuse logic in your Vue applications. By moving state and logic into composables, your components become smaller, cleaner, and easier to manage. You have seen a real example with the useTodos composable and how it is used in a Vue component to manage a list of tasks.

In the next exercises, you will get hands-on practice with custom composables. You will write your own composables, use them in components, and see how they can simplify your code. Remember, on CodeSignal, all the necessary libraries are already installed, so you can focus on learning and building.

Congratulations on reaching this milestone! You are now ready to start using custom composables in your own projects. Let’s move on to the practice exercises and put these ideas into action.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal