T
TanStack6mo ago
deep-jade

Working with Row Selection State

So I have a hard time managing the Row Selection State manually:
onRowSelectionChange: (selection) => {
const selectedRows = selection();
const allRows = table.getRowModel().rows;

console.log(selectedRows, allRows);

// this will tell the table about the new selection
return selection
}
onRowSelectionChange: (selection) => {
const selectedRows = selection();
const allRows = table.getRowModel().rows;

console.log(selectedRows, allRows);

// this will tell the table about the new selection
return selection
}
selectedRows is really weird. When something is selected, it'll contain the row index and true, like { 1: true }. But every time something is deselected it will just be {}. I cannot rely on table.getSelectedRowModel().rows because it will only contain the selection at the time before updating the state. So it's always one behind. I need to get the selected rows and write them to context variable, so that it will always contain the currently selected selected rows, but it seems kinda impossible
14 Replies
deep-jade
deep-jadeOP6mo ago
synchronizing in the state just the index and true is just not enough for me, I need to know sync the actual data in there I need to access the selected row original data somewhere else, how do I do that? I've now tried merging things together:
onRowSelectionChange: (selection) => {
const newSelection = selection();
const allRows = table.getRowModel().rows;

// first, get the currently selected rows
const currentlySelected = table.getSelectedRowModel().rows

// then the newly selected ones
const newlySelected = Object.keys(newSelection)
.filter((key) => newSelection[key] === true) // Only `true` rows in `newSelection`
.map((key) => allRows[key]?.original);

// now, merge it all together
const updatedSelection = [...currentlySelected, ...newlySelected];

console.log(updatedSelection);

setProfilesSelection(selection)

// this will tell the table about the new selection
return selection
},
onRowSelectionChange: (selection) => {
const newSelection = selection();
const allRows = table.getRowModel().rows;

// first, get the currently selected rows
const currentlySelected = table.getSelectedRowModel().rows

// then the newly selected ones
const newlySelected = Object.keys(newSelection)
.filter((key) => newSelection[key] === true) // Only `true` rows in `newSelection`
.map((key) => allRows[key]?.original);

// now, merge it all together
const updatedSelection = [...currentlySelected, ...newlySelected];

console.log(updatedSelection);

setProfilesSelection(selection)

// this will tell the table about the new selection
return selection
},
But it fails with deselection. When something is deselected, selection() just returns an empty object with no other context at all this seems overly complicated for something that should be simple I cannot find any information on this on the docs. It looks like I cannot expose any other additional information from tanstack table about selection, this can't be true the example is also useless for me, because it doesn't access any information from the rows https://tanstack.com/table/latest/docs/framework/react/examples/row-selection
unwilling-turquoise
unwilling-turquoise5mo ago
Hi there - wondering what your use case is here - why do you need to access changing state so early, essentially before re-rendering. I would simply derive the current state following the React re-render that occurs when selecting/de-selecting a row. You should be able to see this if you simply console log out the data you want to track - following a selection/deselection this log will output the updated state. If instead you're trying to track whether something should be selectable or de-selectable and you intend to intercept that event, I'd recommend disabling the select field instead - avoiding the select/deselect event in the first place. Hope this makes sense and is what you're looking for.
harsh-harlequin
harsh-harlequin5mo ago
I have just come upon a similar issue, removing StrictMode fixed it
unwilling-turquoise
unwilling-turquoise5mo ago
Removing StrictMode from React will open your app up to a whole range of issues that are hard to debug.
deep-jade
deep-jadeOP5mo ago
I need to keep track of the selected rows because I'm doing something with it later. However, I realised that I can just get the currently selected elements by writing
const selectedRows = table.getSelectedRowModel().rows
const selectedRows = table.getSelectedRowModel().rows
Somewhere within my component. So I don't need to hook into onRowSelectionChange at all. I am however keeping only the selected row's original data stored in a paralell state variable with an effect.
useEffect(() => {
// store only the originally passed data
setProfilesSelection(selectedRows.map((row) => row.original))
}, [selectedRows, setProfilesSelection])
useEffect(() => {
// store only the originally passed data
setProfilesSelection(selectedRows.map((row) => row.original))
}, [selectedRows, setProfilesSelection])
unwilling-turquoise
unwilling-turquoise5mo ago
Bingo for the first bit. Glad you got it sorted. For the second, you’re trying to share state between components? That’s what React Context is for. There are other options but useEffect is not one of them.
deep-jade
deep-jadeOP5mo ago
@aniallator8 setProfilesSelection comes from a context. I am using context. I am useeffect to observe changes in selectedRows. All changes get written to the context.
unwilling-turquoise
unwilling-turquoise5mo ago
OK - I get ya. I understand why you're using the useEffect, but I'm not sure why you need to keep track of the selectedRows elsewhere. If there was a user event that triggers this then I'd recommend updating your context within that event handler. In either case, try this to update the rowSelection state - this goes back to the internal Table method for updating row selection state - note that the updated rowSelection contains the full state, not just the newly selected/deselected row. So there's no need to persist the previous state, just overwrite it.
onRowSelectionChange: (selection) => {
setRowSelection((prev) => {
const updatedRowSelection =
typeof selection === 'function' ? selection(prev) : selection;
console.log({ updatedRowSelection });
// update your context here...

return {
...updatedRowSelection,
};
});
},
onRowSelectionChange: (selection) => {
setRowSelection((prev) => {
const updatedRowSelection =
typeof selection === 'function' ? selection(prev) : selection;
console.log({ updatedRowSelection });
// update your context here...

return {
...updatedRowSelection,
};
});
},
Remember to remove the useEffect. No point adding potential bugs when Tanstack Table does give you the ability to override the default methods. Hope this helps.
absent-sapphire
absent-sapphire5mo ago
I did a feature to manage row selection with tanstack
...
createTable: (table): void => {
table.toggleRowSelected = (rowId: string) => {
table.options.onRowSelectionChange?.(old => {
const newSelection = { ...old };
if (newSelection[rowId]) {
delete newSelection[rowId];
} else {
newSelection[rowId] = true;
}
return newSelection;
});
};

table.getIsRowSelected = (rowId: string) => {
return !!table.getState().rowSelection[rowId];
};
...
createTable: (table): void => {
table.toggleRowSelected = (rowId: string) => {
table.options.onRowSelectionChange?.(old => {
const newSelection = { ...old };
if (newSelection[rowId]) {
delete newSelection[rowId];
} else {
newSelection[rowId] = true;
}
return newSelection;
});
};

table.getIsRowSelected = (rowId: string) => {
return !!table.getState().rowSelection[rowId];
};
toggleRowSelected is called when clicking on a row and set the state with the row value. Full code of the feature is here https://github.com/fblettner/liberty-core/blob/main/src/forms/FormsTable/features/RowSelection.tsx
GitHub
liberty-core/src/forms/FormsTable/features/RowSelection.tsx at main...
Liberty Core is a reusable component library designed for React applications - fblettner/liberty-core
deep-jade
deep-jadeOP5mo ago
this will not give me the current selection, only the previous selection. I've solved this issue simply by writing:
const selectedRows = table.getSelectedRowModel().rows
const selectedRows = table.getSelectedRowModel().rows
I'm then observing changes in the variable and update a context that keeps track of them. Several different components must be able to access data from selected rows, so that seemed right:
useEffect(() => {
// store only the originally passed data
setProfilesSelection(selectedRows.map((row) => row.original))
}, [selectedRows, setProfilesSelection])
useEffect(() => {
// store only the originally passed data
setProfilesSelection(selectedRows.map((row) => row.original))
}, [selectedRows, setProfilesSelection])
This works and does what I need.
genetic-orange
genetic-orange4mo ago
My use case is a bit different. I'm handling server-side pagination manually, and this form is meant to help users filter and select from a paginated dataset. As users select items, I maintain a "shopping cart"-style popover button that tracks their selections. I can’t use table.getSelectedRowModel() because whenever the user filters or changes the page, the table re-renders with new rows, and previously selected ones are no longer part of the rendered model. If I were to sync with getSelectedRowModel() using useEffect, the cart would reset every time the page changes. The cart button has access to the table instance but lives outside the useTable setup. Ideally, I’d be able to register an onSelect listener on the table instance so that whenever a row is selected or deselected, I could capture that event and reference the current page’s data to retrieve the full data model. From an outsider’s perspective, it feels unnecessarily difficult to access full row data on selection—as if the library is working against the idea of external control.
unwilling-turquoise
unwilling-turquoise4mo ago
Ah. That first issue you hit where the table re-renders: the library allows you to track via a custom id. You just need to use that option when setting up your table instance. Now whenever the table updates, your id is persisted in the table data.
unwilling-turquoise
unwilling-turquoise4mo ago
Row Selection Guide | TanStack Table Docs
Examples Want to skip to the implementation? Check out these examples: API Row Selection Guide The row selection feature keeps track of which rows are selected and allows you to toggle the selection o...
deep-jade
deep-jadeOP4mo ago
I am also still struggling with this. I have that right now:
const selectedRows = table.getSelectedRowModel().rows
const selectedProfiles = useMemo(() => selectedRows.map(row => row.original), [selectedRows])
setProfilesSelection(selectedProfiles)
const selectedRows = table.getSelectedRowModel().rows
const selectedProfiles = useMemo(() => selectedRows.map(row => row.original), [selectedRows])
setProfilesSelection(selectedProfiles)
This works but it also the culprit in a rerendering issue. I don't see how else I am supposed to do this though. I need this information outside the table itself. For example, rows are selected and then a modal opens and the modal says "N profiles selected", an action is applied to the selected rows, etc. and this is all happening in other components of course, so I am storing the selection state in a context but it causes rerenderings

Did you find this page helpful?