Updating temp states from js query using field id that ran the query

Hi,
I'm trying to track if fields have been updated. To do this I was hoping to use setIn for a temp state to record them e.g.:
state_data_changed
0 -> field_name -> clean (i.e. original data)
0 -> field_name -> dirty (i.e. new data)
The hope is then in the "On Change" for the field I can check the clean against the dirty value and if it reverts back to the original value its "clean" again so remove the entry in temp state.
Written it for a single field and works perfectly, however I dont want to write a js query for 40 fields so was hoping I could just pass in the field name element to the script and use it to run the necessary checks / updates.

e.g. in JS query
console.log("OLD VALUE:" + state_data_changed.value[fieldIndex].field_id.clean);

Passing in the index (i) works fine. If I substitue "field_id" for the actual field name it works perfectly. Unfortunately I cant seem to get this to work. Ive tried using triggeredById, I've tried passing in using additionalScope and sending in "self" / "self.id" etc but I cant get it to pull back the already stored value.

My JS skills are fairly limited so I'm hoping someone out there can point me in the right direction. :grinning:

Cheers,
Rob

Hi @Rob_Faulkner,

I have been thinking about this problem as well. I think the easy solution for you could be to set your action on the change to be 'Run a script'

In that script you will have access to {{ self.id }} and {{ self.value}} which you can then set to an intermediary Temporary state value. Then in your script handler that updates state_data_changed will have access to the current field data. So something like this:

const currentField = { id: self.id, value: self.value }
current_input.setValue(currentField)

await update_state.trigger() // whatever your global JS method is

Then in the onSuccess for this JS query you set the current_input value to null.

Make sense?

Hi @Ron_West

Thanks for the suggestion. I think I understand what youre saying however the issue Im having isnt passing through the current value of the input its using the "self.id" value to access the stored value in my temp state.

Current setup is something like this:

  1. onFocus to the input field I update my temp state with the current value using the eventhandler, so in effect this:

state_data_changed.setIn[{{i}},{{self.id}},"clean"];

With the value set to current field value. It is set to "Only Run when" the value is not already set (i.e. it will only store the value once so its always the original clean value).
{{state_data_changed.value[i].txt_cust_business_name.clean==undefined}}

This creates a temp state in the format:
Screenshot 2023-03-24 at 07.02.07

  1. onChange to the input field I use Run Script to send in value / id:
js_field_data_changed.trigger({
  additionalScope: {
    fieldName: self.id,
    fieldValue: self.value,
    fieldIndex: i
  }
});
  1. I then want to use the fieldName to reference the state and check the "clean" value against the "dirty" value:
if (state_data_changed.value[fieldIndex].txt_cust_business_name.clean!=fieldValue) {
  //Field changed so write to dirty value.
  state_data_changed.value[fieldIndex].txt_cust_business_name.dirty=fieldValue;
} else {
  //Clear the state as nothing changed.
  state_data_changed.value[fieldIndex].txt_cust_business_name.setValue("{}");
}
  1. The above works fine. Also, in the event the user changes the value to something different and then changes it back again, the JS will know it reverted to its original value and delete the entry in state_data_changed. The theory being that before the user moves away I can check state_data_changed and inform the user they have unsaved changes (and what those changes are).

  2. The issue Im having is using fieldName from point 2 above in the script in point 3. fieldIndex works fine, however if I try to do the following:

if (state_data_changed.value[fieldIndex].fieldName.clean!=fieldValue) {
//Do something
}

I basically get back that state_data_changed.value[fieldIndex].fieldName.clean is undefined or Cannot read properties of undefined (reading 'clean'). I assume its something to do with fieldName being seen as just a text value rather than the field it refers to.

Without this, I cant dynamically reference the field being changed and would therefore need a separate JS query for each field where the fieldName would have to be manually set.

Thanks again for pitching in, if I've misunderstood your recommendation please let me know.

Cheers,

@Rob_Faulkner,

It does look like you are close with your solution. This is probably something that can be solved with a zoom session where you try some things with some help. My recommendation would be to bring this to the Office Hours for Retool. I believe its Wednesday and Thursday. You will have an opportunity to share your screen with some Retool Support Engineers and they can probably help.

I am interested in seeing if you get this resolved -- I have wanted to do similar work. And realistically, this should be a part of the product - so maybe you can bring it to their attention and have someone create a ticket for an enhancement.

Thanks @Ron_West ... Im away from the office next week but Office Hours sounds like a good plan, Ill get on it as soon as Im back and let you know how I get on! :crossed_fingers:

1 Like

Hey @Rob_Faulkner!

Can you try using state_data_changed.value[fieldIndex][fieldName].clean? If you're using a variable to access a property on an object you specifically need to use bracket notation. Let me know if that does the trick!

Well dont I feel stupid! :slight_smile:

@Kabirdas that worked, thank you! I could have sworn I had already tried that (multiple times) and got an error about not using dot notation popping up in the script editor? Must have got myself tied up in multiple errors and got a false negative.

@Ron_West ... code below that works if its useful:

  1. In Focus event handler for the field:
  2. In OnChange event handler Run Script:
await js_field_data_changed.trigger({
  additionalScope: {
    fieldChanged: self,
    fieldIndex: i
  }
});
  1. Finally, add js query:
if (state_data_changed.value[fieldIndex][fieldChanged.id].clean!=fieldChanged.value) {
  //Field changed so write to dirty value.
  state_data_changed.setIn([fieldIndex,fieldChanged.id,"dirty"],fieldChanged.value);
} else {
  //Clear the state as nothing changed.
  state_data_changed.setIn([fieldIndex,fieldChanged.id],{});
}

Thanks again @Kabirdas, two saves in one week :wink:

Rob