0

I'm starting to get comfortable with Vue's composition API, but every time I make a composable I ask myself which pattern to use. If I have a composable whose state depends on outside triggers:

  • should I return a function from the composable that performs an action on the state? Or
  • should I instead let the composable accept a reactive state from outside to update the state managed by the composable?

Scenario: list users

For example, I need to make a composable that fetches a list of users from an API. The list must reactively update when filtered by user name.

Composable pattern 1: return reactive list and action on that list

<script setup lang="ts">
const { list, setList } = useUserList();
</script>

<template>
  <pre>{{ list }}</pre>
  <button type="button" @click="setList('john')">List users named "John"</button>
</template>

Composable pattern 2: return reactive list based on input reactive dependency

<script setup lang="ts">
const nameFilter = ref('');
const { list } = useUserList(nameFilter);
</script>

<template>
  <pre>{{ list }}</pre>
  <button type="button" @click="nameFilter = 'John'">List users named "John"</button>
</template>

Which of the two patterns should I do? The example scenario may be simple enough to make the choice negligible, but on more complex logic (e.g., making higher-order composables) I imagine its effect compounding.

2
  • My perspective is that it depends on the scenario—just as there's no universal code, there's no universal design pattern. Focusing specifically on the two patterns you mentioned: Composable Pattern 1 is more cohesive, while Composable Pattern 2 is more flexible. If the code involving nameFilter becomes extensive, using Composable Pattern 1 would lead to overly bloated code, in which case I would opt for Composable Pattern 2 or other patterns. Commented Mar 13 at 8:57
  • @yuanyxh interesting take. But I don't think code in pattern 1 would necessarily become bloated much more than pattern 2 in your scenario. I could also create a nameFilter ref in pattern 1 then call setList(nameFilter.value) every time the ref value changes. Commented Mar 13 at 9:45

1 Answer 1

0

Setter function is generally redundant in Vue because refs are reactive and can be watched. As a rule of thumb, modified pattern 1 is commonly used:

const { list, filter } = useUserList();

If a composable is implemented for team or public usage, then both modified pattern 1 and pattern 2 are applicable. It may be necessary to pass a ref that already exists to a composable.

A universal implementation that allows both uses:

function useUserList(filter = ref()) {
  const list = ref();
  ...
  return { list: readonly(list), filter }
}
Sign up to request clarification or add additional context in comments.

2 Comments

I think you have a point about redundancy. But under the premise that I need list to be updatable indirectly (i.e., discourage users from updating list itself and instead let it get updated using a name filter string in some way), what do you think should be the composable's input-output interface?
I updated the example to reflect the supposed case closer. The points are the same, you can use refs instead of functions where possible. This won't apply if they need to trigger an action rather than set value, e.g. if you want it to force update the list even if filter value stays the same, it would be updateFilter function. And accept optional external ref in case this makes the usage easier. "filter" can be an existing ref that comes from another composable that isn't as flexible as yours

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.