Getters in Pinia allow us to compute and/or extract deep properties and make them available as first-level properties. However, sometimes, we need to create a getter that acts as a Higher-Order-Function and returns a function that accepts params.
So how do we make a getter return a function, and would that getter be reactive or not?
Returning a function from a getter will make it reactive if you use it properly in your Vue component.
Let’s explore this in detail by checking some use cases where it can benefit us.
Example Store
Let’s imagine we have a store that contains hardcoded customer data:
import { defineStore } from 'pinia'
const customers = [
{
name: 'John Doe',
payments: [
{
amount: 523,
},
],
active: true,
},
{
name: 'Jame Doe',
payments: [
{
amount: 420,
},
],
active: true,
},
{
name: 'Jason Doe',
payments: [
{
amount: 620,
},
],
active: true,
},
{
name: 'Jasona Doe',
payments: [
{
amount: 600,
},
],
active: false,
},
]
export const useCustomersStore = defineStore('customers', {
state: () => ({ customers }),
})
Each customer can be active/inactive and has a payments
array, which contains their payment history.
We want to display a list of active customers whose payments are above a certain threshold.
First, let’s build a Vue component that displays customers names:
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useCustomersStore } from './store/customers'
const customersStore = useCustomersStore()
</script>
<template>
<div>
<ui>
<li v-for="customer in customerStore.customers">
{{ customer.name }}
</li>
</ui>
</div>
</template>
Next, create a getter that returns the data we need.
Getter that returns a function
First, the simplest criterion we need is to get all active customers:
export const useCustomersStore = defineStore('customers', {
state: () => ({ customers }),
getters: {
activeCustomers(state) {
return state.customers.filter((customer) => customer.active)
},
},
})
Next, from the active customers, return only the customers whose payments are above or equal to minAmount
.
Then, return a function from the getter that accepts that argument:
activeCustomersWithPaymentThreshold(state) {
const activeCustomers = state.customers.filter((customer) => customer.active)
return (minAmount: number) => {
return activeCustomers.filter((customer) => customer.payments.every((payment) => payment.amount >= minAmount))
}
},
Finally, use this getter in the Vue component to display this data:
<script setup lang="ts">
import { useCustomersStore } from './store/customers'
const customersStore = useCustomersStore()
const topCustomers = customersStore.activeCustomersWithPaymentThreshold(500)
</script>
<template>
<div>
<ui>
<li v-for="customer in topCustomers">
{{ customer.name }}
</li>
</ui>
</div>
</template>
We should only see customers that satisfy that criteria.
Reactivity
let’s explore how reactivity would work, given that we only have a const
that holds our data; it is not reactive. Let’s say we want to control the threshold amount via an input field:
<script setup lang="ts">
import { ref } from 'vue'
import { useCustomersStore } from './store/customers'
const customersStore = useCustomersStore()
const minAmount = ref(500)
const topCustomers = customersStore.activeCustomersWithPaymentThreshold(500)
</script>
<template>
<div>
<input v-model="minAmount" />
<hr />
<ui>
<li v-for="customer in topCustomers">
{{ customer.name }}
</li>
</ui>
</div>
</template>
How do we make it reactive? We can either wrap the getter in a computed:
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useCustomersStore } from './store/customers'
const customersStore = useCustomersStore()
const minAmount = ref(500)
const topCustomers = computed(() => {
return customersStore.activeCustomersWithPaymentThreshold(Number(minAmount.value))
})
</script>
<template>
<div>
<input v-model="minAmount" />
<hr />
<ui>
<li v-for="customer in topCustomers">
{{ customer.name }}
</li>
</ui>
</div>
</template>
Or use the returned function directly in the template:
<ui>
<li v-for="customer in customersStore.activeCustomersWithPaymentThreshold(Number(minAmount))">
{{ customer.name }}
</li>
</ui>
Either way, when we change the input field value, we get a new list.
Conclusion
In conclusion, returning a function from a getter can be beneficial if you don’t want to handle the logic of data wrangling in your component and maybe not repeat yourself if this functionality is used in multiple places in your app.