Last active
November 5, 2024 13:32
-
-
Save sadhakbj/8a3937361c8fcfa1ffdcfeb61b628ce4 to your computer and use it in GitHub Desktop.
Data table component with Laravel & React JS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React from "react"; | |
| import DataTable from "./DataTable"; | |
| const App = () => { | |
| return ( | |
| <div className="card"> | |
| <div className="card-header">Users</div> | |
| <div className="card-body"> | |
| <DataTable | |
| fetchUrl="/api/users" | |
| columns={["name", "address", "email", "created_at"]} | |
| ></DataTable> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default App |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { debounce } from "lodash" | |
| import React, { useEffect, useRef, useState } from "react" | |
| import Paginator from "./Paginator" | |
| const SORT_ASC = "asc" | |
| const SORT_DESC = "desc" | |
| const DataTable = ({ columns, fetchUrl }) => { | |
| const [data, setData] = useState([]) | |
| const [perPage, setPerPage] = useState(10) | |
| const [sortColumn, setSortColumn] = useState(columns[0]) | |
| const [sortOrder, setSortOrder] = useState("asc") | |
| const [search, setSearch] = useState("") | |
| const [pagination, setPagination] = useState({}) | |
| const [currentPage, setCurrentPage] = useState(1) | |
| const [loading, setLoading] = useState(true) | |
| const handleSort = (column) => { | |
| if (column === sortColumn) { | |
| sortOrder === SORT_ASC ? setSortOrder(SORT_DESC) : setSortOrder(SORT_ASC) | |
| } else { | |
| setSortColumn(column) | |
| setSortOrder(SORT_ASC) | |
| } | |
| } | |
| const handleSearch = useRef( | |
| debounce((query) => { | |
| setSearch(query) | |
| setCurrentPage(1) | |
| setSortOrder(SORT_ASC) | |
| setSortColumn(columns[0]) | |
| }, 500) | |
| ).current | |
| const handlePerPage = (perPage) => { | |
| setCurrentPage(1) | |
| setPerPage(perPage) | |
| } | |
| const loaderStyle = { width: "4rem", height: "4rem" } | |
| useEffect(() => { | |
| const fetchData = async () => { | |
| setLoading(true) | |
| const params = { | |
| search, | |
| sort_field: sortColumn, | |
| sort_order: sortOrder, | |
| per_page: perPage, | |
| page: currentPage, | |
| } | |
| const { data } = await axios(fetchUrl, { params }) | |
| setData(data.data) | |
| setPagination(data.meta) | |
| setTimeout(() => { | |
| setLoading(false) | |
| }, 300) | |
| } | |
| fetchData() | |
| }, [perPage, sortColumn, sortOrder, search, currentPage]) | |
| return ( | |
| <div> | |
| {/* Search per page starts */} | |
| <div className="row mb-3"> | |
| <div className="col-md-3"> | |
| <div className="input-group"> | |
| <input | |
| className="form-control" | |
| placeholder="Search..." | |
| type="search" | |
| onChange={(e) => handleSearch(e.target.value)} | |
| /> | |
| </div> | |
| </div> | |
| <div className="col-md-2"> | |
| <div className="input-group"> | |
| <label className="mt-2 me-2">Per page</label> | |
| <select className="form-select" value={perPage} onChange={(e) => handlePerPage(e.target.value)}> | |
| <option value="5">5</option> | |
| <option value="10">10</option> | |
| <option value="20">20</option> | |
| <option value="50">50</option> | |
| </select> | |
| </div> | |
| </div> | |
| </div> | |
| {/* Search per page ends */} | |
| <div className="tableFixHead"> | |
| <table className="table table-hover"> | |
| <thead className="table-dark"> | |
| <tr> | |
| {columns.map((column) => { | |
| return ( | |
| <th key={column} onClick={(e) => handleSort(column)}> | |
| {column.toUpperCase().replace("_", " ")} | |
| {column === sortColumn ? ( | |
| <span> | |
| {sortOrder === SORT_ASC ? ( | |
| <i className="ms-1 fa fa-arrow-up" aria-hidden="true"></i> | |
| ) : ( | |
| <i className="ms-1 fa fa-arrow-down" aria-hidden="true"></i> | |
| )} | |
| </span> | |
| ) : null} | |
| </th> | |
| ) | |
| })} | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {data.length === 0 ? ( | |
| <tr> | |
| <td colSpan={columns.length}>No items found</td> | |
| </tr> | |
| ) : ( | |
| "" | |
| )} | |
| {!loading ? ( | |
| data.map((d, index) => { | |
| return ( | |
| <tr key={index}> | |
| {columns.map((column) => { | |
| return <td key={column}>{d[column]}</td> | |
| })} | |
| </tr> | |
| ) | |
| }) | |
| ) : ( | |
| <tr> | |
| <td colSpan={columns.length + 1}> | |
| <div className="d-flex justify-content-center"> | |
| <div className="spinner-border" style={loaderStyle} role="status"> | |
| <span className="sr-only">Loading...</span> | |
| </div> | |
| </div> | |
| </td> | |
| </tr> | |
| )} | |
| </tbody> | |
| </table> | |
| </div> | |
| {data.length > 0 && !loading ? ( | |
| <div className="mt-2"> | |
| <Paginator | |
| pagination={pagination} | |
| pageChanged={(page) => setCurrentPage(page)} | |
| totalItems={data.length} | |
| /> | |
| </div> | |
| ) : null} | |
| </div> | |
| ) | |
| } | |
| export default DataTable |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import React, { useEffect, useState } from "react" | |
| const OFFSET = 4 | |
| const Paginator = ({ pagination, pageChanged, totalItems }) => { | |
| const [pageNumbers, setPageNumbers] = useState([]) | |
| useEffect(() => { | |
| const setPaginationPages = () => { | |
| let pages = [] | |
| const { last_page, current_page, from, to } = pagination | |
| if (!to) return [] | |
| let fromPage = current_page - OFFSET | |
| if (fromPage < 1) fromPage = 1 | |
| let toPage = fromPage + OFFSET * 2 | |
| if (toPage >= last_page) { | |
| toPage = last_page | |
| } | |
| for (let page = fromPage; page <= toPage; page++) { | |
| pages.push(page) | |
| } | |
| setPageNumbers(pages) | |
| } | |
| setPaginationPages() | |
| }, [pagination]) | |
| return ( | |
| <nav aria-label="Page navigation example"> | |
| <ul className="pagination"> | |
| <li className={"page-item" + (pagination.current_page === 1 ? " disabled" : "")}> | |
| <a className="page-link" onClick={() => pageChanged(1)}> | |
| First | |
| </a> | |
| </li> | |
| <li className={"page-item" + (pagination.current_page === 1 ? " disabled" : "")}> | |
| <a className="page-link" onClick={() => pageChanged(pagination.current_page - 1)}> | |
| Previous | |
| </a> | |
| </li> | |
| {pageNumbers.map((pageNumber) => { | |
| return ( | |
| <li | |
| className={ | |
| "page-item" + (pageNumber === pagination.current_page ? " page-item active" : "") | |
| } | |
| key={pageNumber} | |
| onClick={() => pageChanged(pageNumber)} | |
| > | |
| <a className="page-link" href="#"> | |
| {pageNumber} | |
| </a> | |
| </li> | |
| ) | |
| })} | |
| <li className={"page-item" + (pagination.current_page === pagination.last_page ? " disabled" : "")}> | |
| <a className="page-link" href="#" onClick={() => pageChanged(pagination.current_page + 1)}> | |
| Next | |
| </a> | |
| </li> | |
| <li className={"page-item" + (pagination.current_page === pagination.last_page ? " disabled" : "")}> | |
| <a className="page-link" onClick={() => pageChanged(pagination.last_page)}> | |
| Last | |
| </a> | |
| </li> | |
| <span class="mt-2 ms-2"> | |
| <i> | |
| Showing {pagination.from} - {pagination.to} of {pagination.total} entries. | |
| </i> | |
| </span> | |
| </ul> | |
| </nav> | |
| ) | |
| } | |
| export default Paginator |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment