What are suggested component+data source setups for showing nested arrays?

tl;dr:
Retool seems to work good OOTB for presenting requests that return [{key:value}] or [{key:{key: value}}]
but
Whenever request contains [{key:value, additional_objects:[{key: value},{key: value}]}] it either requires haxes or significantly degrades UX.

Hi!
In the past I’ve made Retool apps that only ever handled back-end responses containing arrays with nested objects that at worst had relation of 1:1 (each row/array’s object had exactly one child object) so displaying, styling and editing it wasn’t ever an issue due to me always being able to reference parent table (via currentRow/selectedRow, self or item) for everything. Aside from a known Retool bug on styling hidden vs disabled due to different contexts :salt:)

My usual flow was:

  • parent table, with togglable rows
  • child tables (inside of tabbed component,) for (every) nested object (using {{currentSourceRow.subarrayKey}})

However recently I needed to create apps that aimed to showcase more complex relations where each row contained an array of related objects and setup of:

  • parent table, with togglable rows
  • child tables (inside of tabbed component,) for (every) nested array of objects

and above setup doesn’t seem to cooperate well with Retool table component assumptions.

Examples:

1. 1:1 relation
(here everything works, since you can easily reference parent table and just drill down to a specific key-value)

BE response pattern:

[{"id": 1,"credentials": {"id": 111,"inserted_at": "2025-08-05T13:38:34",},"inserted_at": "2025-02-13T13:54:38","workspace_id": 6720,"project": {"id": 2119,"location": "United States","inserted_at": "2025-01-07T12:27:52"},"map": {"id": 8858,"state": "done","inserted_at": "2026-02-18T16:37:16"},"last_edited_at": "2026-02-18"},...]

Things I need:

  • Button components outside of child table that trigger per row actions (ex. getting map’s id for request using {{tableName.selectedRow.map.id}})
  • styling of parent rows based on child table rows

Things that work:

tl;dr - here all works smoothly. :smiley:

  • Button components outside of child table that trigger per row actions (ex. getting map’s id for request using {{tableName.selectedRow.map.id}})
  • styling of parent rows based on child table rows (using references like {{currentRow.project.location}} for row color)

2. 1:n relation with specific UX/styling requirements
(here’s where most of things aside from listing breaks, since using {{currentRow.arrayName}} as data source still makes table work in context of entire parent table instead of just currentRow data)

BE response pattern:

[{"members": [{"active": true,"inserted_at": "2023-07-03T11:33:11","email": "test1@gmail.com","role": "owner"},{"active": true,"inserted_at": "2023-11-06T14:33:15","email": "test2@gmail.com","role": "member"},...],"organization_id": 16202,"workspaces": [{"id": 11765,"name": null,"inserted_at": "2023-10-16T15:07:50"},{"id": 53175,"name": "Say my name","inserted_at": "2026-01-12T11:13:26"}],"organization_name": null},{"members": [{"active": true,"inserted_at": "2023-10-25T13:41:30","email": "test+20@gmail.com","role": "owner",},{"active": false,"inserted_at": "2024-08-05T14:09:24","email": "test1@gmail.com","role": "member",},...],"organization_id": 16658,"workspaces": [{"id": 12217,"name": null,"inserted_at": "2023-10-25T13:41:30"}],"organization_name": null},...]

Things I need:

  • multiselect on child table
  • styling of parent rows based on child table rows
  • styling of child rows based on child rows
  • styling custom column Buttons of child rows based on child row data
  • not running custom column Buttons event handlers depending on child row data
  • running per row actions
  • styling Button components next to child table based on child table row data

Things that don’t work:

  • multiselect on child table and batch actions always points to the same table (when referencing {{childTable.selectedRows}} will always point to one specific child table or be undefined depending on the exact place you place the reference, regardless of the true selected row/rows origin)
  • styling of child rows based on child rows (as despite child table having it’s own data source, styles are computed based on parent table’s complete array)
  • styling custom column Buttons of child rows based on child row data (again child having parent table’s complete array)
  • styling Button components next to child table based on child table row data (impossible to get this context?)

Things that work:

  • not running custom column Buttons event handlers depending on child row data (different available context that’s more local?)
  • running per row actions has same issues as multiselect, but can be haxed around using intermediary variables (so instead of request relying on table.data.key directly, I have a custom row button in child’s table, that grabs {{ currentRow.id }} via Event handler, saves it to app-wide variable, and then request uses {{ appWideVarName.value }}
  • styling of parent rows based on child table rows works only as a .filter/.map hax if you have within parent and child same exact data point to match it within JS snippets :confused:

3. 1:n relation with full CRUD requirement, including moving child objects between parents (which again breaks whenever you want to use 2 components for displaying purposes)

BE response pattern:

[{"id": 291,"name": "optimization","inserted_at": "2025-12-18T13:12:20.832Z""prompts": [{"id": 1293,"text": "What questions do you ask AI?","inserted_at": "2025-12-18T13:12:20.832Z"},...]},{"id": 299,"name": "apple","inserted_at": "2025-12-18T13:12:20.832Z""prompts": [{"id": 1299,"text": "Why Macbooks have stupid NAND memory integration designs?","inserted_at": "2025-12-18T13:12:20.832Z"},...]},...]

Things I need:

  • adding a new object to parent table/array (including cases where rows can have incomplete kvs)
  • adding a new object to child table/array (including cases where rows can have incomplete kvs)
  • editing values on parent table
  • editing values on child table
  • moving objects between child tables/arrays
  • deleting objects in child table
  • deleting objects in parent table

Things that don’t work:

(to attempt full CRUD without multiple different UX actions and multiple saves I attempted to make a app-variable that would have working copy of data I modify with UI elements, instead of using request.data and Retool’s built-in change sets)

  • adding a new object to parent table/array (not compatible with how Retool handles select editable columns, as appending new data to variable that’s table source triggers edit mode automatically?)
  • adding a new object to child table/array (-||-)
  • moving objects between child tables/arrays (not supported?)
  • deleting objects in child table (impossible if attempted within same move as add?)
  • deleting objects in parent table (-||-)

Things that work:

  • flattening BE output into cartesian object and then redoing object layout back up before sending update POST request (*maybe would work, but is very error prone in my PoC attempts)
  • editable JSON field (but that heavily depends on users requiring JSON editing knowledge and providing logic and syntax-wise a correct code on every edit :confused: )

I’ve been attempting to gracefully solve both 1:n nested arrays cases and I’m kinda lost, hopefully I’m missing something obvious - thank you in advance for any insights/suggestions. :folded_hands:

Cheers!

Hi @Piotr_Zubko,

Thank you for the well articulated post!

The root of most of your pain is that Retool's table component was designed for flat data. For 1:n cases, the most reliable mental model is to

Treat Retool as a thin UI shell, and keep your data logic entirely in transformers and JS variables, not in component references.

The more you lean on currentRow / selectedRow for nested structures, the more you'll hit Retool's context resolution bugs. A clean workingCopy variable + transformers that derive display slices from it is the most maintainable pattern for your use cases.

For Case 2, this is a known Retool quirk. The workaround is to use a separate query/transformer that isolates the child data before it ever reaches the table.

Unfortunately there is not 'out-of-the-box' handling for nested data structures, but the good news is that using JS queries and transformers, you can coerce the data into shapes that are better supported by the default components.

With custom components as a back up option where you need complex parent to child component interactions.

1 Like

Let me know if I can answer any additional questions, @Piotr_Zubko!

Hi there, @Darren!

Actually there is one, (though I’ve been purposefully avoiding asking it for now since I haven’t done IMHO enough research into it yet due to other tasks at hand):

Is it expected that all the issues I’ve mentioned above suddenly disappear if I specifically use ListView as parent element?

Then I can feely use item.whateverKey and each item list references correct one.
Things like scopes for Disabled property are also working as expected, and despite referencing a generic component that’s common between childs it always grabs the local one (vs 1st one in nested table approach).

Does that mean that ListView is (the only one?) that’s specifically coded for nested component approach and other components are kinda broken in this regard? :thinking:

The possibly other one that’s AFAIK working also correctly is TabbedContainer, but it doesn’t fulfill the same UI purpose.

The ListView component is definitely the most suited to your specific needs, although enabling expandable rows on the Table component should accomplish something very similar. It sounds like that is what you were trying previously, though. :thinking:

In essence, both of these constructs expose each element of an iterable to a sub-canvas as either item or currentRow, allowing you to treat that row as it's own distinct data source. I have seen issues with namespace collision in deeply nested structures, so that is definitely something to watch out for.

Based on my experience so far, I’m inclined to say that:

  • whatever exposes itemto children → works
  • whatever exposes anything table-like somethingRowto children → works for a child itself as data source and event management, but following attempts of styling child content do not (including cross-child styling)

More concrete example from my app at hand (made new components to ensure I didn’t leave any :wastebasket: behind for test purposes):

  1. Since item is not defined for the nested tables:

  1. I use currentRow and pick a specific object (one of many objects containing array of ojects) from parent:

  1. I add a custom Button column:

  2. But when I attempt to hide it, Hidden property for example has parent table context, not current, child table context: (using currentRow/currentSourceRow as item again is unavailable)

  3. Although Disabled specifically does allow me to do that (and seems to work on all instances of child tables):

And now’s the kicker and why I posted this thread:

  1. When attempting to use Buttons outside of child table, but still under parent table, they only work on 1st instance of the object, the rest are “dead”:

vs

(despite value changing on both in table and in Retools’s code value preview - it’s never reflected anywhere on actual app UI, regardless if we’re talking about styling said button, hiding it, disabling it, changing its copy, etc.)

Okay there's a lot to break down here. To start, I think most of the behavior that you're describing is largely explained by namespace collision and inconsistent linting.

When configuring the child table, currentRow and currentSourceRow are ambiguous references that may or may not be overwritten in certain contexts. The Hidden input, for example, is not dynamically evaluated for each individual row and thus I expect both currentRow and currentSourceRow to point to the parent. This is how it plays out in practice, as well.

The Disabled input, on the other hand, is dynamic and does generate mapped values based on each row, meaning I would expect both currentRow and currentSourceRow to point to the child. This is where things get weird, because the linter and code preview clearly think they refer to the parent. In practice, currentRow points to the parent and currentSourceRow points to the child... :sweat_smile: That's almost definitely a bug and, on top of that, the linter is plain wrong.

Last but not least, I'm not able to recreate the last scenario that you've described. Either that or I'm not understanding correctly. In my own testing, a button defined alongside the child table is able to retrieve context from said table without any issues.