I'm trying to keep track of selected items by page.
Here is the codesandbox link: https://codesandbox.io/s/affectionate-forest-tx3309?file=/src/App.js
No matter what I tried, when I click the next page it triggers "onChangeRowsPerPage" first and removes all selected items. Any ideas on how can I solve this problem?
Thanks!
import { useEffect, useMemo, useState } from "react";
import axios from "axios";
import DataTable from "react-data-table-component";
import "./styles.css";
export default function App() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [totalRows, setTotalRows] = useState(0);
const [perPage, setPerPage] = useState(10);
const [currentPage, setCurrentPage] = useState(1);
const [selectedRows, setSelectedRows] = useState([]);
const fetchUsers = async (page) => {
setLoading(true);
const response = await axios.get(`https://reqres.in/api/users?page=${page}&per_page=${perPage}&delay=1`);
setData(response.data.data);
setTotalRows(response.data.total);
setLoading(false);
};
const handlePageChange = (page) => {
setCurrentPage(page);
fetchUsers(page);
};
const handlePerRowsChange = async (newPerPage, page) => {
setLoading(true);
const response = await axios.get(`https://reqres.in/api/users?page=${page}&per_page=${newPerPage}&delay=1`);
setData(response.data.data);
setPerPage(newPerPage);
setLoading(false);
};
const handleRowSelected = async (row) => {
console.log(row, currentPage);
// Check current page has selected items
const selectedPageRowIndex = selectedRows.findIndex((row) => row.page === currentPage);
// If there is no selected records for this page
if (selectedPageRowIndex === -1) {
const tmpRow = { ...row }; // Copy returned row object
tmpRow.page = currentPage; // Set page
setSelectedRows([...selectedRows, tmpRow]); // Update state
} else {
// If exist, update
console.log("Current page :", currentPage, " updating...");
const tmpSelectedPageRows = [...selectedRows]; // Copy state
tmpSelectedPageRows[selectedPageRowIndex].selectedRows = row.selectedRows; // Update selected rows
setSelectedRows(tmpSelectedPageRows); // Update state
}
};
useEffect(() => {
fetchUsers(1); // fetch page 1 of users
}, []); // eslint-disable-line react-hooks/exhaustive-deps
// Table Column Configuration
const columns = useMemo(() => [
{
name: "Avatar",
cell: (row) => (
<img height="30px" width="30px" alt={row.first_name} src={row.avatar} />
)
},
{
name: "First Name",
selector: (row) => row.first_name
},
{
name: "Last Name",
cell: (row) => row.last_name
},
{
name: "Email",
selector: (row) => row.email
}
]);
return (
<div className="App">
<h1>react-data-table-component</h1>
<p>with remote pagination + pre/selected rows</p>
{JSON.stringify(selectedRows, null, 2)}
<DataTable
title="Users"
columns={columns}
data={data}
progressPending={loading}
pagination
paginationServer
paginationTotalRows={totalRows}
onChangeRowsPerPage={handlePerRowsChange}
onChangePage={handlePageChange}
selectableRows
onSelectedRowsChange={handleRowSelected}
/>
</div>
);
}
I had to go into heavy workaround mode to do this, the component doesn't nicely support server side pagination with selectable rows.
Take a look:
https://codesandbox.io/s/flamboyant-tesla-22fbq7?file=/src/App.js
import { useCallback, useEffect, useMemo, useState } from "react";
import axios from "axios";
import DataTable from "react-data-table-component";
export default function App() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [totalRows, setTotalRows] = useState(10);
const [action] = useState({ fromUser: false }); //this is a way to have an instant-changing state
const [rowsPerPage, setRowsPerPage] = useState(4); //change to 10 after you remove paginationRowsPerPageOptions
const [currentPage, setCurrentPage] = useState(1);
const [selectedRowsPerPage, setSelectedRowsPerPage] = useState([]);
const fetchUsers = async (page, rowsPerPage) => {
setLoading(true);
const response = await axios.get(
`https://reqres.in/api/users?page=${page}&per_page=${rowsPerPage}&delay=1`
);
setData(response.data.data);
setTotalRows(response.data.total);
setLoading(false);
};
const handlePageChange = (page) => {
fetchUsers(page, rowsPerPage);
setCurrentPage(page);
};
const handleRowsPerPageChange = async (newRowsPerPage) => {
if (!data.length) return; //when the table is rendered for the first time, this would trigger, and we don't need to call fetchUsers again
fetchUsers(1, newRowsPerPage);
setRowsPerPage(newRowsPerPage);
setCurrentPage(1);
setSelectedRowsPerPage([]);
};
const handleOnSelectedRowsChange = useCallback(
({ selectedRows }) => {
if (!action.fromUser) return; //the component always trigger this with 0 selected rows when it renders a page, what would clear the selection
selectedRowsPerPage[currentPage] = selectedRows; //there is no way to tell if a row was DEselected, so I had to control the selected rows per page,
//the array would get an index to control each page
console.log(JSON.stringify(selectedRowsPerPage));
},
[currentPage, selectedRowsPerPage, action.fromUser]
);
const handleMouseEnter = () => {
action.fromUser = true; //this was the way I found to prevent the component to clear the selection on every page render,
//if the user is not with the mouse on a row, doesn't allow to change the selected rows
};
const handleMouseLeave = () => {
action.fromUser = false; //When the users moves the mouse out of a row, block the changes to the selected rows array (line 39)
};
const getAllSelectedRows = () => {
//if you need to get all of the selected rows in all pages, call this
const allSelected = [];
selectedRowsPerPage.forEach((selectedPerPage) => {
if (selectedPerPage) {
selectedPerPage.forEach((selectRow) => {
allSelected.push(selectRow);
});
}
});
return allSelected;
};
//this applies the selected rows on the page renders, it checks if the id of the row exists in the array
const handleApplySelectedRows = (row) =>
selectedRowsPerPage[currentPage]?.filter(
(selectedRow) => selectedRow.id === row.id
).length > 0;
useEffect(() => {
//it's controlled by page, for example selectedRowsPerPage[1] contains the selected rows for page 1 and so forth...
const preSelectedItems = [
//index 0 is always null, because pages start at 1
null,
//index 1 in the selected for fir the first page:
[
{
id: 3,
email: "emma.wong@reqres.in",
first_name: "Emma",
last_name: "Wong",
avatar: "https://reqres.in/img/faces/3-image.jpg"
},
{
id: 1,
email: "george.bluth@reqres.in",
first_name: "George",
last_name: "Bluth",
avatar: "https://reqres.in/img/faces/1-image.jpg"
}
],
//index 2 null for example, because nothing is selected for page 2
null,
//index 3, one selected for page 3
[
{
id: 11,
email: "george.edwards@reqres.in",
first_name: "George",
last_name: "Edwards",
avatar: "https://reqres.in/img/faces/11-image.jpg"
}
]
];
setSelectedRowsPerPage(preSelectedItems);
}, []);
const columns = useMemo(
() => [
{
name: "Avatar",
cell: (row) => (
<img
height="30px"
width="30px"
alt={row.first_name}
src={row.avatar}
/>
)
},
{
name: "First Name",
selector: (row) => row.first_name
},
{
name: "Last Name",
cell: (row) => row.last_name
},
{
name: "Email",
selector: (row) => row.email
}
],
[]
);
return (
<div className="App">
<h1>react-data-table-component</h1>
<h2>Users</h2>
{
//had to remove the title in the DataTable, because the select count was only regarging the current page not all selected rows
}
<DataTable
pagination
paginationServer
selectableRows
columns={columns}
data={data}
progressPending={loading}
paginationTotalRows={totalRows}
selectableRowsNoSelectAll={true} //I needed to remove the select all, because it would not work due to the mouse enter/leave workaround
paginationDefaultPage={currentPage}
paginationRowsPerPageOptions={[4, 8, 15]} //you can remove it later, just to have more pages
paginationPerPage={rowsPerPage}
onRowMouseEnter={handleMouseEnter}
onRowMouseLeave={handleMouseLeave}
onChangePage={handlePageChange}
onChangeRowsPerPage={handleRowsPerPageChange}
onSelectedRowsChange={handleOnSelectedRowsChange}
selectableRowSelected={handleApplySelectedRows}
/>
<button
onClick={() => {
setSelectedRowsPerPage([...selectedRowsPerPage]);
}}
>
Refresh All Selected
</button>
<br />
<br />
{JSON.stringify(getAllSelectedRows())}
</div>
);
}