Subscribe on changes!

If a component is loaded by defineAsyncComponent in v-for, it will remount if any state value changed in v-for

avatar
Jun 18th 2021

Version

3.1.1

Reproduction link

https://jsfiddle.net/horseluke/8vpe05qw/

Steps to reproduce

  1. Open https://jsfiddle.net/horseluke/8vpe05qw/. This is a minimal reproduction using Vue 3.1.1.
  2. Click button "Just change state that in v-for" after 2 seconds.
  3. Click button "Just change state that in v-for" after 2 seconds again.
  4. Input random words in the first textarea and the second input text.
  5. Click button "Submit my answer".

What is expected?

  1. In step 2 and step 3, value that showed in "Mount time" should not be changed. If not changed, component is not remounted / rerendered.

  2. In step 4 and step 5, all input random words should be preserved.

What is actually happening?

  1. In step 2 and step 3, the first value showed in "Mount time" which belongs to component long_textarea has been changed. This means components long_textarea loaded by defineAsyncComponent has been remounted / rerendered. The second value showed in "Mount time" does not change because component short_text is not loaded by defineAsyncComponent.

  2. In step 4 and step 5, all input random words in the first textarea has been cleared. This means component long_textarea loaded by defineAsyncComponent has been remounted / rerendered. The second input text is preserved because component short_text is not loaded by defineAsyncComponent.


I'm developing a internal questionnaire system. Questions can appear as many models, such as single choice, multi choices, fill in the blanks etc.

My thought is to import question models as needed. To do this, I divided into these steps:

  1. Split question models into single .vue files.

Question model component exports a function to provide user input answer. If has error, just throw it.

  1. Define a loader, using defineAsyncComponent to load specific question model by name.
//name example: single_choice, multi_choices, long_textarea etc
function loadViewByDataModel(name){

  return defineAsyncComponent({
    loader: () => {
        return import("@/components/question-data-model/"   name   "/CompShow.vue");
    },
   });
}
  1. In show page component, use v-for to render question lists, use "component :is" directive to run loader, pass props to the real question model components using "v-bind", use ref to collect question model components instance, then collect answers from them.

Show page component collect answers from question model components instance, and handling error. If has error, show it in the question model component instance below.

<template v-for="(row, i) in state.questions" v-bind:key="i">

  <div>

      <component 
            v-bind:is="loadViewByDataModel(row.data_model)" 
            v-bind:ref="(el) => actionAddToQuestionCompList(i, el)"
            v-bind:question="row" 
            v-bind:testPassValue="state.testPassValue"
      ></component>

      <div v-if="state.answerErrorList[i]">{{state.answerErrorList[i]}}</div>

    </div>

</template>
setup(props, context){

  const state = reactive({
    questions: questions,
    answerErrorList: [],
  });

  const questionCompList = ref([]);

  function actionAddToQuestionCompList(i, el){
    if(el){
      questionCompList.value[i] = el;
    }
  }

  async function actionGetAllAnswers(){
    answerErrorList = [];
    await nextTick();
    let answers = [];
    for(let i =0 ; i < questionCompList.value.length; i  ){
          let comp = questionCompList.value[i];
          try{
              let answer = comp.actionGetAnswer(true);
              if(answer instanceof Promise){
                  answer = await answer;
              }
              answers[i] = answer;
          }catch(e){
            state.answerErrorList[i] = e.message;
          }
    }
    
    console.log([state.answerErrorList, answers]);

    return answers;

  }

  //https://v3.vuejs.org/guide/composition-api-template-refs.html#usage-inside-v-for
  onBeforeUpdate(() => {
    questionCompList.value = [];
  });

  return {
    state,
    actionAddToQuestionCompList,
    actionGetAllAnswers,
  };
},

But as I collect answers from question models, I find that component loaded by defineAsyncComponent will remounted / rerendered after click "submit".

After investigation, I find that if I put any state value in "v-for" block and change that value, component loaded by defineAsyncComponent in that block would be remounted / rerendered. This behaviour will clear data such as any user inputs in that component.

I think this is a bug, so I raise it.

avatar
Jun 18th 2021

Please provide a boiled down reproduction in a new issue not a copy of the part of your project that shows the error. This is too much to check. The title and description of your issue suggest the reproduction could be much much smaller and easier to follow.


Remember to use the forum or the Discord chat to ask questions!

avatar
Jun 18th 2021

Well, jsfiddler url I provided is the minimal reproduction, it is not a simple copy of the part of my project. What I provided in description is just for background.

avatar
Jun 18th 2021

I will try to provide another minimal reproduction asap.