# Creating custom composable

You can create your own composable if our implementation and interfaces don't cover your use case. However, there are few things you need to consider and handle, like context and reactivity.

Before implementing, we recommend getting familiar with our composables factories (opens new window). This will help you understand how they work internally.

# Naming convention

While not mandatory, we recommend that you follow the industry convention of naming composables using camelCase and prefixing them with the use keyword.

# Structure

We recommend following our convention of returning raw data object (result in the example below) as well as loading and error properties:

  • Raw data object should contain a response from the API later passed to getters to extract data.

  • loading property should indicate if any of the composable methods is waiting for the data from the API.

  • error property should be an object with keys matching the names of the composable methods. Its values indicate if any of the composable methods threw an error.

Why `loading` and `error` are `computed` properties?

You might have noticed that loading and error are wrapped in the computed function in the composable factories. This prevents external modifications that could result in a state mismatch and hard-to-track errors.

# Shared composable

In most cases, your composable is bound to a specific entity and isn't reusable across the components or pages. For example, you might have a product with a given ID. If you load it using useProduct, this instance won't be helpful if you open a page with a different product.

However, some composables are reusable and shared. Two such examples are useUser and useCart, which will load the same data, regardless of the component or page they are called from.

Composables specific to a single entity should accept an id parameter and use it in names passed to sharedRef.

# Example

The example below shows how to implement composable bound to a single entity. If your composable is shared, you can remove the id parameter and its uses.

Only call `sharedRef` inside composable

Be sure to create variables using sharedRef inside a wrapper function. Otherwise, you might have issues with reactivity or even leak state between the requests.

import { computed } from '@vue/composition-api';
import { sharedRef, useVSFContext, Logger } from '@vue-storefront/core';

export const useCustom = (id: string) => {
  // Loads context used to call API endpoint
  const context = useVSFContext();

  // Shared ref holding the response from the API
  const result = sharedRef(null, `useCustom-${id}`);

  // Shared ref indicating whether any method is waiting for the data from the API
  const loading = sharedRef(false, `useCustom-loading-${id}`);

  // Shared ref holding errors from the methods
  const error = sharedRef({
    search: null
  }, `useCustom-error-${id}`);

  // Method to call an API endpoint and update `result`, `loading` and `error` properties
  const search = async (params) => {
    Logger.debug(`useCustom/${id}/search`, params);

    try {
      loading.value = true;
      // Change "yourIntegration" to the name of the integration
      result.value = await context.$yourIntegration.api.searchCustom(params);
      error.value.search = null;
    } catch (err) {
      error.value.search = err;
      Logger.error(`useCustom/${id}/search`, err);
    } finally {
      loading.value = false;
    }
  };

  return {
    search,
    result: computed(() => result.value),
    loading: computed(() => loading.value),
    error: computed(() => error.value)
  };
};