Search code examples
javascriptvue.jspromisetimeout

VueJS: best way to toggle between two instances of the same component


Lets say I have foo.vue that I import into the parent as components named a and b and displayed according to a variable show

Now from the parent if I switch between components a and b without setting the show = null I get all sorts of child state errors.

So I set show to null before setting it to the corresponding vue component like this

this.show = null
this.show = a // or b

but this does not work. component ends up with state of the previous. This is verified even on props that are not updated.

I made it work using timeout

toggle(show){
   this.show = null
   let that = this
   setTimeout(() => {
      that.show = show
   }, 200)
 }

Is there a better way ? That is not elegant to me

What I understand is that what is going on inside the child does not matter if the parents tells it to switch it should go from blank slate and rebuild ? But in my case it is not the case. Is there anything that could cause my issue ?

setting to null forces a hard refresh somehow ?

My child component is complex with ajax call to retrieve a list but nothing out of the ordinary.

parent code

<template>
<div id="userAreaEventWrapper">
    <div class="userarea-submenu">
        <div v-on:click="toggle(comps.eventForm)" class='button-base-out'>
            <div :class="isSelected(comps.eventForm)">
                New Event
            </div>
        </div>
        <div v-on:click="toggle(comps.events)" class='button-base-out'>
            <div :class="isSelected(comps.events)">
                My Events
            </div>
        </div>
      <div v-on:click="toggle(comps.eventsAttending)" class='button-base-out'>
        <div :class="isSelected(comps.eventsAttending)">
          Attending
        </div>
      </div>
    </div>
    <EventForm v-if="show === comps.eventForm" @created="toggle(comps.events)" :mode="'create'"></EventForm>
    <Events ref="events" :mode="currentMode" v-if="show === comps.events" @noResults="toggle(comps.eventForm)" :attending="false"></Events>
    <AttendingEvents ref="attending" :mode="'home'" v-if="show === comps.eventsAttending" :attending="true"></AttendingEvents>
</div>
</template>

<script>
const EventForm = () => import( './event-form.vue')
const Events = () => import( './events.vue')
const AttendingEvents = () => import( './events.vue')

export default {
    name: "UserEventComponent",
    components:{
        EventForm,
        Events,
        AttendingEvents
    },
    data(){
        return{
            comps:{
              eventForm:1,
              events:2,
              eventsAttending:3
            },
            show: 2,
            currentMode:'user',
        }
    },
    methods: {
        toggle(show){
          this.show = null
          let that = this
          setTimeout(() => {
            that.show = show
          }, 200)
        },
        isSelected(n){
          return n === this.show ? 'button-base-in button-base-in-hover' : 'button-base-in'
        },

    },
}
 </script>

child

fetch api on mounted()

  mounted() {
    this.currentMode = this.mode
    if(this.resumeData && this.resumeData.slug){
       this.selectedSlug = this.resumeData.slug
    } else {
       this.resume(this.resumeData)
       this.getEvents()
    }
   }

Solution

  • This part doesn't serve a good purpose:

    const Events = () => import( './events.vue')
    const AttendingEvents = () => import( './events.vue')
    

    The core feature of JavaScript modules (this includes ES modules) are evaluated only once on first import, import( './events.vue') resolves to the same component. It gives a better idea whats going on when a component is used under a single name.

    What happens on show update is that there's 1 events component instance in the same place in component hierarchy, so it's not re-mounted and just receives a set of new props, specified in <Events> and <AttendingEvents> respectively.

    Sibling instances of the same component should be distinguished by key:

    <Events ref="events" key="events" ... ></Events>
    <Events ref="attending" key="attending" ... ></Events>
    

    It is used most times with v-for but also applicable to all directives that affect component hierarchy - v-if, etc.