filtervuejs3getterpinia

Pinia getter undefined if used with filter


I have a Pinia store:

import { defineStore } from "pinia";
import { api } from "@/services/api";

export const useMaterialStore = defineStore("material", {
  state: () => ({
    material: {},
  }),
  actions: {
    async load_material(machine_id) {
      const response = await api.get("material/machine/" + machine_id);
      console.log(response.data);
      this.material[machine_id] = response.data;
    },
  },
  getters: {
    getReadyMaterial: (state) => {
      return (machine_id) => state.material[machine_id];
    },
  },
});

The load_material action gets an array of objects from the API and stores it to the state.

The state looks like this:

{
    "2": [
        {
            "id": 1,
            "machine": 2,
            "order_date": null,
            "confirm_date": null,
            "delivery_date": null,
            "ready": false
        }
    ],
    "4": [
        {
            "id": 2,
            "machine": 4,
            "order_date": null,
            "confirm_date": null,
            "delivery_date": "2023-10-30",
            "ready": true
        },
        {
            "id": 3,
            "machine": 4,
            "order_date": null,
            "confirm_date": null,
            "delivery_date": null,
            "ready": false
        }
    ]
}

This is my component:

<template>
  <div>Ready: {{ materialStore.getReadyMaterial(props.machine_id) }}</div>
</template>

<script setup>
import { onMounted } from "vue";
import { useMaterialStore } from "@/store/material";

const materialStore = useMaterialStore();

const props = defineProps({
  machine_id: Number,
});

onMounted(() => {
  materialStore.load_material(props.machine_id);
});
</script>

All this works as expected, if props.machine_id is 2, the div looks like this:

[ { "id": 1, "machine": 2, "order_date": null, "confirm_date": null, "delivery_date": null, "ready": false } ]

No comes the problem, I want to filter the array for objects that have the ready set to true.

If I change the getter to return (machine_id) => state.material[machine_id].filter((m) => m.ready == true); I get an error in the console:

Uncaught (in promise) TypeError: state.material[machine_id] is undefined

I'm completely lost whats going on here and hope somebody can point out what I'm doing wrong


Solution

  • Your getter runs immediately and does not wait for your state to load. In your first example, state.material[machine_id] is initially undefined. While your data is being fetched your UI is actually rendering {{ undefined }} which is syntactically legal, but renders nothing. The data arrives sometime later and is rendered as expected. The reason this all works is because you're not trying to do anything to undefined other than return it.

    In your second example, state.material[machine_id] is still initially undefined, but now you're trying to call filter() on it, which gives you your error. You can't do this until your state actually has data available to filter.

    One solution is to not call your getter until materialStore.material data exists. This can be accomplished with a v-if on your div, checking that the object isn't empty. Only when it's non-empty will it attempt to render which will call your getter, and at that point it will be safe to do so:

    <div v-if="Object.keys(materialStore.material).length > 0">
      Ready: {{ materialStore.getReadyMaterial(props.machine_id) }}
    </div>