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

mcadio's avatar

Can't get my data to load in pinia store

I keep getting an error that my first function in actions doesn't exist. I've been following along with the Vue video series, and trying to apply it to an existing vue app I'd like to improve upon. When I look at the vue devtools, it shows this info for the dashStore from the DocDash component. I have no idea what I am doing wrong that it won't recognize my function, so I can assign data to the days array in the pinia store. Can someone help me understand?

{"$id":"dashStore","$onAction":{"_custom":{"type":"function","display":"<span style=\"opacity:.5;\">function</span> bound addSubscription()","tooltip":"<pre>function () { [native code] }</pre>","_reviveId":0}},"$patch":{"_custom":{"type":"function","display":"<span style=\"opacity:.5;\">function</span> $patch(partialStateOrMutator)","tooltip":"<pre>function $patch(partialStateOrMutator) {\n    let subscriptionMutation;\n    isListening = isSyncListening = false;\n    if (true) {\n      debuggerEvents = [];\n    }\n    if (typeof partialStateOrMutator === \"function\") {\n      partialStateOrMutator(pinia.state.value[$id]);\n      subscriptionMutation = {\n        type: MutationType.patchFunction,\n        storeId: $id,\n        events: debuggerEvents\n      };\n    } else {\n      mergeReactiveObjects(pinia.state.value[$id], partialStateOrMutator);\n      subscriptionMutation = {\n        type: MutationType.patchObject,\n        payload: partialStateOrMutator,\n        storeId: $id,\n        events: debuggerEvents\n      };\n    }\n    const myListenerId = activeListener = Symbol();\n    nextTick().then(() => {\n      if (activeListener === myListenerId) {\n        isListening = true;\n      }\n    });\n    isSyncListening = true;\n    triggerSubscriptions(subscriptions, subscriptionMutation, pinia.state.value[$id]);\n  }</pre>","_reviveId":1}},"$reset":{"_custom":{"type":"function","display":"<span style=\"opacity:.5;\">function</span> $reset2()","tooltip":"<pre>function $reset2() {\n    const { state } = options;\n    const newState = state ? state() : {};\n    this.$patch(($state) => {\n      assign($state, newState);\n    });\n  }</pre>","_reviveId":2}},"$subscribe":{"_custom":{"type":"function","display":"<span style=\"opacity:.5;\">function</span> $subscribe(callback, options2 = {})","tooltip":"<pre>$subscribe(callback, options2 = {}) {\n      const removeSubscription = addSubscription(subscriptions, callback, options2.detached, () => stopWatcher());\n      const stopWatcher = scope.run(() => watch(() => pinia.state.value[$id], (state) => {\n        if (options2.flush === \"sync\" ? isSyncListening : isListening) {\n          callback({\n            storeId: $id,\n            type: MutationType.direct,\n            events: debuggerEvents\n          }, state);\n        }\n      }, assign({}, $subscribeOptions, options2)));\n      return removeSubscription;\n    }</pre>","_reviveId":3}},"$dispose":{"_custom":{"type":"function","display":"<span style=\"opacity:.5;\">function</span> ()","tooltip":"<pre>() => {\n      $dispose();\n      api.notifyComponentUpdate();\n      api.sendInspectorTree(INSPECTOR_ID);\n      api.sendInspectorState(INSPECTOR_ID);\n      api.getSettings().logStoreChanges && toastMessage(`Disposed \"${store.$id}\" store \u{1F5D1}`);\n    }</pre>","_reviveId":4}},"count":"__vue_devtool_undefined__","_hotUpdate":{"_custom":{"type":"function","display":"<span style=\"opacity:.5;\">function</span> (newStore)","tooltip":"<pre>(newStore) => {\n      hotUpdate(newStore);\n      api.addTimelineEvent({\n        layerId: MUTATIONS_LAYER_ID,\n        event: {\n          time: now(),\n          title: \"\u{1F525} \" + store.$id,\n          subtitle: \"HMR update\",\n          data: {\n            store: formatDisplay(store.$id),\n            info: formatDisplay(`HMR update`)\n          }\n        }\n      });\n      api.notifyComponentUpdate();\n      api.sendInspectorTree(INSPECTOR_ID);\n      api.sendInspectorState(INSPECTOR_ID);\n    }</pre>","_reviveId":5}},"_isOptionsAPI":true,"appts":[{"id":29980,"patient":{"id":95,"state":"","doctor_id":1,"first":"John","last":"Doe","nickname":"DoeJ","home":"","work":"","cell":"1235551212","email":null,"address":"","city":"","zip":"","dob":null,"startdate":"2014-05-06","list":"","active":1,"referred_by":null,"referral_tag":null,"created_at":null,"updated_at":"2022-09-10T11:34:28.000000Z","cc":"Adjustment","deleted_at":null,"recall":null,"recall_note":null,"plan_id":14,"parent_id":95,"payment_day":1,"secondary_id":null},"patient_id":95,"nickname":"DoeJ","parent_id":null,"date_time":"2024-01-01T21:10:00.000000Z","payments":[],"parentpayments":[],"pmts":[],"appt_status_id":9,"appt_type_id":12,"appt_note":null,"subjective":"the patient is here to be checked for chiropractic subluxation.","appt_reminder":true,"objective_text":null,"assessment":"clear upper cervical check, no adjustment needed today","plan":"recheck next visit","clearUC":0,"doctor_id":1,"officehour_id":1},{"id":29888,"patient":{"id":234,"state":"","doctor_id":1,"first":"Jane","last":"Doe","nickname":"DoeJa","home":null,"work":"","cell":"5556661212","email":null,"address":"","city":"","zip":"","dob":null,"startdate":"2018-03-30","list":"","active":1,"referred_by":null,"referral_tag":null,"created_at":null,"updated_at":"2021-12-31T16:51:10.000000Z","cc":null,"deleted_at":null,"recall":1,"recall_note":"03-09-2021","plan_id":1,"parent_id":234,"payment_day":15,"secondary_id":null},"patient_id":234,"nickname":"DoeJa","parent_id":null,"date_time":"2024-01-01T22:00:00.000000Z","payments":[],"parentpayments":[],"pmts":[],"appt_status_id":6,"appt_type_id":2,"appt_note":null,"subjective":null,"appt_reminder":false,"objective_text":null,"assessment":null,"plan":null,"clearUC":0,"doctor_id":1,"officehour_id":1}]}

DocDash.vue

<template class="w-screen">
    <!-- MESSAGES -->
    <loading v-if="dash.isLoading" name="Appointments"></loading>
    <div class="grid grid-cols-1 md:grid-cols-4 gap-4 w-90" v-else>
        <!-- APPT DASH -->
        <div class="md:col-span-3 p-2">
            <ApptDash></ApptDash>
            <!-- Appt Modal -->

        </div>
    </div>

</template>

<script setup>
import {onMounted} from "vue"
    import {useRoute} from "vue-router";
    import { useDocDashStore } from "../../stores/DocDashStore";
    import Loading from "@/components/Loading.vue";
    import ApptDash from "@/components/ApptDash.vue";

    const route = useRoute();
    const day = route.params.day
    const dash = useDocDashStore()

    onMounted(async()=> {
        await dash.loadAppts(day)
    })
</script>

ApptDash.vue

<script setup>
import moment from "moment";
import {useDocDashStore} from "../../stores/DocDashStore";
import DashActions from "@/components/DashActions.vue";
const dash = useDocDashStore()
</script>

<template>
    <DashActions :viewFinished="dash.viewFinished"></DashActions>
    <table class="table">
        <thead>
        <tr>
            <td>Select All
                <input class="h-6 w-6" type="checkbox" @click="dash.selectAll()" :checked="dash.allSelected">
            </td>
            <td class="flex text-lg font-semibold"> {{ dash.appts.length + '&nbsp;' }}
                <span class="flex text-lg font-semibold uppercase" v-if="!dash.viewFinished"> On Deck  <svg @click="dash.viewFinished = !dash.viewFinished" class="ml-2 h-6 w-6 text-green-500"  viewBox="0 0 24 24"  fill="none"  stroke="currentColor"  stroke-width="2"  stroke-linecap="round"  stroke-linejoin="round">  <rect x="1" y="5" width="22" height="14" rx="7" ry="7" />  <circle cx="8" cy="12" r="3" /></svg></span>
                <span class="flex text-lg font-semibold uppercase" v-if="dash.viewFinished"> Checked Out<svg @click="dash.viewFinished = !dash.viewFinished" class="ml-2 h-6 w-6 text-red-500"  viewBox="0 0 24 24"  fill="none"  stroke="currentColor"  stroke-width="2"  stroke-linecap="round"  stroke-linejoin="round">  <rect x="1" y="5" width="22" height="14" rx="7" ry="7" />  <circle cx="16" cy="12" r="3" /></svg></span>
            </td>
            <td>Time </td>
            <td>Patient</td>
            <td>Type</td>
        </tr>
        </thead>
        <tbody v-if="dash.getOnDeck">
        <tr v-for="(appt, index) in dash.getOnDeck" :key="appt.id"
            class="odd:bg-gray-300 hover:bg-blue-300"
            v-bind:class="{
                        'bg-amber-300 text-red-700 font-bold tracking-wider odd:bg-amber-300': appt.mark_due && appt.appt_status_id === 2,
                        'bg-red-600 odd:bg-red-800' : appt.appt_status_id === 4 || appt.appt_status_id === 6,
                        'bg-gray-300 odd:bg-gray-500': appt.appt_status_id === 9 || appt.appt_status_id === 3,
                         'text-amber-200 font-bold tracking-wide': appt.mark_due && appt.appt_status_id !== 2}" >
            <td class="p-4">
                <input class="h-6 w-6" type="checkbox" @click="dash.select(index)" :checked="dash.selected.indexOf(index) > -1">
            </td>
            <td class="flex p-4">
                <a :href="'/appts/'+ appt.id +'/edit'" class="mr-2">
                    <svg class="h-6 w-6 stroke-red-500"  fill="none" viewBox="0 0 24 24" stroke="currentColor">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
                    </svg>
                </a>
                <a :href="'/appts/'+ appt.id" class="ml-2">
                    <svg class="h-6 w-6 text-blue-800"  viewBox="0 0 24 24"  fill="none"  stroke="currentColor"  stroke-width="2"  stroke-linecap="round"  stroke-linejoin="round">
                        <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" />  <circle cx="12" cy="12" r="3" />
                    </svg>
                </a>
            </td>
            <td class="p-4">{{ moment(appt.date_time).format('h:k A') }}</td>
            <td v-if="appt.patient" class="p-4">{{  appt.patient.nickname }}</td>
            <td class="p-4">
                <span v-if="!dash.viewFinished">{{ appt.appt_type ? appt.appt_type.appt_abbr : appt.appt_type_id }}</span>
                <span v-else>{{ appt.appt_status ? appt.appt_status.status : appt.appt_status_id }}</span>
            </td>
        </tr>
        </tbody>
        <tbody v-else>
        <tr class="bg-indigo-400">
            <td colspan="5">No appointments {{ dash.viewFinished ? 'Checked Out' : 'On Deck' }}</td>
        </tr>
        </tbody>
    </table>
    <!--            <appt-modal :appt="selectedAppt"></appt-modal>-->
</template>

DocDashStore.js

import { defineStore } from "pinia"
import {useSwal} from "../composables/swal"
import moment from "moment"
import Toaster from "@meforma/vue-toaster"
import {isString} from "lodash";

export let useDocDashStore = defineStore('DocDashStore', {
    state() {
        return {
            appts: [],
            days: [],
            viewFinished: false,
            selected: [],
            selectedAppt: {},
            day: null,
            apptModal: false,
            apptModalType: null,
            isLoading: true
        }
    },
    actions: {
        async loadDays() {
            console.log("Init...");
            this.isLoading = true
            const res = await axios.get('/api/cal');
            console.log(res.data.data)
            this.days = res.data.data // Assuming the API returns the data directly
            this.isLoading = false
        },
        async loadAppts(day) {
            this.isLoading = true
            const response = await axios.get('/api/dash/' + day);
            console.log(response.data)
            this.appts = response.data.data // Assuming the API returns the data directly
            this.isLoading = false
        },
        select(index) {
            this.selected.indexOf(index) > -1
                ? this.selected.splice(this.selected.indexOf(index), 1)
                : this.selected.push(index);
            console.log('selected', this.selected, 'appt',this.getOnDeck[index].patient_id);
        },
        isSelected() {
            if(this.selected.length < 1) {
                useSwal("","Select at least 1 appointment!",alert)
                return false;
            } else {
                return true;
            }
        },
        selectAll(){
            this.selected = this.getOnDeck.map(function(a,index){ return index });
            console.log('selected', this.selected);
        },
        loadApptModal(index = null) {
            console.log('load appt modal');
            // set the modal type, assign appt, clear errors
            this.selectedAppt = this.appts[index];
            this.apptModalType = 'update';
            // show modal
            this.apptModal = true;
        },
        bookNextAppt(date, hours) {
            // check if selected
            let hasPatients = this.isSelected()
            // if patients selected, book the appts
            if(hasPatients) {
                this.selected.forEach((id) => {
                    let req = {
                        // patients are passed as an array on the admin side
                        patient_id: [this.getOnDeck[id].patient_id],
                        date_time: this.getOnDeck[id].date_time,
                        appt_time: this.getOnDeck[id].appt_time,
                        appt_date: new moment(date).format("YYYY-MM-DD"),
                        appt_type_id: this.getOnDeck[id].appt_type_id === 2 ? 2 :  4,
                        appt_reminder: 1,
                        appt_status_id: 2,
                        officehour_id: hours.id,
                        doctor_id: hours.doctor_id,
                        sendJson: true
                    };

                    if(!date) {
                        this.$toast.error('The office is CLOSED that day!');
                    }
                    // add the appointment to the db
                    axios.post('/api/appt', req).then(response => {
                        console.log(response.data);
                        // handle message responses
                        this.handleResponse(response)
                    }).catch(error => {
                        console.log(error);
                        this.handleError(error)
                    })
                });
            }
            this.resetData();
        },
        updateApptStatus(status) {
            // select appointment(s)
            let $selected = this.isSelected();
            // update each appt
            if($selected) {
                this.selected.forEach((id) => {
                    this.appt = this.getOnDeck[id];
                    let updated = {
                        appt_status_id: status,
                        appt_type_id: status === 2 ? 2 : this.appt.appt_type_id
                    };
                    // update the server
                    this.updateAppt(id, this.appt, updated);
                });
                this.resetData();
            }
        },
        updateApptType(type) {
            console.log('update appt type', type);
            let $selected = this.isSelected();
            if(type === 11) {
                console.log('type is 11, open modal ', type);
                console.log(this.selected);
                let index = this.selected[0];
                this.appt = this.getOnDeck[index];
                console.log(index, this.appt);
                this.loadApptModal(index);
            } else {
                console.log('type is not 11, mark clear', type);
                if($selected) {
                    this.selected.forEach((id) => {
                        this.appt = this.getOnDeck[id];
                        let newData = {
                            appt_status_id: 9,
                            appt_type_id: type,
                            subjective: this.appt.subjective ? this.appt.subjective : this.addSubjective(),
                            assessment: 'clear upper cervical check, no adjustment needed today',
                            plan: 'recheck next visit'
                        };
                        this.sendBoxMessage(this.appt)
                        this.updateAppt(id, this.appt, newData)
                    });
                    this.resetData();
                }
            }
        },
        updateAppt(index, appt, updated) {
            axios.patch('/api/appts/' + appt.id, updated)
                .then(response => {
                    // find the index of the appointment
                    let pos = this.appts.findIndex(i => i.id === appt.id);
                    // remove the old one
                    this.appts.splice(pos, 1);
                    // push in the updated version
                    this.appts.push({...appt, ...updated});
                    // display success message
                    Toaster.info(response.data.message ? response.data.message : response.data.error);
                    // close the modal
                    $("#apptModal").modal("hide");
                    this.resetData();
                })
                .catch(error => {
                    Toaster.error(error);
                })
        },
        sendMessage() {
            let message = prompt('What message would you like to send?')
            let hasPatients = this.isSelected()
            // if patients selected, book the appts
            if(hasPatients) {
                let config = {
                    headers: {
                        'Accept': 'application/json',
                        'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
                    }
                }
                this.selected.forEach((id) => {
                    let req = {
                        appt: this.getOnDeck[id].id,
                        message: message
                    }

                    axios.post('/api/appt/message', req, config)
                        .then(response => {
                            console.log(response.data);
                            this.handleResponse(response)
                        })
                        .catch(error => {
                            console.log(error);
                            this.handleError(error)
                        });
                })
            }
            this.resetData()
        },
        sendBoxMessage(appt) {
            let config = {
                headers: {
                    'Accept': 'application/json',
                    'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content')
                }
            }
            let req = {
                appt: appt.id
            }
            axios.post('/api/appt/boxMessage', req, config)
                .then(response => {
                    console.log(response.data);
                    this.handleResponse(response)
                })
                .catch(error => {
                    console.log(error);
                    this.handleError(error)
                });
        },
        resetData(index = null) {
            console.log('Reset Data Index: ' + index);
            // set the type of modal
            this.modal = index === null ? 'create' : 'update';
            // reset the appt to blank or assign selected
            this.appt = index === null
                ? {} : this.appt = this.appts[index];
            this.index = '';
            this.selected = [];
        },
        paidInFull(payment) {
            console.log('DocDash Paid In Full: payment', payment);
            let pos = this.appts.findIndex(i => i.patient_id === payment.patient_id);
            this.appt = this.appts[pos];
            console.log(this.appt);
            // update appt.due and mark_due fields
            if(Array.isArray(this.appt.due)) {
                // find the payment
                let pmtIndex = this.appt.due.findIndex(i => i.id === payment.id);
                // remove the payment from list of due
                this.appt.due.splice(pmtIndex, 1);
            } else {
                this.appt.due = null;
                this.appt.mark_due = false;
            }
            //update appts list
            this.appts.splice(pos, 1);
            this.appts.push(this.appt);
        },
        handleResponse(response) {
            if(response.data.message && !response.data.messages) {
                if(response.data.message.message) {
                    this.$toast.info(response.data.message.message);
                } else {
                    this.$toast.info(response.data.message);
                }
            }
            if(response.data.success) {
                this.$toast.success(response.data.success);
            }
            if(response.data.error && !response.data.errors) {
                this.$toast.error(response.data.error, { duration: false });
            }
            if(response.data.errors) {
                console.log(response.data.errors);
                response.data.errors.forEach((error) => {
                    this.$toast.error(error,{ duration: false });
                });
            }
            if(response.data.messages) {
                console.log(response.data.messages);
                response.data.messages.forEach((msg) => {
                    this.$toast.success(msg, {duration: false})
                })
            }
            if(isString(response.data)) {
                this.$toast.info(response.data)
            }
        },
        handleError(error) {
            this.$toast.error(error, {duration: false});
        }
    },
    // AKA computed properties
    getters: {
        countOnDeck() {
            return this.viewFinished ?
                this.appts.filter((appt) => appt.appt_status_id !== 2).length :
                this.appts.filter((appt) => appt.appt_status_id === 2).length;
        },
        getOnDeck() {
            return this.viewFinished ?
                this.appts.filter((appt) => appt.appt_status_id !== 2) :
                this.appts.filter((appt) => appt.appt_status_id === 2);
        },
        getFinished() {
            return this.viewFinished && this.value < 1 ?
                !this.viewFinished :
                this.viewFinished;
        },
        allSelected() {
            return this.selected.length === this.appts.length
        }
    }
})

App.vue

<template>
    <router-view />
</template>

<script setup>
import { useDocDashStore } from "../stores/DocDashStore";
const dash = useDocDashStore()
await dash.loadDays()
</script>

app.js

import { createApp } from "vue";
import App from "./App.vue"
import router from "@/router";
import Toaster from "@meforma/vue-toaster";
import { createPinia } from 'pinia';

const app = createApp(App);
const pinia = createPinia();

app.use(router).use(pinia).use(Toaster).mount("#app");
0 likes
1 reply
LaryAI's avatar
Level 58

Based on the information provided, it seems like you're encountering an issue where a function in your Pinia store's actions is not being recognized. This can happen for a few reasons, but the most common one is that the function is not properly defined or not being called correctly.

First, ensure that the function you're trying to call (loadAppts) is defined within the actions object of your Pinia store (DocDashStore.js). From the code you've provided, it looks like it is defined correctly.

Next, check how you're calling the function in your DocDash.vue component. You're using await dash.loadAppts(day) within the onMounted lifecycle hook, which seems correct.

However, if you're still getting an error, here are a few things to check:

  1. Make sure that the day variable you're passing to loadAppts is defined and has the correct value.
  2. Ensure that the loadAppts function is not misspelled or incorrectly referenced anywhere in your code.
  3. Verify that the Pinia store is properly initialized and that the useDocDashStore function is imported correctly in your component.
  4. Check if there are any errors in the network request inside the loadAppts function. If the API call fails, it might cause the function to throw an error.
  5. Make sure that the axios library is correctly imported in your DocDashStore.js file, as it's used for making API requests.

If you've checked all of the above and the issue persists, it might be helpful to see the exact error message you're getting. This can provide more insight into what's going wrong.

Lastly, ensure that your Vue DevTools is up to date and functioning correctly, as sometimes issues can arise from outdated tooling.

If you can provide the exact error message, I can give you a more precise solution to the problem.

Please or to participate in this conversation.