Build custom views using repeatable components
Learn how to display and interact with your data using repeatable components.
A repeatable component maps an array of data to repeating instances. Visually, a repeatable component is made up of a series of instances—each instance contains one or multiple components. Assemble components to display within the first instance, and configure them to reference your data source. A series of instances appears, each one corresponding to one item in your array.
List View is the main repeatable component for apps. The initial state of this component is a vertically-scrolling list of instances, each of which include a Container and are connected to demo data.
The following components are presets of List View:
- Container List View, which is a Container that includes a List View component.
- Grid View, which is a preset of List View with Layout Type set to Grid.
In the following demo, the underlying configuration for displaying user names, emails, avatars, and other charateristics is only done once. The configuration is then repeated for every user in the data source, resulting in the grid-like view.
Features
Key features of repeatable components include:
- One-time configuration of repeatable components.
- Dynamic loading of source data.
- The ability to add several nested components in each instance.
- Configurable user interaction.
- Custom styles.
Specify content options
The Content section of the Inspector contains settings that control the content in the repeatable components, such as:
- The data source.
- The primary key.
Select the data
Add a List View component to the canvas and select a Data source. You can write JavaScript, pass an array, or use query data as your data source. The only requirement is that the data source is an array (primitive and object arrays both work).
Retool tries to set a primary key for List View components when you select a data source. If your app has multiple user input components, make sure the primary key is set correctly. This ensures your app state is correct after users make changes like adding or sorting data.
After selecting a data source, Retool seeds the component with containers for each item in your data source. Add components to the first container, and they're automatically propagated to the other containers. Retool sets some default values, but you use the item
and i
variables to display data in each component.
The item
variable
The item
variable corresponds to the value of an entry in your data source. It's equivalent to referencing listView1.data[i]
. You can use item
to access values in your data source, such as {{ item.id }}
or {{ item.value }}
.
The i
variable
The i
variable is a number that represents the index of an entry in your data source. Since a list view is a repeatable component, each repeated instance has a different index which you can use to render and access unique values.
i
is often used to iterate over a set of values and perform some kind of action. For example, you could iterate over an entire list view to update values, make API requests, send emails, etc. For example, this JavaScript transformer iterates over all the entries in listView1
, and returns each sales person with over 200 sales.
var salesPerson = {{ listView1.data }};
var highSales = salesPerson.filter(i => i.sales > 200);
return highSales;
Configure user interaction
The Interaction section of the Inspector contains settings that control user interaction. These settings are limited for repeatable components – to configure event handlers and other interactions, select the first Container and make your changes in the Inspector.
Aggregate form components
If you need to access the value of an input component within a repeatable component, toggle Enable instance values on. When enabled, the repeatable component attempts to evaluate all input values and make them available in the instanceValues
property.
instanceValues
is an array of objects for every instance of the component, where each object is a mapping of an input's formDataKey
to its value
. This also includes the primaryKey
of the list item.
For example, you can use a JavaScript transformer to iterate over a list of Number Input components and return the sum total:
const formData = {{ listViewName.instanceValues }}
const sum = formData.reduce((sum, data) => sum += data.numberInput1, 0)
return sum
Use resetInstanceValues()
or clearInstanceValues()
to reset or clear input values for a List View or Grid View component anywhere in the app.
Customize appearance
You can customize the presentation of your component in the Spacing and Appearance sections of the Inspector. The settings available in the Appearance section depend on the type of component you use.
All components have a Hidden setting, which you can dynamically control with a truthy value that evaluates to true
or false
. You can also control the hidden
property for a component using the .setHidden()
method from event handlers and JavaScript queries.
You can also create custom style rules using the Styles setting. Each component has different styles that you can configure, but common ones include colors, fonts, and text styles. Use app-level or organization-level themes to set these styles across all components.
You can customize the overall appearance of repeatable components. Select the component and modify appearance settings like height, layout type, etc.
Height configuration
Retool recommends using a fixed height for List View cmoponentss. However, you might use auto height when your dataset is less than 25 items or when fixed heights don't otherwise fit your use case. For the latter, Retool recommends using the auto height setting with server side pagination. This requires some additional code to function.
For example, you can use a JavaScript transformer and a Page Input component to return only a subset of values from your data source.
const currentPage = {{ pageInput1.value }} - 1
const pageSize = 5
const pageStart = currentPage * pageSize
const pageEnd = pageStart + pageSize
return {{filteredTableData.value}}.slice(pageStart, pageEnd)
After you set the List View's data source to the transformer value, you add a Page Input component. Page Input components include a Page count value that you can set manually, or you can write JavaScript to display the page count dynamically. For example, {{Math.ceil(filteredTableData.value.length / 5)}}
divides the number of items returned to calculate the page count where each page contains five items. See the demo below for a walkthrough.
Nest repeatable components
You can nest a repeatable component within another repeatable component. This nesting dynamically generates UI elements based on multidimensional arrays and tree-like data structures—for example, org charts or threaded comments. Nested repeatable components support up to three levels of depth and can also reference nested components using componentName.item
.
Access items via namespace
Each List View can access its items and those of its parent and child using namespaces. For example, imagine the following set of nested List Views:
listView1
listView2
listView3
In this example:
listView1
can access its item viaitem
orlistView1.item
.listView2
can access its item viaitem
orlistView2.item
. It can access its parent's item vialistView1.item
.listView3
can access its item viaitem
orlistView3.item
. It can access its ancestor's item vialistView1.item
andlistView2.item
.
Reference nested components
You can reference nested components from other nested components within the same repeatable instance (e.g., {{ componentName.value }}
). Nested components cannot be referenced outside of a given repeatable. If you need to reference the source data of a list item, you can reference the data
or instanceValues
of List View.
Demo nested List View
The following demo app uses two List View components. The first contains the teams: Engineering, Marketing, and Sales. The second is nested inside the first and contains the team members.
The transformedDataJS
and transformedDataSql
queries convert sampleData
into objects that you can use in a nested List View.
- Sample data
- Transformed with JS
- Transformed with SQL
Create a JavaScript query named sampleData
with the following:
return {
name: ["Bob", "Kate", "Sarah", "Ali", "Joe", "Lisa", "Lenny"],
team: [
"Engineering",
"Engineering",
"Marketing",
"Sales",
"Engineering",
"Sales",
"Marketing",
],
};
Create a JavaScript transformer named transformedDataJS
with the following:
function aggregateSampleData() {
const teams = _.uniq({{ sampleData.data.team }});
const aggregated = teams.map(team => ({
team,
members: {{ sampleData.data.name }}.filter(name =>
{{ sampleData.data.team }}[{{ sampleData.data.name }}.indexOf(name)] === team
)
}));
return aggregated;
}
return aggregateSampleData();
Create a Query JSON with SQL resource query named transformedDataSQL
with the following:
select team, array(name) as members from {{sampleData.data}} group by team
To configure the demo app, set the parent List View component to use the transformedDataJs
transformer as its data source. Set the Value of the container title to {{ item.team }}
, which displays the team names. Set the nested List View's data source to {{ item.members }}
, and set the value of the container title to {{ item }
, which displays the members of each team.
You could also transform the data with SQL instead. The demo shows an additional container view that uses transformedDataSQL
as the data source.
Click through the following Arcade to view the configuration process.