How to Create Getters that Accept Params in Pinia

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>
pinia customers store

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.

Pinia getters that accepts params

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.

pinia getter return function computed

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.

Amenallah Hsoumi
Amenallah Hsoumi

Senior Software Engineer, Indie Hacker, building cool stuff on the web!

Articles: 19