When programming, it’s important to structure your codebase so that you reuse code where possible. Duplicating code can bloat the codebase and complicate debugging, especially in larger apps.

Vue simplifies code reuse through composables. Composables are functions that encapsulate logic, and you can reuse them across your project to handle similar functionality.

Was It Always Composables?

Before Vue 3 introduced composables, you could use mixins to capture code and reuse it in different parts of your application. Mixins contained Vue.js options such as data, methods, and lifecycle hooks, enabling code reuse across multiple components.

To create mixins, you structure them in separate files and then apply them to components by adding the mixin to the mixins property within the component's options object. For example:

 // formValidation.js
export const formValidationMixin = {
  data() {
    return {
      formData: {
        username: '',
        password: '',
      },
      formErrors: {
        username: '',
        password: '',
      },
    };
  },
  methods: {
    validateForm() {
      this.formErrors = {};
  
      if (!this.formData.username.trim()) {
        this.formErrors.username = 'Username is required.';
      }
  
      if (!this.formData.password.trim()) {
        this.formErrors.password = 'Password is required.';
      }
   
      return Object.keys(this.formErrors).length === 0;
    },
  },
};

This code snippet shows the contents of a mixin for validating forms. This mixin houses two data properties—formData and formErrors—initially set to empty values.

formData stores input data for the form, including username and password fields initialized as empty. formErrors mirrors this structure to hold potential error messages, also initially empty.

The mixin also contains a method, validateForm(), to check that the username and password fields are not empty. If either field is empty, it populates the formErrors data property with an appropriate error message.

The method returns true for a valid form, when formErrors is empty. You can use the mixin by importing it into your Vue component and adding it to the mixin property of the Options object:

 <template>
  <div>
    <form @submit.prevent="submitForm">
      <div>
        <label for="username">Username:</label>
        <input type="text" id="username" v-model="formData.username" />
        <span class="error">{{ formErrors.username }}</span>
      </div>

      <div>
        <label for="password">Password:</label>
        <input type="password" id="password" v-model="formData.password" />
        <span class="error">{{ formErrors.password }}</span>
      </div>

      <button type="submit">Submit</button>
    </form>
  </div>
</template>

<script>
import { formValidation } from "./formValidation.js";

export default {
  mixins: [formValidation],
  methods: {
    submitForm() {
      if (this.validateForm()) {
        alert("Form submitted successfully!");
      } else {
        alert("Please correct the errors in the form.");
      }
    },
  },
};
</script>

<style>
.error {
  color: red;
}
</style>

This example shows a Vue component written using the Options object approach. The mixins property includes all the mixins you've imported. In this case, the component uses the validateForm method from the formValidation mixin to inform the user whether form submission was successful.

How to Use Composables

A composable is a self-contained JavaScript file with functions tailored to specific concerns or requirements. You can leverage Vue's composition API within a composable, using features like refs and computed refs.

This access to the composition API allows you to create functions that integrate into various components. These functions return an object, which you can readily import and incorporate into Vue components through the setup function of the Composition API.

Create a new JavaScript file in your project's src directory to use a composable. For larger projects, consider organizing a folder within src and creating separate JavaScript files for different composables, ensuring each composable's name reflects its purpose.

Inside the JavaScript file, define the function you require. Here's a restructuring of the formValidation mixin as a composable:

 // formValidation.js
import { reactive } from 'vue';

export function useFormValidation() {
  const state = reactive({
    formData: {
      username: '',
      password: '',
    },
    formErrors: {
      username: '',
      password: '',
    },
  });

  function validateForm() {
    state.formErrors = {};

    if (!state.formData.username.trim()) {
      state.formErrors.username = 'Username is required.';
    }

    if (!state.formData.password.trim()) {
      state.formErrors.password = 'Password is required.';
    }

    return Object.keys(state.formErrors).length === 0;
  }

  return {
    state,
    validateForm,
  };
}

This snippet begins by importing the reactive function from the vue package. It then creates an exportable function, useFormValidation().

It continues by creating a reactive variable, state, which houses the formData and formErrors properties. The snippet then handles the form validation with a very similar approach to the mixin. Finally, it returns the state variable and the validateForm function as an object.

You can use this composable by importing the JavaScript function from the file in your component:

 <template>
  <div>
    <form @submit.prevent="submitForm">
      <div>
        <label for="username">Username:</label>
        <input type="text" id="username" v-model="state.formData.username" />
        <span class="error">{{ state.formErrors.username }}</span>
      </div>

      <div>
        <label for="password">Password:</label>
        <input type="password" id="password" v-model="state.formData.password" />
        <span class="error">{{ state.formErrors.password }}</span>
      </div>

      <button type="submit">Submit</button>
    </form>
  </div>
</template>

<script setup>
import { useFormValidation } from "./formValidation.js";
import { ref } from "vue";
const { state, validateForm } = useFormValidation();

const submitForm = () => {
  if (validateForm()) {
    alert("Form submitted successfully!");
  } else {
    alert("Please correct the errors in the form.");
  }
};
</script>

<style>
.error {
  color: red;
}
</style>

After importing the useFormValidation composable, this code destructures the JavaScript object it returns and carries on with the form validation. It alerts whether the submitted form is a success or has errors.

Composables Are the New Mixins

While mixins were useful in Vue 2 for code reuse, composables have replaced them in Vue 3. Composables provide a more structured and maintainable approach to reusing logic in Vue.js applications, making it easier to build scalable web apps with Vue.