Create custom list views

Learn how to build list views that dynamically repeat using your data.

The List View component allows you to create repeatable rows of data whose look and feel are defined by you.

The List View component is similar to a Container in that you can nest other components inside of it. You control the number of rows, which determines the number of times a component appears in a list view.

🚧

Table components and modules do not support nesting within a List View.

Demo

Try out the app to see list views in action (buttons are disabled). The app incorporates query data to display a list of orders, and buttons to request a return.

The demo uses data generated from Retool's API Generator. You can generate data yourself to follow along by clicking Generate API and saving the endpoint URL.

Display query results in a list view

List View components are empty when you first drag them onto the canvas. The default number of rows is three, so when you add a component like Button to a list view, the button repeats three times.

Default behavior for row length in list views

You can set the number of rows in a list view dynamically to match the number of rows returned in a query. This ensures that only the exact number of list rows are used.

Set the number of rows

Setting the number of rows dynamically ensures your list view is the correct size, even if you don't know the size of your result set.

For example, to retrieve the number of rows to show in your list view, use the generated API to retrieve all orders.

  1. Create a query using a RESTQuery resource and name the query getOrders.
  2. Select the GET Action type and enter the URL of the endpoint you generated.
  3. Click Run to confirm the query ran successfully.

In the Inspector under Basic, you can now set {{ getOrders.data.length }} in the Number of rows field.

Set number of rows in list view based on query results

This adds more rows to the list view, but the same data is shown in each row. If you inspect a row, you can see the Text values are static. Similar to setting the number of rows, you can use query data and the i variable to dynamically set component properties like Text.

The i variable

The i variable allows you to reference the index of an item within a List View component. This allows you to render and access unique values for each row.

In this example, the data returned in getOrders includes the product SKU, customer email, and return status. You can use the i variable to access individual items within getOrders.data.

To show the customer email for each product, add a Text component to the list view and update its Value to {{ getOrders.data[i].customer }}. The same applies for the product SKU, using getOrders.data[i].product_id.

Access row data by indexing into getOrders.data[i]

You can also use the i variable in queries. For example, you might want to add a button that allows users to mark orders with a requested return. To do this, write a query that updates the record, setting return_requested, using the i variable to reference the product's ID.

You can then add an event handler to trigger the query when the button is clicked. When a component inside a list view triggers a query, the value of i is substituted with the row index of that component. In this example, that's the index of the Button component.

Create requestReturn API call to update individual orders

Nested List Views

📘

Nested List Views are available on Retool Cloud and Self-hosted Retool v2.98.2 and later.

You can nest List Views to dynamically generate UI elements based on multidimensional arrays and tree-like data structures—for example, org charts or threaded comments. Nested List Views support up to three levels of depth and can also reference child components using componentName.value.

The following demo app shows one level of nested List Views to represent an org chart. It uses a two-dimensional array, which consists of a list of teams, and within each team, its name and a list of team members. You can download and import the app as JSON to learn how it's built.

The transformedJS and transformedSql queries convert sampleData into an object you can use in a nested List View.

return {
    name: ["Bob", "Kate", "Sarah", "Ali", "Joe", "Lisa", "Lenny"],
    team: [
        "Engineering",
        "Engineering",
        "Marketing",
        "Sales",
        "Engineering",
        "Sales",
        "Marketing",
    ],
};
const aggregatedMembers = sampleData.data.reduce((aggregator, row) => {
  const nameArray = aggregator[row.team] ?? [];
  aggregator[row.team] = [...nameArray, row.name];
  return aggregator;
}, {});

return Object.entries(aggregatedMembers).map(([team, members]) => ({
  team,
  members,
}));
select team, array(name) as members from {{sampleData.data}} group by team

In nested List Views, ri is an array of indexes for each nested level. For example, in the demo app:

  • ri[0] contains indexes for the list of team names
  • ri[1] contains indexes for the list of team members

To access or iterate over data, use the ri variable as an index. In the example app, team members are shown dynamically using transformedData.data[ri[0]].members[ri[1]]] as the value of the team member text.

You can also use i to refer to the last element, or the deepest level, in ri. If you nest List Views to three levels, i is equivalent to ri[2].

To access a component within a nested List View, you can reference componentName.value. To access a component outside of the List View, you can pass a specific index—for example, textInput[1][2] accesses the third text input in the second nested List View. Providing indexes is less performant than using componentName.value.

Access nested List View items within queries

You can reference ri and i within a query if it is triggered by a component within a nested List View. For example, if you configure an event handler on a Button component within a nested List View to trigger the query, the button's ri and i values will be available to that query.