Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

jan_zikmund's avatar

Composables - share data and methods best practice

Hi, I am writing a component that shows data table. I have it split in several composables: useTable, useSearch, useOrdering, usePagination.

It basically works fine for the isolated refs and methods, but what I don't see is - what's the best practice when one composable needs to use the other? Eg.

The data is filtered by search, then ordered, then paginated. So I use something like this:

const { rows } = useTable();
const { filteredRows } = useSearch( rows );
const { orderedRows } = useSorting( filteredRows );
const { paginatedRows } = usePagination( orderedRows );

1] Is this the best way to do it? I plan to use this as single file and so eg. useSearch() already has access to rows even if I don't pass it as parameter. But this seems more "right" as I know what the component works with (?)

2] Currently all 4 const above are just computed properties so they respond to updates of filters and ordering criteria. But do I really need 4 computed when all I care about is just single result in the end? Would it make more sense if instead they exported methods like { applyFiltering }, { applyOrdering } etc. and then I would pass them to the table to give me the result as so:

cost { rows } = useTable(applyFiltering, applyOrdering, applyPagination)
function useTable(applyFiltering, applyOrdering, applyPagination) {
    const rows = ref(null);
    const displayRows = computed(() => {
        return applyPagination(applyOrdering(applyFiltering(rows)));
    });
    return {
        rows: displayRows
    }
}

Thanks so much for your thoughts.

0 likes
1 reply
LaryAI's avatar
Level 58

The approach you're taking by chaining composables is a common pattern in the Vue composition API, and it's a good way to keep your logic modular and reusable. However, there are a few considerations to keep in mind to ensure that your composables are well-architected and maintainable.

  1. Passing Dependencies Explicitly: It's generally a good practice to pass dependencies explicitly to your composables. This makes the dependencies of your composable clear and makes your code more testable, as you can easily mock dependencies when writing tests.

  2. Avoiding Unnecessary Computeds: If you only care about the final result, you don't necessarily need to create a computed property for each step of the transformation. Instead, you can create a single computed property that applies all transformations in sequence. This can be more efficient, as Vue can better optimize a single reactive dependency chain.

Here's an example of how you might refactor your code to use a single computed property:

import { computed, ref } from 'vue';

function useTable() {
    const rows = ref([]);

    // Your initial data fetching logic would go here
    // rows.value = fetchData();

    return { rows };
}

function useSearch(rows, searchTerm) {
    return computed(() => rows.value.filter(row => row.includes(searchTerm)));
}

function useSorting(rows, sortCriteria) {
    // Implement sorting logic based on sortCriteria
    return computed(() => /* sorted rows */);
}

function usePagination(rows, pageSize, currentPage) {
    // Implement pagination logic based on pageSize and currentPage
    return computed(() => /* paginated rows */);
}

// In your component setup function
const { rows } = useTable();
const searchTerm = ref('');
const sortCriteria = ref({});
const pageSize = ref(10);
const currentPage = ref(1);

const filteredRows = useSearch(rows, searchTerm);
const orderedRows = useSorting(filteredRows, sortCriteria);
const paginatedRows = usePagination(orderedRows, pageSize, currentPage);

// Now you can use paginatedRows as the final computed property that reflects all transformations

In this example, paginatedRows is the only computed property you need to use in your template, as it represents the final, transformed data set.

Remember that the key to using composables effectively is to keep them focused and ensure that they do one thing well. By passing dependencies explicitly and avoiding unnecessary computed properties, you can create a set of composables that are both efficient and easy to understand.

Lastly, if you find that you are repeating this pattern often, you might consider creating a higher-order composable that abstracts the chaining logic, making it easier to apply the same set of transformations to different sets of data.

Please or to participate in this conversation.