Custom Component model cache bug

There seems to be a bug in Custom Components where the model values seem to be cached when being referenced in a Query (or a Run Script).

Scenario: have a retool.subscribe in the JS of the Custom Component which updates the model when something changes. And then try to update a state variable in the Retool app using the updated model. The state variable retains the old value instead of being updated with the new model value.

There seems to be some underlying (event loop?) caching that is causing the issue; the bug occurs whether or not Keep variable references inside the query in sync with your app. is enabled or disabled.

The Update Model and Fooo calls the following query:

customComponent3.updateModel({state: new Date().getTime()}); //this triggers the retool.subscribe method in the Custom Component

Link to JSON of the app

Hi @Tanker

By looking at your code, it looks like you are not updating the model on every Quill edit, that should be the correct way.

So, when you press the button you can update Fooo and do whatever you want with the updated model.

Hope this help

Thanks @abusedmedia appreciate the reply. Unfortunately that's not the issue; that if statement in the JS is an artifact of my original code - the issue occurs even without it.

As seen in the text below the editor, the model.body will always update with the new value (the code for that is: model.body is {{customComponent3.model.body}}). So the issue isn't in the JS - it's somewhere else.

Also, I can't use the actual Quill text-change event for other technical reasons. (Basically circular dependency issues; long story.)

If you check in the State panel, the model of your custom component, you'll see that is not updating while you write in Quill editor.

You're updating it (by means of subscribe mechanism) on pressing the Button, that is also triggering the Query.

So the button press:

  • trigger the query
  • update the model you're using in the query

But the query' trigger comes before the model update due the back and forth of the Retool-Component communication.

Hope this help

The flow of the app is this:

  1. Write something in the editor
  2. Click the button to trigger a model update (of model.state, which is just a dumb name for a property that contains a timestamp), in order for the subscribe event listener in the Component JS to receive an event.
  3. In the component JS, update the body property of the model with the editor's contents. (You can confirm this is successful in the State panel; additionally you can see the value change under the editor: model.body is new value)
  4. Back in the query, try to update the value of fooo variable with the supposedly updated contents of model.body (which we confirmed was updated!). This fails.

To put it in even more stark terms, if I wrap the second line of the query in a setTimeout and wait e.g. 10 seconds, it still fails. So it's not a race condition issue.

setTimeout(() => fooo.setValue(customComponent3.model.body), 10000);

I followed your instructions and after running the Query manually, both in the State panel and in the text, the fooo variable is correctly updated, containing the model value.

Here's a demo. You did this and it's not happening for you? Which browser?

Screen Recording 2023-11-02 at 3.11.46 AM (1)

You are trying to update the model and then set a variable in the same query. That's the problem.

The model requires more time to be updated because there's a roundtrip.
So the variable' set happens before the new model value.
So you get always the previous value.

And even if you use a timeout, that's a closure function that uses the actual value and not the value the will be after 10 sec.

Gotcha - I guess that makes sense. (Totally forgot about JS closures, async/await has spoiled me. Though looking at setTimeout documentation seems to indicate the a global variable (e.g. fooo wouldn't be closed over. Still seems like a bug to me.)

Seems like there's no solution to do what I'm trying to do. Even placing each of these in separate Click handlers has the same issue:

  1. Run Script: customComponent3.updateModel({state: new Date().getTime()});
  2. Run Script: fooo.setValue(customComponent3.model.body);

Retool executes js on a separated worker that actually clones the object states.
You cannot exploit object referencing in an async execution like you'd do in a regular js file.
It's not clear what you're achieving though.
Maybe there's a better way.

Ok so I made a change that the model.body will be automatically update with the text-change event from within the Quill.js editor. But I'm still experiencing the issue
Link to updated app JSON

My goal is to have 2 way binding - between a state variable (fooo) and model.body. I'm specifically using fooo as an array because in my real app I'm trying to have a ListView of CustomComponents where each of the CustomComponents.model.bodys are bound with the fooo list.

This is the RunScript; (in my real app I do some logic by temp.push to append an item to fooo.)

fooo.setIn([0], customComponent3.model.body);

let temp = [...fooo.value];
  //  temp.push("appended"); //for example

The issue is fooo.setIn([0], customComponent3.model.body) - if I do something like fooo.setIn([0], "my hard coded value") then there's no problem. But for some reason there some strange caching going on.

Screen Recording 2023-11-02 at 1.53.05 PM

Hi @Tanker

If you change the component model to {body: "initial" }, meaning, you remove the circular dependency, everything works.

Hope this help

Doesn't work for me. The value of fooo[0] is still wrong.

(Aside for that I do want the "circular dependency" - which I handle in the JS - because I'm trying to get 2-way binding. Ie when fooo[0] is updated the model.body will automatically update too.)

@Tanker Hi there, I'm wondering if you were able to resolve this. I tested your app example and I can see the issue with using setValue() in your JS code to programmatically update the value of fooo.

However, if you assign the value of fooo using a reference to customComponent3.model.body in double curly braces it does work as expected:

Kapture 2023-11-28 at 17.30.58

Would something like this work for you? Let me know if you have any questions.

Thanks for the reply, but this wouldn't work for my use case, I specifically need to use setValue.

I ended up figuring out that it's related to the event loop, and I needed to wrap everything in promises and .then (for some reason await wouldn't work properly).

@Tanker Thanks for following up and glad to hear you were able to get it to work. I will close this out for now.