Event handlers update: triggeredById broken / works differently now?

Something must have happened with the recent update, as we had a tool working that has stopped.

Summary: Two different buttons (call them "button1" and "button2") trigger two separate queries ("query1" and "query2"), and either of those queries trigger a 3rd query ("queryFinal") and it is this third final query's results that are tied to a table for results population.

So, before the update, we might use this flow:

button1 -> (triggers) query1 -> (after this query runs - on success) -> queryFinal

or this flow:

button2 -> (triggers) query2 -> (after this query runs - on success) -> queryFinal

and as you might expect, we have a table (call it "tblResults"), and the Data attribute for tblResults is: {{ queryFinal.data }}.

There's no magic in queryFinal, it is simply this:

let obj = eval(triggeredById);
return obj.data.data;

This has worked very well for the last few months!

Now, however, when we click button1 we get:

queryFinal: query1 is not defined.

And when we click button2 we get:

queryFinal: query2 is not defined.

I've confirmed that both queries 1 & 2 themselves each individually run and return data, as they always have. I just cannot successfully hand that returned data off to a third query using the triggeredById key.

Is there some other new syntax change has to be updated in order for triggeredById to successfully reference the calling query (and its subsequent data)?

Hey @Wrapmate

We did recently make a change which I believe resulted in this behavior that you are experiencing, as we no longer are passing the entire app scope into the JS queries.

One potential workaround would be to instead use the following for your queryFinal query:

return {
query1: query1.data,
query2: query2.data
}[triggeredById]
3 Likes

This worked! (once I returned to the table that is populated with the results, and updated the Data field from {{qryFinal.data}} to {{qryFinal.data.data}}

I'd love an explanation on why that works, if possible.

Huh, odd. I did not need to use data.data notation in my test app, I imagine this is a result of however your query1 and query2 are set up to return data.

The way that this solution works is because the JS query is run in a sandbox, Retool calculates which parts of the app data should be passed into that sandbox by looking for references. In this case we are referencing query1.data and query2.data, so those get passed into the sandbox. Using these values we construct an object whose keys are named the same as the possible values of triggrereById and the values are the corresponding data values.

Then we simply key into the object, to return the value associated with the key specified by triggeredById.

Let me know if you have any other questions!

1 Like

Thanks for the solution @mark and @Wrapmate. It helped my project!

2 Likes

Insane, I'm battling a few hours with this until I really sat down to research :stuck_out_tongue:

@mark After some time has passed here, do you know any "nicer" ways of using triggeredById to call other queries?

I'm trying to use it as the main error_logger for all my queries and keeping an up-to-date array of all the names will be a hassle :confused:

Anyone found a better way for this?

I've done good amount of research on this problem within Retool over the years- There is unfortunately not a nicer way of doing this at the moment programmatically given the performance update we had to implement above in this thread. Any app model properties (i.e. query1) will only be able to be evaluated inside of the JS sandbox if they are explicitly referenced in the JS code that is being run. This is to prevent us needing to pass in all of the data and properties from the entire app into the sandbox for every query execution or {{ }} tag evaluation. For arbitrary references to be available, we would also need to pass in potentially many MBs of query returns, filepicker data, etc.

Because of that limitation, even if you had a dynamic list of string references to try to eval() (the original approach here), they would not be able to resolve. We would need to build out a new solution to solve for this, like triggeredByPlugin.value that then adds the triggering plugin object to the sandbox if it is referenced

Thanks for all the info @alex-w,

yeah, I tried dynamically adding all queries I care about via eval() but noticed it didn't work, makes sense!

Yeah, for it to work at the moment you would need to do something self-defeating like this to get it added to the scope of the query:
\

let arbitraryReference = [query1, query2]

//with triggerdById as 'query1'
let obj = eval(triggeredById);
return obj.data;

Which is less secure and not a space saver compared to:

let queries = [query1, query2]

//with triggerdById as 'query1'
let obj = queries[triggeredById];
return obj.data;
1 Like