Value of input field not getting changed

Using Vue3 and Vuex4

I got an input field:

<input :class="invalid.includes(item.attribute) ? 'invalidInput' : 'validInput'" type="text" :id="item.attribute" :name="item.attribute" :placeholder="item.default_value" min="0" step="any" :value="item.value" @input="validate(item.attribute, $event)" class="p-1">

I change the value of “invalid” like this. Just checking for the validity of a regex and adding/removing the attribute to the array.

        VALIDATE_INPUT: (state, data) => {
            var regex = /(?=.*\d)^\$?(([1-9]\d{0,2}(,\d{3})*)|0)?(\.\d{1,2})?$/;

            switch (data.attribute) {
                case 'invoice_amount_f':
                    if (!regex.test(data.value)) {
                        state.validations.push(data.attribute)
                    } else {
                        let index = state.validations.findIndex(el => el === data.attribute);

                        if (index > -1) {
                            state.validations.splice(index, 1);
                        }
                    }
                    break;
                default:
                    break;
            }
        }

The action calling the mutation is called like:

        const validate = (attribute, event) => {
            store.dispatch('validate', {
                attribute: attribute,
                value: event.target.value
            });
        }

Computed:

        var invalid = computed({
            get() {
                return store.getters.getValidationState;
            }
        });

When now typing something into the input field the text in the field ain’t chaning. This seems to happen cause I use the value of invalid inside the template. Why is that?

EDIT: It seems to have something to do with the scope I am using it in.

    <h3>{{ invalid }}</h3>
    <div v-if="nestedListItems && Object.keys(nestedListItems).length !== 0">
        <draggable v-model='nestedListItems' item-key="id" class=" w-12/12 bg-white m-auto border" animation="150">

When rendering it outside of draggable it’s absolutely fine. Inside it crashes my store.

Do you mean there is no input in the field, the keystrokes do not show any text-input changes?
Or the invalid/valid class is not applied and the text-style is not changing?

What do you mean by “crashes”? Do you have any error logs you can share?

Somehow when manipulating “invalid” and using the value inside the draggable scope the value of the input field jumps back to what it was before. That’s what I meant by crashing. So the state isn’t updating properly.

Is there a reason why you use :value="item.value" instead of v-model="item.value"?
And if you remove the value binding, is then the text-input and class-toggle working?

The structure is as follows:

  <draggable v-model='nestedListItems' item-key="id" class=" w-12/12 bg-white m-auto border" animation="150">
            <template #item="{ element, index, }">
                <div class="my-4 p-2 flex flex-row justify items-center border-2  overflow-x-scroll">
                    <div v-for="(item, propertyName) in element" :key="item.id" :class="{'hidden': !item.hasOwnProperty('type'), 'block': item.hasOwnProperty('type')}">
                        <div v-if="item.type === 'integer'" class="p-2 w-full m-1">
                            <label :for="item.attribute">{{ item.label }}</label>
                            <input type="number" :id="item.attribute" :name="item.attribute" :placeholder="item.default_value" min="0" step="any" :value="item.value" @change.prevent="refresh($event, item.refresh, index, propertyName)" @input="validate($event, item.attribute, index, propertyName)" class="border  p-1">
                        </div>

v-model is on the nestedElements for updating the order when I drag/drop the elements. Then according to the vuex docs I use :value="..." for value binding. Cause I don’t want to use a setter for every input field. Also it’s a template generated by a JSON structure so the amount of inputs is flexible and can differ)

If I remove the value binding it is working as I want, yea. But how could I use both?

I see. Yes, :value= is the way to go then to have only one-way data-binding.

You could enforce the one-way data-binding once more by passing the value as a string value, making sure it is definitely not passed by-reference … for testing

<input ... :value="item.value + ''" ...>

That sadly didn’t do the trick. The value still is jumping back to what it was before after typing. I don’t get why changing the invalid store value has an impact on the item value anyways.

Checking the onchange method the value in the store gets updated as it should. Just the data-binding isn’t working as expected.

Where does the nestedListItems value come from? Any change on it will trigger a re-draw on the draggable-component, wouldn’t it?

Are you maybe on-change updating its contents? And maybe it only triggers and update of the store-item that resembles nestedListItems but uses out-dated versions of the item-objects which are holding the old empty value?

NestedList is the whole form element that’s holding all input data and some information for the backend.
I am updating it witht eh input value onchange so it should be up to date when validating. That’s why I checked @change.prevent="refresh($event, item.refresh, index, propertyName)" calling the action in the store. So the value inside the store is set to the correct value.

Are you maybe on-change updating its contents? And maybe it only triggers and update of the store-item that resembles nestedListItems but uses out-dated versions of the item-objects which are holding the old empty value?

I checked this - and you are right. The values in item are just changing on a reload. Tho I really have no idea why this could be.

I think I found the reason, why nestedForm isn’t reactive. It’s because I pass a copy of it to the component by turning it into an array cause draggable doesn’t work with raw json.

            var result = [];

            if (state.form.line_items_attributes && Object.keys(state.form).length !== 0) {
                for (var i in state.form.line_items_attributes.nested_form) {
                    result.push(state.form.line_items_attributes.nested_form[i]);
                }
                state.form.line_items_attributes.nested_form = cloneDeep(result);
                return result;
            } else {
                // Needs better error handling
                console.log("Some error handling...");
                return state.form;
            }

Where do you pass this copy from? Because in this line

state.form.line_items_attributes.nested_form = cloneDeep(result);

you are actually mutating the store state (what should trigger a big no-no warning in development if done outside a mutation).

If you want to have your state value transformed I suggest to put the object-to-array logic into a computed value.

Sidenote: alternative obj-to-array+shallow-clone approach idea

let obj = state.form.line_items_attributes.nested_form;
obj = Object.keys(obj).map(key => { return {...obj[key]}; }); // updated

I’m getting it directly from the backend in this format. Maybe I should use strict mode, to get this warning then. Thanks!

The computation as you mentioned then can be made in the getter?
Or just put it into the computed proerty receiving the form?

Tho this obj = Object.keys(obj).map(key => {...obj[key]}); is throwing an error. Also like obj = Object.keys(obj).map(key => {[...obj[key]]}); saying obj is not iterable.

But I just might tell the backend dev to structure his json as:

  "line_items_attributes": {
            "type": "associations",
            "label": "Positionen",
            "attribute": "line_items_attributes",
            "model_class": "expense_line_item",
            "nested_form": [{..}] 

So I receive an array directly.

Sorry, I was too eager with the destructuring here and omitted one set of parenthesis :slight_smile: Fixed:

obj = Object.keys(obj).map(key => { return {...obj[key]}})

But if it comes directly from the backend why are you even doing another set of cloning of the object?

This would be the same without the shallow clone

obj = Object.keys(obj).map(key => obj[key])

And yes, either you put that into a store getter or into a computed value.
Then again you could also make the adjustment once on read-in of the value when you retrieve it from the backend.
Depending on how much data you hold in this object it might become a costly action that might slow down your interface if it has to be done on every keystroke.

But if it is anyway only a numeric-indexed object it actually should rather be an array instead. And if asking the backend dev for that change is an option it would not even be an empty request but an actual improvement over the data format.

1 Like

Thanks for your advice and help! So I definitely will go for the way of askingthe backend dev here, cause the JSON could get quite big at some point and I also took the performance in mind like you said.

1 Like