Search code examples
vue.jsmodelcomponents

What is wrong with the way the view model is being injected into the Vue control in this example?


The following example gives the error "Uncaught TypeError: Cannot read properties of undefined (reading 'count1')".

File: button.html

<!DOCTYPE html>
<html>
<head>
    <title>Button Counter</title>
    <script src="/js2/vue-global-min-3.3.4.js"></script>
    <script src="button.js"></script>

    <script>
        const vm = Vue.reactive
        (
            {
                count1: { count: 0 },
                count2: { count: 0 },
                count3: { count: 0 }
            }
        );

        initButton();
    </script>
</head>
<body>
    <div id="app">
        <button-counter id="b1" :count="vm.count1"></button-counter>
        <button-counter id="b2" :count="vm.count2"></button-counter>
        <button-counter id="b3" :count="vm.count3"></button-counter>
        <div id="r1">{{ vm.count1.count }}</div>
    </div>
</body>
</html>

File: button.js

var initButton = function ()
 {
    const ButtonCounter =
    {
        props: ['count'],
        emits: ['update:count'],
        template: `
            <button @click="incrementCount">
                You clicked me {{ count }} times.
            </button>
        `,
        setup (props)
        {
            const incrementCount = () =>
            {
                this.$emit ('update:count', { ...props.count, count: props.count + 1 });
            };

            return { incrementCount };
        },
    };

    function registerTag ()
    {
        const app = Vue.createApp ({});
        app.component ('button-counter', ButtonCounter);
        app.mount ('#app');
    }

    document.addEventListener('DOMContentLoaded', registerTag);
};

I was expecting to see three buttons and the div text. I got the error mentioned above. I checked the expression "vm.count1.count" and it is a Vue reference with the value 0.


Solution

  • vm is defined in the global scope, but Vue needs it declared in the corresponding component, which in your case is the root component. A quick fix is to return it from a setup function:

        function registerTag ()
        {
            const app = Vue.createApp ({
              setup(){         // <------  add a setup() function which returns vm
                return {
                  vm
                }
              }
            });
            app.component ('button-counter', ButtonCounter);
            app.mount ('#app');
        }
    

    Or move the declaration into the setup block completely, which would get you closer to a typical Vue setup, as you can explore in the Vue 3 sandbox.