svelte-headless-table
svelte-headless-table copied to clipboard
Race condition between the `groupBy` plugin and svelte rerender
There seems to be somewhat of a race condition (not sure it's the right term, but it illustrates the problem) between the groupBy
plugin and svelte rerender (because of the {#each}
block maybe?).
Here's the actual problem :
When I trigger a group
operation (hCellProps.group.toggle()
) on some columns (not all, seemingly[^1]), some $originalRows
are displayed in place of the $rows
(or $pageRows
) that should show.
[^1]: When there are around 10 groups or more, it tends to happen frequently.
Notes and observations :
- The same
originalRows
are showing each time (in my case, rows 5, 6, 8, 9, 10) - The grouping occurs correctly if the same column key is set in
initialGroupByIds
- Then if I ungroup / regroup, chaos starts in the table
- But the
$pageRows
store is dumped with correct columns (see<pre>
tags in the bottom)
A peek of my current code : (I'll try to put a reproduction somewhere when I have more time)
<script>
// ...
const table = createTable(readable(data), {
select: TablePlugins.addSelectedRows(),
columnFilter: TablePlugins.addColumnFilters(),
tableFilter: TablePlugins.addTableFilter({
fn: ({ filterValue, value }) => {
return value.toLowerCase().includes(filterValue.toLowerCase());
}
}),
sort: TablePlugins.addSortBy({
isMultiSortEvent: isShiftClick,
initialSortKeys: [{ id: 'createdAt', order: 'desc' }]
}),
group: TablePlugins.addGroupBy({
// disableMultiGroup: true
isMultiGroupEvent: isShiftClick
// initialGroupByIds: ['statutActuelStr']
}),
expanded: TablePlugins.addExpandedRows({}),
resize: TablePlugins.addResizedColumns(),
grid: TablePlugins.addGridLayout(),
pagination: TablePlugins.addPagination({
initialPageSize: 100
}),
});
let columns = table.createColumns({
table.display({ // this column shows expand button if rows are grouped
id: 'expand',
header: (headerCell, { pluginStates }) =>
createRender(RowsUngrouper, {
groupByIds: pluginStates.group.groupByIds
}),
cell: ({ row }, { pluginStates }) => {
const { canExpand, isExpanded } =
pluginStates.expanded.getRowState(row);
return createRender(RowExpander, { isExpanded, canExpand });
},
plugins: {
resize: {
disable: true,
initialWidth: CELL_WIDTH.MIN
},
}
}),
// ...
table.column({ // this column is the one I group by
header: 'Statut',
accessor: 'statutActuelStr',
cell: statutCell,
plugins: {
group: {
getGroupOn: (v) => (v === null ? 'Indéterminé' : v)
},
columnFilter: txtColFilter,
resize: {
initialWidth: CELL_WIDTH.LARGE,
minWidth: CELL_WIDTH.MED
}
}
}),
// ...
});
const tableViewModel = table.createViewModel(columns, {
rowDataId: (item, _index) => item.id.toString()
});
</script>
<table>
<thead>
...
</thead>
<tbody>
{#each $pageRow as pageRow, i (pageRow.id)}
<Subscribe
pageRowAttrs={pageRow.attrs()}
let:pageRowAttrs
pageRowProps={pageRow.props()}
let:pageRowProps
>
<tr {...pageRowAttrs} data-row-index={i} on:click={() => rowClicked(pageRow)}>
{#each pageRow.cells as cell (cell.id)}
<TBodyCell {cell} />
{/each}
</tr>
</Subscribe>
{/each}
</tbody>
</table>
<!-- for debugging : here rows seems to be correctly shown after a rerender -->
<section>
<details>
<summary>$originalRows</summary>
<pre>{JSON.stringify(
$originalRows.map(
(r, i) =>
`(${String(i).padStart(3, ' ')}) id : ${r.id.padStart(6, ' ')}, nom : ${r.cells.find((c) => c.id == 'prenom')?.value ?? 'Inconnu'}`
),
getCircularReplacer(),
2
)}</pre>
</details>
<details>
<summary>$rows</summary>
<pre>{JSON.stringify(
$rows.map(
(r, i) =>
`(${String(i).padStart(3, ' ')}) id : ${r.id.padStart(6, ' ')}, nom : ${r.cells.find((c) => c.id == 'prenom')?.value ?? 'Inconnu'}`
),
getCircularReplacer(),
2
)}</pre>
</details>
<details>
<summary>$pageRows</summary>
<pre>{JSON.stringify(
$pageRows.map(
(r, i) =>
`(${String(i).padStart(3, ' ')}) id : ${r.id.padStart(6, ' ')}, nom : ${r.cells.find((c) => c.id == 'prenom')?.value ?? 'Inconnu'}, cells :
${r.cells.map((c) => c.value).join()}`
),
getCircularReplacer(),
2
)}</pre>
</details>
</section>
Dirty fix :
<script>
const getRows = async (current) => await new Promise((res) => setTimeout(() => res(current), 0));
</script>
<tbody {...$tableBodyAttrs}>
{#await getRows($pageRows)}
Loading...
{:then finalRows}
{#each finalRows as row, i (row.id)}
...
{/each}
{/await}
<tbody/>
Versions :
{
"@sveltejs/kit": "^2.5.4",
"svelte": "^4.2.12",
"svelte-headless-table": "^0.18.2"
}
Screenshots :