Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

react-data-table-component onChangePage method triggers after onSelectedRowsChange method

I'm trying to keep track of selected items by page.

  • When I click next page I'm updating the "currentPage" state with "onChangePage" event of data table.
  • Because "onChangeRowsPerPage" event triggers first before "onChangePage" event and reset selected items before page change, I always end up losing the previous page selected items.

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>
);
}
like image 453
senerdude Avatar asked Apr 02 '26 02:04

senerdude


1 Answers

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: "[email protected]",
          first_name: "Emma",
          last_name: "Wong",
          avatar: "https://reqres.in/img/faces/3-image.jpg"
        },
        {
          id: 1,
          email: "[email protected]",
          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: "[email protected]",
          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>
  );
}
like image 182
Paulo Fernando Avatar answered Apr 03 '26 18:04

Paulo Fernando



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!