Extend your Retool Apps with JavaScript

Best Practices with JavaScript

Purpose of this tutorial

We’ll build additional complexity and logic on our Approval Dashboard use case by leveraging JavaScript transformers and queries. Our JavaScript examples showcase the differences between transformers and queries, and how you can leverage both to operationalize your applications with critical business logic.

Who is this for?

This tutorial is for builders who have started to create their first app on Retool and want to build additional complexity by leveraging JavaScript.

Prerequisites

If you haven’t checked it out already, here’s our first installment on how we built out the Approval Dashboard!

JavaScript Transformers

Transformers allow you to read and manipulate data from queries and components, but will not allow you to control components or trigger queries within a Retool application. Within transformers, you can leverage the {{ }} notation to reference components and queries in your application. References within the {{ }} are considered inputs to a transformer, and any update to an input will rerun the transformer.

Transformers will automatically run and be recomputed when any objects referenced by them change. Consequently, having many transformers in an application can impose a performance penalty (since they will all get re-evaluated on each input’s state change).

Having many transformers in an application can incur a performance penalty because of how Retool eagerly re-evaluates transformers. For example, a transformer that references some objects, and is itself referenced by some components, will re-evaluate when any of the objects it references change, with each re-evaluation causing all the components to be re-rendered. The number of re-evaluations and re-renders can compound quickly in a complex application with multiple transformers, which results in slow/unresponsive apps.

We recommend only using transformers to compute values that need to always be kept up to date in your application.

When to use JavaScript transformers

  1. Data manipulation: Change the results of one or multiple queries into a different format that you can reference through {{ transformer.value }}. Note, transformers will not allow you to control components or trigger queries, but instead should only be used for manipulation of data values.
  2. Helper functions: Reference the output of complex, multi-line JS logic in other places without writing transformations in-line. For example, current_ui will stay up-to-date and return the currently selected UI. In other components, simply reference current_ui.value rather than recomputing its logic.


JavaScript Queries

In addition to writing resource queries, you can also write JavaScript queries within Retool to create rich interactivity between components and queries. With JavaScript, you can write queries that compose arbitrary pieces of logic that trigger queries and manipulate components into a single function call creating greater customizability for your applications.

JavaScript queries differ from transformers in the following aspects:

  1. Unlike transformers, JavaScript queries have the ability to be manually triggered. By doing so, you have more intentional control over when and where a query will be running. When a query is set to run manually, you can use the “watched inputs” setting to have it behave the same as running automatically for those specific inputs.
  2. JavaScript queries can mutate application state by controlling components and triggering queries within an application. Consequently, JavaScript queries can effectively encapsulate data manipulation and application control flow (e.g. a single JavaScript query can reformat some user input data, pass it to a REST endpoint and then close a modal).
  3. Objects in Retool’s application state are referenceable within JavaScript queries without use of the {{ }} syntax.

When to use JavaScript queries

  1. Centralize complex logic: Instead of scattering complex logic throughout an app, you can consolidate critical app business logic within a single JavaScript query. This, in turn, will 1) make the logic flow cleaner and 2) make it easier to quality control. Check out the note in the JS queries sub-section.
  2. Conditionally trigger queries based on application state: A common use case is to build out decision logic within your app. You can leverage JavaScript queries to programmatically trigger based on certain input conditions (e.g. triggering notifications based on success of a query). Check out the “Creating Critical Business Logic with Bulk Update” sub-section.
  3. Create a custom API-like functionality: JavaScript queries allow you to compose an API of sorts that combines multiple queries together along with the ability to use additionalScope to pass down params for each query. If you have multiple functionalities to be achieved, you can roll them into a single JS query that takes in different parameters and passes them down to the underlying queries. The JS query can be called using “Run Script” with additionalScope used to specify inputs.

Explore Examples

Check out this app to interact with the below examples and download the code seamlessly to connect to the same resource endpoints!

Note
We are using the Retool API generator to create API endpoints, with example endpoints below you can add as resources on your Retool instance to interact with the app.

  1. pending_approvals example endpoint: https://retoolapi.dev/83m3xZ/data

  2. verified example endpoint: https://retoolapi.dev/6algue/data

JavaScript Transformers Examples

Data Manipulation using JS Transformers + Plotly Charts

A common UI/UX pattern is to slice and transform query data into a more visual format. In order to reshape our data, we’ll leverage JavaScript transformers.

For a CX agent in this example it would be helpful to have high level metrics of the requests they need to go through. So we created a chart that visualizes the status of all the pending requests a CX agent has to go through.

We’ll need to query the “pending_approvals” endpoint to fetch data for the graph visualization. However, the response won’t be in the format we need it to be, so that’s where Transformers come into play to manipulate the data to match the expected input for our charting library.

Now that we have a data format with labels and datasets we just need to connect this to our charting component.

Our Chart component uses Plotly, and can build a wide variety of different charts (check out Plotly’s documentation), for this example since we used a transformer to do the hard work utilizing the chart component is seamless. Now we can easily see pending requests by approval status.


JavaScript Queries Examples

Triggering Side Effects through Event Handlers

Event handlers provide a great method to extend interactivity between a component and the rest of the Retool ecosystem. So far, we have explored one common use case of utilizing event handlers to control a query; now we’ll take a look at another common use case while understanding best practices with event handlers.

In the last tutorial, we built out the ability to view a specific pending request’s metadata based on the Table’s selected row. We’ll extend this functionality with decision logic that will display a list of recommended actions a CX agent can take based on the approval status of a pending request.

We’ll need to use the Row Click event handler that performs the Control Component action. This will make the row click on the table to take the user to another component, in this example we’ll take the user to a separate tab in a tabbed container which highlights status specific actions a CX agent can take advantage of.

Additionally, we see again how we can leverage JS to control which tab of the tabbed container to show depending on the Approval Status.


Note

This logic can also be abstracted to be within a JS query as well. As a rule of thumb if the complexity of the application increases it can be easier to consolidate the query logic and dependencies in a JS query instead of through components and chained queries. Here’s a simple example below that demonstrates how to centralize complex logic.

if (!pending_table.selectedRow.data) return;
const issue = pending_table.selectedRow.data.approval_status;
switch(issue) {
  case 'Late Request':
    tabsRecommendedActions.setCurrentViewIndex(1)
    break;
  case 'In Review':
    tabsRecommendedActions.setCurrentViewIndex(0)
    break;
}

Creating Critical Business Logic with Bulk Update

In certain cases, event handlers aren’t robust enough for a greater level of complexity when queries begin to get chained. This is where scripting in Retool can come into play in order to consolidate queries and actions into one central location through JS queries.

For a CX agent, a common scenario we want to account for is accidentally approving several users that now need to be bulk removed from our approval table.

In order to do this, we can chain and programmatically trigger multiple queries within a single JS query to bulk update a resource.

const promises = approved_table.selectedRow.data.map((row) => {
    return delete_approved_users.trigger({
        additionalScope: {
            id: row.id,
        },
    });
});

return Promise.all(promises);

In the image above we are removing all the selected users in the Approved users table by triggering the delete_approved_users query. This example is just the first step in really adding complexity with scripting as users could combine multiple queries, JS functions like exporting as a PDF, and branching to achieve a complicated workflow all in a JS function if necessary.

4 Likes

This is a good write-up, especially after I have a race condition issue with JS transformer

I spent a hour debugging this and this diagram summarizes this issue

The race condition could happen when some query is triggered by input change (e.g. JS transformer) and some are triggered by events.

8 posts were split to a new topic: When is the best time to use transformers?