Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Max100's avatar

Object comparison fails @input, but not @blur

On each field of a form @input calls a function to check if the data in the form changed. If the data changed, a Save button appears so the user can save the changes.

When the page is rendered and the file_form object is initialized, a clone of the object is also saved. Something line this:

const file_form = useForm({    
    name: props.afile.name,
    type: props.afile.type,
    phone: props.afile.phone,
});

let saved_file_form = {};
saved_file_form = { ...file_form };  // clone a copy of  file_form

function fileEditCheck() {
    if( objectChanged( file_form, saved_file_form ) == true) {  // if changes in file form
        state.action = 'file_edit';
    } else state.action = 'file_show';
}

function objectChanged( obj_1, obj_2 ) {
    let obj_changed = false;

    for ( var key in obj_1 ) {
        if( obj_2[key] != obj_1[key] ) {
            obj_changed = true;
            break;
        }
    }

    return obj_changed;
}
<template>
<TextInput v-model="file_form.name" @input="fileEditCheck()" @blur="fileEditCheck()"/>
<TextInput v-model="file_form.type" @input="fileEditCheck()" @blur="fileEditCheck()"/>
<TextInput v-model="file_form.phone" @input="fileEditCheck()" @blur="fileEditCheck()"/>
</template>

If the user changes a field on the form, the save button appears as expected. But if they change the data back to the way it was initially, the button does not go away. However, with the test on @blur, it works properly.

It's not a big issue, but I'm curious why it's not working. Using console.log, I checked the values before and after the comparison, and though they match @input, it still returns true (changed). Yet @blur, it's false (no change).

Is that the way it's supposed to work?

0 likes
7 replies
LaryAI's avatar
Level 58

It seems like the issue you're encountering is related to the timing of when the @input event is fired compared to the @blur event. The @input event is fired immediately when the input's value changes, while the @blur event is fired when the input loses focus.

The problem might be due to the fact that the v-model directive updates the file_form object immediately on input, but the comparison with saved_file_form does not account for the reactivity of Vue.js. When you spread file_form to create saved_file_form, you're only creating a shallow copy, and since file_form is reactive, it might be getting updated in a way that your comparison function does not expect.

To ensure that you're comparing the current form state with the saved state correctly, you should create a deep copy of the file_form object when you initialize saved_file_form. You can use a utility function like JSON.parse(JSON.stringify(obj)) to create a deep copy.

Here's how you can modify your code:

const file_form = useForm({    
    name: props.afile.name,
    type: props.afile.type,
    phone: props.afile.phone,
});

let saved_file_form = JSON.parse(JSON.stringify(file_form)); // create a deep copy of file_form

function fileEditCheck() {
    if (objectChanged(file_form, saved_file_form)) { // if changes in file form
        state.action = 'file_edit';
    } else {
        state.action = 'file_show';
    }
}

function objectChanged(obj_1, obj_2) {
    return JSON.stringify(obj_1) !== JSON.stringify(obj_2);
}

// Template remains the same

By using JSON.stringify in the objectChanged function, you're comparing the serialized versions of the objects, which is a simple way to perform a deep comparison. However, be aware that this method can be inefficient for large objects and doesn't work with objects that contain functions, circular references, or non-serializable values.

If you need a more robust solution for deep comparison, consider using a utility library like Lodash, which provides a _.isEqual function that can handle deep object comparison more effectively.

// Using Lodash's isEqual function for deep comparison
import _ from 'lodash';

function objectChanged(obj_1, obj_2) {
    return !_.isEqual(obj_1, obj_2);
}

Remember to install Lodash if you choose to use it:

npm install lodash

This should resolve the issue with the save button not disappearing when the form data is changed back to its initial state.

Max100's avatar

@LaryAI It was worth a shot, but that didn't work either. The new code produced the same results. I haven't tried lodash, but that doesn't seem like it would change anything. Thanks anyway, Lary :)

gych's avatar
gych
Best Answer
Level 29

Can you share the part where the button is showed?

Code below should also simplify your current code

const file_form = useForm({    
    name: props.afile.name,
    type: props.afile.type,
    phone: props.afile.phone,
});

function fileEditCheck(fieldName) {
  if(file_form[fieldName] !== props[fieldName]) state.action = 'file_edit';
  else state.action = 'file_show';
}
</script>

<template>
<input v-model="file_form.name" @input="fileEditCheck('name')" @blur="fileEditCheck('name')"/>
<input v-model="file_form.type" @input="fileEditCheck('type')" @blur="fileEditCheck('type')"/>
<input v-model="file_form.phone" @input="fileEditCheck('phone')" @blur="fileEditCheck('phone')"/>
</template>
Max100's avatar

@gych Thanks for your reply. Here's the part where the buttons are showed:

<div v-show="state.action == 'file_edit'" class="flex mt-6 mb-2 justify-center">
   <button type="button" href="" class="btn btn-primary w-36 mr-14" @click="fileform_click( 'save' )">
      Save
   </button>
   <button type="button" href="" class="btn btn-primary" @click="fileform_click( 'cancel' )">
      Cancel
   </button>
</div>

Thanks for your code suggestions. That definitely simplifies and improves the code and I'll try it when I get back.

I'm curious to see if it resolves things, because previously it seemed the data was there for the comparisons to work, but it wasn't working for some reason. I'll let you know how it turns out. Thanks again!

gych's avatar

@Max100 Are the buttons in the same template as the input fields? Can you also show how you defined state.action as variable in your code.

Max100's avatar

@gych Yes, the buttons are in the same template, and here is how state.action is defined:

const state = reactive({
    row: 0,             // current list row
    folder_row: 0,
    show: props.entries.per_page,   // how many rows to show in list
    tab: 2,                         // current tab 
    action: 'browse',
});

The good news is, I used the approach you suggested and the issue is resolved! The form now behaves as it should.

I'm still not clear why the object comparison wasn't working, but perhaps something else in the useForm() object changes when a field is being edited. Thereafter, even if the edited field later matches again, the objects no longer do. And maybe this other value gets reset on the blur event, so the comparison works on the blur event but not the input event?

Anyway, things are now working as expected, and I learned a lot by following your suggestions! Thanks again for your help!

1 like

Please or to participate in this conversation.