1
votes

I have a multistep form on vue js. I need to dynamically during input send requests, validate on the server and receive a response to display validation errors.

My data:

data() {
    return {
        form: {...this.candidate}, // we get an object with fields already filled *
        errors: new Errors(), // object with validation errors
    }
},

Now for each input I have computed property:

characteristicFuturePlans: {
    get() {
        return this.form.characteristic_future_plans;
    },
    set(value) {
        this.saveField('characteristic_future_plans', value);
    }
},

saveField method sends data:

saveField(field, value) {
    this.form[field] = value; // keep the data in the object relevant
    axios.put(`/api/candidate/${this.candidate.token}`, {[field]: value})
        .then(() => { this.errors.clear(field) })
        .catch((error) => {
            this.errors.record(field, error.response.data.errors[field]);
        });
},

Now, with each change of input, a request will be sent. But with this approach, when we quickly type text in a field, sometimes the penultimate request sent comes after the last. It turns out that if you quickly write "Johnny", sometimes a query with the text "Johnn" will come after a query with the text "Johnny" and the wrong value will be saved in the database.

Then I made sure that the data was sent 1 second after the termination of text input. Added timerId: {} to data(){} and then:

saveField(field, value) {
    if(this.timerId[field]) {
        clearTimeout(this.timerId[field]);
    }
    this.timerId[field] = setTimeout(this.send, 1000, field, value);
},
send(field, value) {
    this.form[field] = value;
    axios.put(`/api/candidate/${this.candidate.token}`, {[field]: value})
        .then(() => { this.errors.clear(field) })
        .catch((error) => {
            this.errors.record(field, error.response.data.errors[field]);
        });
},

But now, if after filling in the input in less than a second, press the button to go to the next step of the form, the page will simply refresh. (the button to go to the next step will send a request to the server to check if the required fields are filled)

How correctly save data to the database during text input? Can I do this without setTimeout()? If so, how can I make sure that the data of the last request, and not the penultimate, is stored in the database? I will be glad to any tip.

Updated. Attach some template code.

Part of Step[i].vue component:

<div class="row">
    <div class="form-group col-md-6">
        <element-form  title="Title text" :error="errors.get('characteristic_future_plans')" required isBold>
        <input-input v-model="characteristicFuturePlans" :is-error="errors.has('characteristic_future_plans')"
            :placeholder="'placeholder text'"/>
        </element-form>
    </div>
</div>

template of input-input component:

<input :value="value" :type="type" class="form-control" :class="isErrorClass" 
       :placeholder="placeholder" @input="$emit('input',$event.target.value)">

Components of form steps are called from the Page component. Nearby is the component of the button for moving to the next step.

<component :is="currentStep"
           :candidate="candidate"
           // many props
           :global-errors="globalErrors"/>

<next-step :current-step="step" :token="candidate.token"
           @switch-step-event="switchStep" @throw-errors="passErrors"></next-step>

NextStep component sends a request to the server, it is checking whether the required fields are filled in the database. If not, throw out a validation error. If so, go to the next form step.

1
can you post the template part?Nikola Kirincic
@niklaz I added some additional informationnuclear_avocado

1 Answers

1
votes

you could try with watching the input values, and then use _.debounce() from underscore.js (src: https://underscorejs.org/#debounce) to delay the method call that makes a server request:

   watch: {
        fieldName: _.debounce(function(value) {
            if(value === ''){
                return;
            }
            this.saveField(this.fieldName, value);
        },
        ...