Temporarily state value is not updated in an expected way

I've been bitten several times by bugs caused by the fact that calling setValue of a temporary state does not immediately update the value. I.e. if you get the value immediately after setting it, it will be the old value.

Even if you consider the value is set asynchronously and get it with setTimeout(..., 0) the behaviour is the same. I also noticed that setValue returns an object with a then property. This made me think that I can just await it, but this changes nothing.

How to reproduce:

  • Create a new app with a temporary state called state with an initial value of 1.
  • Add the following JS query:
    const newValue = state.value + 1
    await state.setValue(newValue) 
    
    utils.showNotification({ 
      title: 'Results', 
      description: `set value: ${newValue}; actual value: ${state.value}`, 
    })
    
    setTimeout(() => {
      utils.showNotification({ 
        title: 'Results after timeout', 
        description: `set value: ${newValue}; actual value: ${state.value}`, 
      })
    }, 0)
    

In theory the set value and actual value should be the same. In reality, actual value is always one step behind in both reads -- the one immediately after the value is set and the async one.

At what point can we be sure that reading the value of a temporary state will return the latest one that was set?

3 Likes

Hi @vangelov !

As we discussed in our live chat, I've gone ahead and filed this as a bug internally! Once there's an update, I'll update this thread!

2 Likes

I also ran into this issue.

Edit: Looks like checking the following box in the "Advanced" tab of the query editor fixes it for me:

4 Likes

Thanks guys! I have not noticed this option for some reason.

Thanks for posting about this issue @vangelov
And Thanks for the solution @dschnurr - It helped me when I was going a bit crazy due to the way Temporary State was behaving.

1 Like

What about when running a script directly from an event handler? As @vangelov mentioned, await does nothing.

And I cannot toggle "Keep variable references inside the query in sync with your app." because it doesn't exist for these lightweight scripts. Even an absurdly long timeout delay won't have an effect; the value seems fully frozen by the context:

As it stands, temporary state vars in Retool seem pretty fraught — why would I not want to have variables' new values available virtually immediately after setting them in the vast majority of contexts? And why should I have to dig for a mostly undocumented, opt-in (in the full JS Query) to keep future references in sync?

If someone wanted to preserve the old value, pre-setValue, why not assign it to a const/var in the script itself and have sync'd references by default?

@DanBlonc Is there any update on the internal ticket filed ~8 months ago?

1 Like

I tried to do a workaround by using local storage, but it behaves the same /:
@DanBlonc please help :pray:

Hey folks!

Keeping variable references in sync actually comes with performance losses so it's recommended to only use the option when necessary, that's part of why it's turned off by default as well. The setting could certainly use more documentation! A number of people have expressed that keeping variables in sync is the expected behavior for them and it makes sense for that to be the case.

At the moment, though, the fix I'm aware of for the original issue is using the "Keep variable references inside the query in sync with your app" option.

@dguzzo and @Alex_Zak, would you mind explaining your use cases a bit more? Does using a JS query event handler with the setting turned on work?

Wow. I want that 4 hours of my life back and some head bandages please! Not the side effect I was expecting, also tried awaits and timers. Yuck. Checkbox worked for me. Thanks everyone. You are obviously doing a lot in editing mode. Maybe check to see if a write/read pattern is detected and warn people? I can't imagine any scenario that this behaviour is desirable other than speed like was mentioned.

I just got bit by this too. I understand that syncing state between the local browser and the cloud is a heavyweight option thus keeping the default be off seems justified. However that does nothing for a common use case which in my case is:

Nothing is being done inside "queries" so their properties are not relevant.

A button has a stack of click handlers and they run in parallel.

I need to do something when they ALL complete so I made up a temporary state variable to act as a counter. When it hits zero I want to trigger an action.

Well as you guessed, it never hits zero. In fact it steps from 3 to 2 and is stuck there.

So all this behavior is expected and "by design" but we need a way to bypass it sometimes. This needs to be a property of a temporary state object, not every object that wants to read an accurate value.

In some programming languages you can mark a variable as being "volatile" which tells the compiler it needs to ignore any cached copy and go check with the authoritative state every access; in the other direction writes must go to the authoritative state and bypass any cache.

Yes "volatile" will make a few lines of code slower but having a keyword like volatile next to the variable's declaration is a fine way to let people know this one is special and remind people that variables which lack that keyword may be eventually consistent.

Hey @Kabirdas
Where do I set this options? :pray:
Thanks :upside_down_face::cherry_blossom:

ok, nm I found it... it's at the very bottom of the advanced settings of a js query.
is there away to use this for button click events etc? :pray:

And I'd love to share, but it was so long ago I don't even remember what it was :sweat_smile:
I'll try to make sure to add more details next time :pray:

Hey folks, thanks for the suggestions here! I've passed them along to the dev team. Hopefully we can make this flow easier!

At the moment there isn't a way to turn it on for script handlers, but wherever you're using a script handler you should be able to just trigger a JS query, either via a query handler or by calling jsQuery.trigger({additionalScope: {...}}) in your script. Let me know if there's something I'm missing, i.e. if there's a particular use case script handlers cover that JS queries don't!