Tuesday, 25 May 2021

Implementing nested $refs in a VueJS SPA implementing Vue-Router

I am working on a Laravel/VueJS SPA that utilizes Vue / Vue-Router. I have a Vue component that has been assigned a $ref (inside Component C / Nested Child Component), that is encapsulated within the primary Vue-Router component (Component B) that is injected into the layout (Component A - Layout Component)

I need to access this element that corresponds to this $ref (inside component C), however I am unable to access it because the component hierarchy (within Component A)is constructed from information declared in Component B.

(Layout-Component / $root) - Vue Layout Component:    
    --(Vue-Router-Component) - Primary component injected into layout via Vue-Router - Child Component of Component A
      --(dynamic-form-field) - Child Component of Vue-Router-Component

<Layout-Component ref="componentA">
    <Vue-Router-Component ref="componentB">
        <dynamic-form-field ref="componentC">
        </dynamic-form-field>
    </Vue-Router-Component>
</Layout-Component>

I tried accessing the $ref using the standard nested $ref syntax:

  this.$root.$refs['componentB'].$refs['componentC'].attribute

However I am unable to access any $refs declared within Components B or C from Component A.


I determined that it is a Vue lifecycle issue, because if I recreate the nested component hierarchy (Components A - C) directly within the main layout (without using data declared within Component B) then I can access the nested $refs without issue via the above syntax.


The problem stems from the creation of the components in Component-A (Layout-Component) from data declared in Component B.

Layout Component (Component A) Snippet:

<template v-for="(input, field) in formDialog.inputs">
                                <template v-if="Array.isArray(input)">
                                    <!-- for every record in the input array -->
                                    <template v-for="(inputArrRecord, arrIndex) in input">
                                        <v-col cols="12" class="p-0">
                                            <v-btn icon x-small
                                                v-if="arrIndex"
                                                class="float-right" color="error"
                                                :title="inputArrRecord.typeTitle ? `Remove ${inputArrRecord.typeTitle}` : 'Remove'"
                                                @click="inputArrRecord.removeAction"
                                            >
                                                <v-icon>mdi-close-box-multiple-outline</v-icon>
                                            </v-btn>
                                        </v-col>

                                        <template v-for="(inputArrRecordInput, field2) in inputArrRecord.inputs"> <!-- for every field in the array record -->
                                            <dynamic-form-field
                                                :ref="`dynamicFormField${field2}`"
                                                :input="inputArrRecordInput"
                                                :field="field2"
                                                :mode="formMode"
                                                :error="formDialog.errors[`${field}.${arrIndex}.${field2}`]">
                                            </dynamic-form-field>
                                        </template>
                                    </template>
                                </template>

                                <dynamic-form-field v-else
                                    :ref="`dynamicFormField${field}`"
                                    :input="input"
                                    :field="field"
                                    :mode="formMode"
                                    :error="formDialog.errors[field]">
                                </dynamic-form-field>
                            </template>

Data Declaration (Component B) Snippet:

    data() {
        return {
            formDialog: {
                errors: [],
                show: false,
                inputs: {
                    id: {
                        val: '',
                        save: true,
                        type: 'hidden'
                    },

                    word_data: [],
                    definitions: [],

                    tags: {
                        val:    [],
                        save:   true, add:  true,
                        type:   'autocomplete',
                        items:  this.$root.cache.tags,
                        ref:    'vocabTagsAutocomplete',
                        label:  'Search For a Tag',
                        actionChange: this.addExistingTag,
                        actionKeydown: this.addNewTag,
                    },

                    synonym: {
                        val: '', save: true, add: true,
                        placeholder: 'Synonyms'
                    }
                },
                titleActions: [
                    {
                        icon:       'mdi-book-plus',
                        btnType:    'text',
                        text:       'Add Word Type',
                        event:      this.cloneWordDataTemplate
                    },
                    {
                        icon:       'mdi-book-plus-outline',
                        btnType:    'text',
                        text:       'Add Definition',
                        event:      this.cloneDefinitionTemplate
                    }
                ]
            }   
        }
    }

dynamic-form-field.vue (Component C) Content:

<template>
    <v-col v-if="(mode == 'add' && input.add) || (mode == 'edit' && input.save)" :cols="input.gridSize || 12">
        <form-field-selection
            :input="input"
            :field="field"
            :error="error"
            :ref="`formFieldSelection${field}`">
        </form-field-selection>
    </v-col>
</template>

<script>
    export default {
        name: 'dynamic-form-field',
        props: [
            'input',
            'field',
            'mode',
            'error'
        ]
    }
</script>

How can I access the $ref declared within Component C from Component A?



from Implementing nested $refs in a VueJS SPA implementing Vue-Router

No comments:

Post a Comment