Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/ember-table/src/FlexRender.gts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Component from '@glimmer/component'
import { cached } from '@glimmer/tracking'
import { FlexRenderComponentConfig } from './flex-render-helpers.ts'
import { flexRender } from '@tanstack/table-core/flex-render'
import type {
Expand Down Expand Up @@ -74,6 +75,7 @@ export class FlexRenderCell<
TData extends RowData,
TValue extends CellData = CellData,
> extends Component<FlexRenderCellSignature<TFeatures, TData, TValue>> {
@cached
get result(): CellRenderResult<TFeatures, TData, TValue> {
const cell = this.args.cell
return flexRender(
Expand Down Expand Up @@ -142,6 +144,7 @@ export class FlexRenderHeader<
TData extends RowData,
TValue extends CellData = CellData,
> extends Component<FlexRenderHeaderSignature<TFeatures, TData, TValue>> {
@cached
get result(): HeaderRenderResult<TFeatures, TData, TValue> {
const header = this.args.header
if (header.isPlaceholder) return null
Expand Down Expand Up @@ -213,6 +216,7 @@ export class FlexRenderFooter<
TData extends RowData,
TValue extends CellData = CellData,
> extends Component<FlexRenderFooterSignature<TFeatures, TData, TValue>> {
@cached
get result(): HeaderRenderResult<TFeatures, TData, TValue> {
const footer = this.args.footer
if (footer.isPlaceholder) return null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { module, test } from 'qunit'
import { render } from '@ember/test-helpers'
import { setupRenderingTest } from 'ember-qunit'
import Component from '@glimmer/component'
import { tracked } from '@glimmer/tracking'
import {
useTable,
FlexRenderCell,
FlexRenderHeader,
stockFeatures,
type Row,
type Cell,
type ColumnDef,
} from '#src/index.ts'

type Person = { id: string; firstName: string }

const getVisibleCells = (
row: Row<typeof stockFeatures, Person>,
): Array<Cell<typeof stockFeatures, Person>> => row.getVisibleCells()

// The result of a columnDef `cell`/`header` render function is read by the
// FlexRender template more than once (branch check + content). Those reads
// must not re-invoke the user's render function: it should run exactly once
// per cell per render pass.
module('Integration | FlexRender | invocation count', function (hooks) {
setupRenderingTest(hooks)

test('cell and header render functions run once per render pass', async (assert) => {
const calls = { cell: 0, header: 0 }

const columns: ColumnDef<typeof stockFeatures, Person, unknown>[] = [
{
id: 'firstName',
accessorFn: (row: Person) => row.firstName,
header: () => {
calls.header++
return 'First name'
},
cell: (info) => {
calls.cell++
return info.getValue<string>()
},
},
]

class TableComponent extends Component {
@tracked data: Array<Person> = [
{ id: '1', firstName: 'Alice' },
{ id: '2', firstName: 'Bob' },
]

table = useTable(() => ({
data: this.data,
features: stockFeatures,
columns,
getRowId: (row: Person) => row.id,
}))

get rows() {
return this.table.getRowModel().rows
}

get headers() {
return this.table.getHeaderGroups()
}

<template>
<table>
<thead>
{{#each this.headers as |headerGroup|}}
<tr>
{{#each headerGroup.headers as |header|}}
<th><FlexRenderHeader @header={{header}} /></th>
{{/each}}
</tr>
{{/each}}
</thead>
<tbody>
{{#each this.rows key='id' as |row|}}
<tr data-test-row={{row.id}}>
{{#each (getVisibleCells row) key='id' as |cell|}}
<td><FlexRenderCell @cell={{cell}} /></td>
{{/each}}
</tr>
{{/each}}
</tbody>
</table>
</template>
}

await render(<template><TableComponent /></template>)

assert.dom('td').exists({ count: 2 })
assert.dom('[data-test-row="1"] td').hasText('Alice')
assert.strictEqual(
calls.cell,
2,
'cell fn ran exactly once per cell on initial render',
)
assert.strictEqual(
calls.header,
1,
'header fn ran exactly once on initial render',
)
})
})
Loading