Search code examples
vue.jsvuex

Vuex state not being initialised before router-view component being rendered - undefined error


I am relatively new to vue and have run into a small issue. I am rendering a component that depends on the state stored in vuex. I load this information in from a json file in the main part of the app. It all works fine if I always land on the root (index.html) of the app when it loads up. However, if I refresh the app from a page that is dynamically generated from the router I hit an error:

[Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined"

found in

---> <Room>
       <RoomsOverview>
         <Root>

As far as I can tell what is happening is that that the component is trying to access the state in vuex but it has not been initialised. Here is the component (Room.vue):

<template>
    <div id="room">
        <h2>{{ roomName }}</h2>
        <div v-for="device in deviceList" v-bind:key="deviceList.name">
            {{ device.name }} - {{ device.function}}
            <svg-gauge v-bind:g-value="device.value" v-bind:g-min="0" v-bind:g-max="50" v-bind:g-decplace="1" g-units="&#8451;">
                <template v-slot:title>
                    Temperature
                </template>
            </svg-gauge>
        </div>
    </div>
</template>

<script>
module.exports = {
    name: 'room',

    /** Load external component files
     *  Make sure there is no leading / in the name
     *  To load from the common folder use like: 'common/component-name.vue' */
    components: {
        'svg-gauge': httpVueLoader('components/DisplayGauge.vue'),
    }, // --- End of components --- //
    
    data() {
        return {

        };
    },
    computed: {
        roomName() {
//            return this.$route.params.roomId;
            return this.$store.getters['rooms/getRoomById'](this.$route.params.roomId);
        },
        deviceList() {
            return this.$store.getters['rooms/getDevicesinRoom'](this.$route.params.roomId);
        },
    },
}
</script>

The error is triggered by the line

            return this.$store.getters['rooms/getRoomById'](this.$route.params.roomId);

This tries to access the current state in the getter:

    getRoomById: (state) => (id) => {
        return state.rooms.find(room => room.id === id).name; // Needs fixing!
    },

but it seems that the array:

// Initial state
const stateInitial = {
    rooms: [],
};

has not been initialised under these circumstances. The initialisation occurs in the main entry point to the app in index.js in the mounted hook

        // Load data from node-red into state
        vueApp.$store.dispatch('rooms/loadRooms')

Where loadRooms uses axios to get the data. This works as expected if I arrive at the root of the site (http://192.168.0.136:1880/uibuilderadvanced/#/) but not if I arrive at a link such as (http://192.168.0.136:1880/uibuilderadvanced/#/rooms/office). I suspect it is all down to the order of things happening and my brain has not quite thought things through. If anyone has any ideas as to how to catch this I would be grateful - some kind of watcher is required I think, or a v-if (but I cannot see where to put this as the Room.vue is created dynamically by the router - see below).

Thanks

Martyn

Further information:

The room component is itself generated by router-view from within a parent (RoomsOverview.vue):

<template>
    <div id="rooms">
        <b-alert variant="info" :show="!hasRooms">
            <p>
                There are no rooms available yet. Pass a message that defines a room id and device id 
                to the uibuilder node first. See <router-link :to="{name: 'usage_info'}">the setup information</router-link> 
                for instructions on how start using the interface.
            </p>
        </b-alert>
        
        <router-view></router-view>
    </div>
</template>

<script>
module.exports = {
    name: 'RoomsOverview',
    data() {
        return {

        };
    },
    computed: {
        hasRooms() {
            return this.$store.getters['rooms/nRooms'] > 0;
        },
        roomList() {
            return this.$store.getters['rooms/getAllRooms'];
        },
    },
}
</script>

and is dependent on the router file:

const IndexView = httpVueLoader('./views/IndexView.vue');
const AdminView = httpVueLoader('./views/AdminView.vue');

export default {
    routes: [
        {
            path: '/',
            name: 'index',
            components: {
                default: IndexView,
                menu: HeaderMenu,
            },
        },
        {
            path: '/rooms',
            name: 'rooms_overview',
            components: {
                default: httpVueLoader('./components/RoomsOverview.vue'),
                menu: HeaderMenu,
            },
            children: [
                {
                    path: ':roomId',
                    name: 'room',
                    component: httpVueLoader('./components/Room.vue'),
                },
            ],
        },
        {
            path: '/admin',
            name: 'admin', 
            components: {
                default: AdminView,
                menu: HeaderMenu,
            },
            children: [
                {
                    path: 'info',
                    name: 'usage_info',
                    component: httpVueLoader('./components/UsageInformation.vue'),
                }
            ]
        },
    ],
};

Solution

  • It seems you already got where the issue is. When you land on you main entry point, the axios call is triggered and you have all the data you need in the store. But if you land on the component itself, the axios call does not get triggered and your store is empty.

    To solve you can add some conditional logic to your component, to do an axios call if the required data is undefined or empty.