Search code examples
vue.jsvuejs2web-componentcustom-element

Why are my object and array props becoming strings in child components?


I'm creating an instant messenger as a Vue.js learning exercise and I am encountering the confounding error wherein arrays and objects passed as props for slotted child components to access are becoming strings, and producing errors of the like:

[Vue warn]: Invalid prop: type check failed for prop "messageGroup". Expected Array, got String with value "[object Object],[object Object],[object Object],[object Object]".

and

[Vue warn]: Invalid prop: type check failed for prop "message". Expected Object, got String with value "[object Object]".

Passing primitive types as props for child components works as expected. Passing objects and arrays, using either :prop-name or v-bind:prop-name results in strings.

How may I resolve this or how is my understanding of the proper way to do this lacking?

Here is the full code for the components and how they are registered:

Messenger:

<template>
    <section id="messenger">
        <header>
            <h1>Test Conversation Window</h1>
        </header>
        <aside class="control-menu">
            Control Menu 
            <hr>
        </aside>
        <!-- The aggrieving section -->
        <main v-if="messageGroups.length" >
            <vue-message-group 
                v-for="(messageGroup, i) of messageGroups" 
                :key="i"
                v-bind:message-group="messageGroup"
                :username="messageGroup[0].username" >
                <vue-message 
                    v-for="message of messageGroup" :key="message.id"
                    v-bind:message="message" >
                </vue-message>
            </vue-message-group>
        </main>
        <!-- -- -->
        <aside class="participants">
            Participants
            <hr>
            <ul v-if="participants.length">
                <li v-for="(user, i) in participants" :key="i">
                    <img v-bind:src="user.avatar" v-bind:alt="user.username">
                    <span>{{ user.username }}</span>
                </li>
            </ul>
        </aside>
    </section>
</template>

<script>
export default {
    data() {
        return {
            participants: [],
            messageGroups: null,
        }
    },
    created() {
        this.getParticipants( [ 'john', 'karen' ] );
        this.messageGroups = this.groupMessages( this.testMessages() );
    },
    methods: {
        getParticipants: async function( users ) {
            this.url = 'http://*.*.*.*/api/user/';
            this.participants = await Promise.all( users.map( 
                async participant => 
                await this.getUserFromAPI( participant )
            ) );
        },
        getUserFromAPI: async function( username )
        {
            return await fetch( this.url + username )
                .then( data => data.json() )
                .then( data => data.data );
        },
        groupMessages: function( messages ) {
            let lastUsername = null;
            let groups = new Array();
            let group = null;
            for ( const message of messages )
            {
                if ( message.username !== lastUsername )
                {
                    group = new Array();
                    group.push( message );
                    groups.push( group );
                    lastUsername = message.username;
                }
                else
                {
                    group.push( message );
                }
            }
            return groups;
        },
        testMessages: function() {
            return [
                {
                    id: 0,
                    username: 'john',
                    message: 'TEST 1 message content.',
                    timestamp: '2020-05-20T23:09:13',
                },
                {
                    id: 1,
                    username: 'john',
                    message: 'TEST 2 message content.',
                    timestamp: '2020-05-20T23:09:13',
                },
                {
                    id: 2,
                    username: 'john',
                    message: 'TEST 3 message content.',
                    timestamp: '2020-05-20T23:09:13',
                },
                {
                    id: 3,
                    username: 'karen >:[',
                    message: 'TEST 4 message content.',
                    timestamp: '2020-05-20T23:09:13',
                },
                {
                    id: 4,
                    username: 'karen >:[',
                    message: 'TEST 5 message content.',
                    timestamp: '2020-05-20T23:09:13',
                },
                {
                    id: 5,
                    username: 'john',
                    message: 'TEST 6 message content.',
                    timestamp: '2020-05-20T23:09:13',
                },
                {
                    id: 6,
                    username: 'karen >:[',
                    message: 'TEST 7 message content.',
                    timestamp: '2020-05-20T23:09:13',
                },
                {
                    id: 7,
                    username: 'karen >:[',
                    message: 'TEST 8 message content.',
                    timestamp: '2020-05-20T23:09:13',
                },
                {
                    id: 8,
                    username: 'karen >:[',
                    message: 'TEST 9 message content.',
                    timestamp: '2020-05-20T23:09:13',
                },
                {
                    id: 9,
                    username: 'karen >:[',
                    message: 'TEST 10 message content.',
                    timestamp: '2020-05-20T23:09:13',
                },
            ];
        },
    },
}
</script>

Message-group:

<template>
    <figure class="message">
        <main>
            <span>{{ username }}</span>
            <slot></slot>
        </main>
    </figure>
</template>
<script>
export default {
    props: {
        username: String,
        messageGroup: Array,
    },
}
</script>

Message:

<template>
    <div class="message">
        <span class="line">{{ message.message }}</span>
        <div class="timestamp">{{ message.timestamp }}</div>
    </div>
</template>
<script>
export default {
    props: {
        message: Object,
    },
}
</script>

App.js:

import Vue from 'vue'

import vueCustomElement from 'vue-custom-element'
/*
* See: https://github.com/karol-f/vue-custom-element
*/

Vue.use(vueCustomElement);

Vue.config.ignoredElements = [
    'vue-messenger',
    'vue-message-group',
    'vue-message',
];


import VueMessenger from './components/VueMessenger.vue'
const messengerStyles = require( '!css-loader!sass-loader!./components/css/VueMessenger.scss' );
Vue.customElement( 
    'vue-messenger', 
    VueMessenger, 
    {
        shadow: true,
        shadowCss: messengerStyles.toString(),
    } 
);

import VueMessageGroup from './components/VueMessageGroup.vue'
const messageGroupStyles = require( '!css-loader!sass-loader!./components/css/VueMessageGroup.scss' );
Vue.customElement( 
    'vue-message-group', 
    VueMessageGroup, 
    {
        shadow: true,
        shadowCss: messageGroupStyles.toString(),
    } 
);

import VueMessage from './components/VueMessage.vue'
const messageStyles = require( '!css-loader!sass-loader!./components/css/VueMessage.scss' );
Vue.customElement( 
    'vue-message', 
    VueMessage, 
    {
        shadow: true,
        shadowCss: messageStyles.toString(),
    } 
);

Result: enter image description here


Solution

  • The problem stems from the fact that I am registering the Vue components as custom elements with the browser; the browser requires all element attributes to be strings, and therefor coerces objects and arrays.

    The most simple work-around seems to be passing object properties instead of objects themselves.

    I am experimenting with passing json in the :props.