2
votes

Vue.js version = 2.3.3

Question to everybody. I have json which contains a list of data, based on the json I create form inputs. The form inputs have a model binded, which basically updates app state trough commits.

I have a component which renders a list of checkboxes, On one page I have 1 instance and on the next one I have 3 instances of the component, each of them have their list of checkboxes, and a different name.

When going from page 1 to page 2, the checkbox_list component from page 1 doesn't fire the destroyed lifecycle hook (other components do fire this).

On page 2 I have 3 other components of type checkbox_list with their name, model and options. (data is initialized as a function). Somehow the third component of type checkbox list doesn't fire the created hook, neither mounted or w/e. It is rendered but doesn't and events and the model is there, and from vue debugger the model array is empty. The problem is that when I update checkbox model (by clicking on one of the checkboxes from the list) in the 3rd component instance (the one which didn't fire the created lifecycle hook) the model contains everything I've checked on the first page of the checkbox_list component instance.

The components are basically rendered in a loop as of the data is parsed. Anybody any ideas why does this happen? Thanks.

Please see bellow code of my components. InputTypes.ts

import Vue from 'vue';
import Component from 'vue-class-component';

import TextInput from './text/TextInput.vue';
import EmailInput from './email/EmailInput.vue';
import RadioGroup from './radio/RadioGroup.vue';
import SelectInput from './select/SelectInput.vue';
import SelectAutocompleteInput from './select/SelectAutocompleteInput.vue';
import PasswordInput from './password/PasswordInput.vue';
import CheckboxGroup from './checkbox/CheckboxGroup.vue';
import AgreementSection from './custom/AgreementSection.vue';
import CvSection from './custom/CVSection.vue';
import SubmitSection from './custom/SubmitSection.vue';


@Component({
  name: 'input-types',
  props: ['field', 'lastPageIndex'],
  components: {
    TextInput,
    EmailInput,
    RadioGroup,
    SelectInput,
    SelectAutocompleteInput,
    PasswordInput,
    AgreementSection,
    CheckboxGroup,
    CvSection,
    SubmitSection
  },
  watch: {
    '$route'($to, $from) {
      let clearFields = this['clearFields'];
      clearFields();
    }
  }
})

export default class InputsTypes extends Vue {
  conditionalFields: object = {fields: []};

  clearFields(): void {
    this.$set(this.conditionalFields, 'fields', []);
  }

  changeHandler(fields): void {
    this.$set(this.conditionalFields, 'fields', fields);
  }

  updateModelValue(data): void {
    console.log(data);
  }
}

InputTypes.vue

<!-- InputTypesComponent -->
<template>
  <div>
    <text-input v-if="field.type == 'text'" :field="field"></text-input>

    <email-input v-else-if="field.type == 'email'" :field="field"></email-input>

    <checkbox-group v-else-if="field.type == 'checkbox_list'" :field="field"></checkbox-group>

    <radio-group v-else-if="field.type == 'radio'" :field="field" :onChange="changeHandler"></radio-group>

    <select-input  v-else-if="field.type == 'select'" :field="field" :onChange="changeHandler"></select-input>

    <select-autocomplete-input  v-else-if="field.type == 'select_autocomplete'" :field="field" :onChange="changeHandler"></select-autocomplete-input>

    <password-input v-else-if="field.type == 'password'" :field="field"></password-input>

    <agreement-section v-else-if="field.type == 'agreement'" :field="field"></agreement-section>

    <div v-else-if="field.type == 'complex_inputs'">
      <label>{{field.label}}</label>
      <div v-for="option, key in field.options" :key="key">
        <input-types :field="option" :onChange="changeHandler"></input-types>
      </div>
    </div>
    <cv-section v-else-if="field.type == 'cv_section'" :field="field"></cv-section>
    <submit-section v-else-if="field.type == 'next_page'" :field="field" :lastPageIndex="lastPageIndex"></submit-section>

    <div v-if="conditionalFields.fields" v-for="field, key in conditionalFields.fields" :key="key">
      <input-types :field="field" :onChange="changeHandler"></input-types>
    </div>
  </div>
</template>

<script lang="ts">
  import InputsTypes from './InputsTypes.ts'
  export default InputsTypes
</script>

CheckboxGroup.ts

import Vue from 'vue';
import Component from 'vue-class-component';


@Component({
  name: 'checkbox-group',
  props: ['field'],
  watch: {
    selected: (e) => {
      console.log(e);
    }
  },
  created(): void {
    let predefinedSelected: Array<string> = [];
    let data: object = {
      'name': this.$props.field.name,
      'value': predefinedSelected
    };

    let updateState = this['updateState'];
    updateState(data);
  }
})

export default class CheckboxGroup extends Vue {
  updateStore(): void {
    let data: object = {
      'name': this.$props.field.name,
      'value': this['selected']
    };
    this.updateState(data);
  }

  updateState(data): void {
    this.$store.commit('SIMPLE_FIELD_UPDATE', data);
  }

  data(): object {
    let predefinedSelected: Array<string> = this.$props.field.selected || [];
    return {
      selected: []
    }
  }
  created(): void {
    console.log('created');
  }
  mounted(): void {
    console.log('mounted');
  }
  destroyed(): void {
    console.log('destroyed');
  }
}

CheckboxGroup.vue

    <template>
  <div class="checkbox-group">
    <div class="row" v-if="field.label">
      <div class="col">
        {{field.label}}
      </div>
    </div>
    <div class="row">
      <div class="form-check col-3" v-for="option, key in field.options" :key="key">
        <label class="form-check-label">
          <input class="form-check-input" v-model="selected" @change="updateStore" type="checkbox" :name="field.name" :value="option.label"/>
          {{option.label}}
        </label>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
  import CheckboxGroup from './CheckboxGroup.ts'
  export default CheckboxGroup
</script>
3

3 Answers

1
votes

This is a great spot for a mutable component, allowing you to have components that can change form at run time based on data.

So you'll want to add your composite types to their own components and then instead of the conglomeration component input-types you just use

<component :is="field.type" :field="field"></component>

wherever you'd use

<input-types :field="field"></input-types>

You will need to make sure that your field.type matches your component labels. so for a text-input component field.type will need to be text-input.

If you need any help let me know.

0
votes

That is a pretty huge description/set of code to try to read/understand what the real problem is. I might suggest trying as simple of a description/situation as possible in the future.

I believe what the source of your issue may be has to do with the interaction of v-for and v-if on the same element. The v-if statement is going to re-evaluate for each loop on that v-for element. Simplest answer is to instead do your v-if on a <template> element just above the thing you want the v-for to run on.

https://vuejs.org/v2/guide/list.html#v-for-with-v-if

0
votes

The problem was solved easily. The problem was that I was changing the views based on an url, and rendering different components, but some of them were persistent in the view so Vue considered those are the same components that were needed in previous view. To solve this I just passed a key to the Page component, so in this situation it is treated as a different component and there is no shallow comparison inside the Page component.

Conclusion: Don't forget you keys guys and girls.