React-table global filter Regex - reactjs

Having trouble getting my GlobalFilter to update the table when using Regex to search for multiple results in a column.
export const Table = ({ data, columns }) => {
const filterTypes = useMemo(
() => ({
// Override the default text filter to use
// "startWith"
text: (rows, id, filterValue) => {
return rows.filter((row) => {
const rowValue = row.values[id];
const isRegexMatch = () => {
try {
return RegExp(filterValue).test(String(rowValue));
} catch (err) {
return false;
}
};
return rowValue !== undefined
? String(rowValue)
.toLowerCase()
.startsWith(String(filterValue).toLowerCase()) || isRegexMatch()
: true;
});
},
}),
[]
);
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page,
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
preGlobalFilteredRows,
setGlobalFilter,
state,
state: { pageIndex, pageSize },
} = useTable(
{
columns,
data,
filterTypes,
},
useFilters,
useGlobalFilter,
useSortBy,
usePagination
);
return (
<div className="col-12">
<div className="table-responsive mb-5">
<GlobalFilter
preGlobalFilteredRows={preGlobalFilteredRows}
globalFilter={state.globalFilter}
setGlobalFilter={setGlobalFilter}
/>
<table {...getTableProps()} className="table">
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render('Header')}
<span>
{column.isSorted ? (
column.isSortedDesc ? (
<ChevronDown baseLayer="icon" />
) : (
<ChevronUp baseLayer="icon" />
)
) : (
''
)}
</span>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()} key={i}>
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
);
})}
</tr>
);
})}
</tbody>
</table>
<div className="pagination mt-3">
<button
onClick={() => gotoPage(0)}
disabled={!canPreviousPage}
className="btn btn-sm btn-link"
>
{'<<'}
</button>{' '}
<button
onClick={() => previousPage()}
disabled={!canPreviousPage}
className="btn btn-sm btn-link"
>
{'<'}
</button>{' '}
<button
onClick={() => nextPage()}
disabled={!canNextPage}
className="btn btn-sm btn-link"
>
{'>'}
</button>{' '}
<button
onClick={() => gotoPage(pageCount - 1)}
disabled={!canNextPage}
className="btn btn-sm btn-link"
>
{'>'}
</button>{' '}
<span className="mt-2 text-muted">
<small>
Page {pageIndex + 1} of {pageOptions.length}{' '}
</small>
</span>
<span className="mt-2 ms-1 me-1 text-muted">
<small> | Go to page:</small>
</span>
<span className="me-1">
<input
type="number"
className="form-control"
defaultValue={pageIndex + 1}
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
gotoPage(page);
}}
style={{ width: '80px' }}
/>
</span>
<select
value={pageSize}
style={{ width: '190px' }}
className="form-control"
onChange={(e) => {
setPageSize(Number(e.target.value));
}}
>
{[10, 20, 30, 40, 50].map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize} per page
</option>
))}
<option key={data.length} value={data.length}>
Show All
</option>
</select>
</div>
</div>
</div>
);
};
I only had success so far in the console with this function being able to filter the results, but so far no luck with the react-table package.
function regexFilter(rows, ids, filterValue) {
rows = rows.filter((row) => {
return ids.some((id) => {
const rowValue = row.values[id];
const isRegexMatch = () => {
try {
return RegExp(filterValue).test(String(rowValue));
} catch (err) {
return false;
}
};
return (
String(rowValue)
.toLowerCase()
.includes(String(filterValue).toLowerCase()) || isRegexMatch()
);
});
});
return rows;
}
What am I not doing correctly or mis-interpreting from the documentation on filterTypes?

Related

Click event with React Table 7

This is my Cell data in column:
import { format } from "date-fns";
export const COLUMNS_ALLLOAN = [
{
Header: "Id",
accessor: "id",
Cell: ({ row }) => Number(row.id) + 1,
},
{
Header: "Account Number",
accessor: "acNumber",
Cell: ({ row }) => <>{row.original.acNumber}</>,
},
{
Header: "Account Name",
accessor: "acName",
},
{
Header: "Loan Type",
accessor: "acType",
Cell: ({ row }) => <>{row.original.acType}</>,
},
{
Header: "Opening Date",
accessor: "acOpen",
Cell: ({ row }) => <>{row.original.acOpen}</>,
},
{
Header: "Sanction Date",
accessor: "acSanction",
Cell: ({ row }) => <>{row.original.acSanction}</>,
},
{
Header: "Expiry Date",
accessor: "acExpiry",
Cell: ({ row }) => <>{row.original.acExpiry}</>,
},
{
Header: "Limit",
accessor: "acLimit",
Cell: ({ row }) => <>{Number(row.original.acLimit)}</>,
},
{
Header: "Outstanding",
accessor: "lastDayBalance",
Cell: ({ row }) => <>{Number(row.original.lastDayBalance)}</>,
},
{
Header: "Overlimit",
accessor: "overlimit",
Cell: ({ row }) => <>{Number(row.original.overLimit)}</>,
},
{
Header: "Available",
accessor: "available",
Cell: ({ row }) => <>{Number(row.original.availableBalance)}</>,
},
{
Header: "Edit",
accessor: "edit",
Cell: ({ row }) => <button className="btn btn-primary">Edit</button>,
},
{
Header: "Delete",
accessor: "X",
Cell: ({ row }) => <button className="btn btn-danger">X</button>,
},
];
This is my page
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import Footer from "../../components/Footer";
import { useMemo } from "react";
import { useTable, useSortBy, useGlobalFilter, usePagination } from "react-table";
import { COLUMNS_ALLLOAN } from "../../components/COLUMNS_ALLLOAN.js";
import Globalfilter from "../../components/Globalfilter";
import { deleteById, getLoans } from "../../features/bradvance/advanceSlice";
const Allloans = () => {
const dispatch = useDispatch();
const { allLoans } = useSelector((state) => state.bradvance);
const navigate = useNavigate();
const goBack = async () => {
navigate(-1);
};
const columns = useMemo(() => COLUMNS_ALLLOAN, []);
const data = useMemo(() => allLoans, []);
// console.log(data);
const tableInstance = useTable(
{
columns,
data: allLoans,
initialState: { pageSize: 30 },
},
useGlobalFilter,
useSortBy,
usePagination
);
const {
headerGroups,
getTableProps,
getTableBodyProps,
page,
prepareRow,
state,
setGlobalFilter,
nextPage,
previousPage,
canNextPage,
canPreviousPage,
pageOptions,
setPageSize,
gotoPage,
pageCount,
} = tableInstance;
const { globalFilter, pageIndex, pageSize } = state;
// console.log(data.length);
const totalLoan = allLoans
.map((item, sl) => {
return item.lastDayBalance;
})
.reduce((acc, curValue) => {
// console.log(curValue);
return acc + curValue;
}, 0);
// console.log(totalLoan);
const handleClick = (id) => {
dispatch(deleteById(id));
alert("Deleted Successfully.");
console.log(id);
};
return (
<>
<div className="container">
<div className="row">
<div className="col">
<div className="summary">
<h1 className="p-4 text-center fw-bold mb-0">All Loans</h1>
</div>
</div>
</div>
<div className="row">
<div className="col">
<Globalfilter filter={globalFilter} setFilter={setGlobalFilter} />
</div>
</div>
<div className="row">
<div className="col">
<div className="summary table-responsive">
<table {...getTableProps()} className="table table-hover table-bordered">
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps(column.getSortByToggleProps())}>{column.render("Header")} </th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td onClick={() => handleClick(row.original.id)} {...cell.getCellProps()}>
{cell.render("Cell")}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</div>
<div className="row ">
<div className="col d-flex justify-content-end">
<ul className="list-group list-group-horizontal">
<li className="list-group-item fw-bold">
Total Loan: <span className="fw-bold text-danger">{allLoans.length}</span>
</li>
<li className="list-group-item fw-bold">
Total Amount:<span className="fw-bold text-danger">{totalLoan.toFixed(2)}</span>{" "}
</li>
</ul>
</div>
</div>
<div className="section p-5 pagination col-12">
<button className="page-link" onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
{"<<"}
</button>{" "}
<button className="page-link" onClick={() => previousPage()} disabled={!canPreviousPage}>
{"<"}
</button>{" "}
<button className="page-link" onClick={() => nextPage()} disabled={!canNextPage}>
{">"}
</button>{" "}
<button className="page-link" onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
{">>"}
</button>{" "}
<span className="page-link">
Page{" "}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{" "}
</span>
<span className="page-link">
| Go to page:{" "}
<input
type="number"
value={pageIndex + 1}
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
gotoPage(page);
}}
style={{ width: "100px" }}
/>
</span>{" "}
<select
className="page-link"
value={pageSize}
onChange={(e) => {
setPageSize(Number(e.target.value));
}}
>
{[20, 50, 100, 200, 500].map((pageSize) => (
<option key={pageSize} value={pageSize} className="page-link">
Show {pageSize}
</option>
))}
</select>
</div>
{/* Go Back */}
<div className="row ">
<div className="col ">
<div className="text-center summary pb-3">
<button className="btn btn-outline-primary mb-0" onClick={goBack}>
<i className="bi bi-skip-backward"></i> Go Back
</button>
</div>
</div>
</div>
<div className="row">
<div className="col">
<Footer />
</div>
</div>
</div>
</>
);
};
export default Allloans;
This is how my page looks:
I can delete each row successfully. The problem is that it deletes when I click on any part of the row. But, I want only the button to be clickable not the whole row. I mean, when I click the delete button only, it should delete.
First, remove the onClick={() => handleClick(row.original.id)} from your row td tag so it become as follows
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>
{cell.render("Cell")}
</td>
);
})}
next, move your on-click handler to the top of the table instance creation
...
const handleClick = (id) => {
dispatch(deleteById(id));
alert("Deleted Successfully.");
console.log(id);
};
const tableInstance = useTable(
{
...
}
);
then, remove the last two columns' definitions (Edit & Delete def) from COLUMNS_ALLLOAN and insert those col definitions into useTable and finally add the onClick event listener to the delete button.
const tableInstance = useTable(
{
columns,
data: allLoans,
initialState: { pageSize: 30 },
},
useGlobalFilter,
useSortBy,
usePagination,
// create Edit and Delete col definition by pushing the two into visibleColumns
(hooks) => {
hooks.visibleColumns.push((col) => [
...col,
{
Header: "Edit",
id: "editBtn",
Cell: ({ row }) => <button className="btn btn-primary">Edit</button>,
},
{
Header: "Delete",
id: "deleteBtn",
Cell: ({ row }) => <button onClick={() => handleClick(row.original.id)} className="btn btn-danger">X</button>,
},
]);
}
);
Here is the minimal example:

React-table version 7 all the data appears on One page

I am trying to implement react-table
I have 750 rows and they all appear on the first page although I have controlled pagination that is telling me I am on page 1 of 68
But all the 750 rows - as I said - appear on one page .
All features are working, except for that issue.
I am fetching the data ( altogether ) from an API and sending them to the Table.
this is the code
import React from 'react';
import {
useSortBy,
useTable,
useGlobalFilter,
usePagination
} from 'react-table';
function Table({ columns, data }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
nextPage,
previousPage,
canNextPage,
canPreviousPage,
pageOptions,
gotoPage,
pageCount,
setPageSize,
rows,
prepareRow,
state,
setGlobalFilter
} = useTable(
{
columns,
data
},
useGlobalFilter,
useSortBy,
usePagination
);
const { globalFilter, pageIndex, pageSize } = state;
return (
<>
<GlobalFilter filter={globalFilter} setFilter={setGlobalFilter} />
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render('header')}
<span>
{column.isSorted
? column.isSortedDesc
? ' 🔽'
: ' 🔼'
: ''}
</span>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
);
})}
</tr>
);
})}
</tbody>
</table>
<div>
<span>
Page{' '}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>
</span>
<span>
| Go to page
<input
type="number"
defaultValue={pageIndex + 1}
onChange={(e) => {
const pageNumber = e.target.value
? Number(e.target.value) - 1
: 0;
gotoPage(pageNumber);
}}
/>
</span>
<select
value={pageSize}
onChange={(e) => setPageSize(Number(e.target.value))}
>
{[10, 25, 50].map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
<button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
{'<<'}
</button>
<button onClick={() => previousPage()} disabled={!canPreviousPage}>
Previous
</button>
<button onClick={() => nextPage()} disabled={!canNextPage}>
Next
</button>
<button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
{'>>'}
</button>
</div>
</>
);
}
const GlobalFilter = ({ filter, setFilter }) => {
return (
<span>
Search:{' '}
<input value={filter || ''} onChange={(e) => setFilter(e.target.value)} />
</span>
);
};
export default Table;
My bad
I should use page instead of rows
Like this
<tbody {...getTableBodyProps()}>
{page.map((row) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td
{...cell.getCellProps()}
className="p-2 border border-collapse"
>
{cell.render('Cell')}
</td>
);
})}
</tr>
);
})}
</tbody>

React-Table resetting to page 1 when I add data

I have a react table with pages that works with me adding new data to the collection however when ever data is added it always resets the current page to 0. Is there some way to save the current state of what page is currently selected? I cant figure out based of this code I have from a fellow what I need to change in order for this to not keep resetting the page index.
My table code looks like this
function Table({ columns, data }) {
const filterTypes = React.useMemo(
() => ({
// Add a new fuzzyTextFilterFn filter type.
fuzzyText: fuzzyTextFilterFn,
// Or, override the default text filter to use
// "startWith"
text: (rows, id, filterValue) => {
return rows.filter(row => {
const rowValue = row.values[id]
return rowValue !== undefined
? String(rowValue)
.toLowerCase()
.startsWith(String(filterValue).toLowerCase())
: true
})
},
}),
[]
)
const defaultColumn = React.useMemo(
() => ({
// Let's set up our default Filter UI
Filter: DefaultColumnFilter,
}),
[]
)
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
state,
visibleColumns,
preGlobalFilteredRows,
setGlobalFilter,
page,
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
selectedPage,
state: { pageIndex, pageSize },
} = useTable(
{
columns,
data,
defaultColumn, // Be sure to pass the defaultColumn option
filterTypes,
initialState: { pageIndex: 0}
},
useFilters, // useFilters!
useGlobalFilter, // useGlobalFilter!
usePagination
)
// We don't want to render all of the rows for this example, so cap
// it for this use case
//const firstPageRows = rows.slice(0, 10)
return (
<>
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>
{column.render('Header')}
{/* Render the columns filter UI */}
<div>{column.canFilter ? column.render('Filter') : null}</div>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row)
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
<br />
{/*<div>Showing the first 20 results of {rows.length} rows</div>*/}
<div className="pagination">
<button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
{'<<'}
</button>{' '}
<button onClick={() => previousPage()} disabled={!canPreviousPage}>
{'<'}
</button>{' '}
<button onClick={() => nextPage()} disabled={!canNextPage}>
{'>'}
</button>{' '}
<button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
{'>>'}
</button>{' '}
<span>
Page{' '}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{' '}
</span>
<span>
| Go to page:{' '}
<input
type="number"
defaultValue={pageIndex}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
style={{ width: '100px' }}
/>
</span>{' '}
<select
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value))
}}
>
{[10, 20, 30, 40].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
</>
)
}```
You might not need to save the current state of what page is currently selected. All you need to do is set autoselect option on your table to false.
Your code should look like this
function Table({ columns, data }) {
const filterTypes = React.useMemo(
() => ({
// Add a new fuzzyTextFilterFn filter type.
fuzzyText: fuzzyTextFilterFn,
// Or, override the default text filter to use
// "startWith"
text: (rows, id, filterValue) => {
return rows.filter(row => {
const rowValue = row.values[id]
return rowValue !== undefined
? String(rowValue)
.toLowerCase()
.startsWith(String(filterValue).toLowerCase())
: true
})
},
}),
[]
)
const defaultColumn = React.useMemo(
() => ({
// Let's set up our default Filter UI
Filter: DefaultColumnFilter,
}),
[]
)
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
state,
visibleColumns,
preGlobalFilteredRows,
setGlobalFilter,
page,
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
selectedPage,
state: { pageIndex, pageSize },
} = useTable(
{
columns,
data,
defaultColumn, // Be sure to pass the defaultColumn option
filterTypes,
initialState: { pageIndex: 0},
autoResetPage: false, //this change should be made
},
useFilters, // useFilters!
useGlobalFilter, // useGlobalFilter!
usePagination
)
// We don't want to render all of the rows for this example, so cap
// it for this use case
//const firstPageRows = rows.slice(0, 10)
return (
<>
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>
{column.render('Header')}
{/* Render the columns filter UI */}
<div>{column.canFilter ? column.render('Filter') : null}</div>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row)
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
})}
</tbody>
</table>
<br />
{/*<div>Showing the first 20 results of {rows.length} rows</div>*/}
<div className="pagination">
<button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
{'<<'}
</button>{' '}
<button onClick={() => previousPage()} disabled={!canPreviousPage}>
{'<'}
</button>{' '}
<button onClick={() => nextPage()} disabled={!canNextPage}>
{'>'}
</button>{' '}
<button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
{'>>'}
</button>{' '}
<span>
Page{' '}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{' '}
</span>
<span>
| Go to page:{' '}
<input
type="number"
defaultValue={pageIndex}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
style={{ width: '100px' }}
/>
</span>{' '}
<select
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value))
}}
>
{[10, 20, 30, 40].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
</>
)}
The answer is found on this page of the docs

Pagination in reactstrap issue(updated)

Here I have created the pagination(reactstrap) for the table. Backend has 100 data records. I display 10 records per page. It works fine as i change the page.I have written some code for displaying paginating numbers.Initially, It should display paginating numbers from 1-5. On clicking 5th page, paginating numbers should display from 6 to 10 and so on.. Please help me. Thanks in advance
import {getInitialUsers, getPageByPageUsers} from "services/userService";
const Users = () => {
const [payloads, setPayloads] = useState({
offset: 0,
limit: 10,
search: "",
page: 1,
rowsPerPage: 10,
count: 0,
});
const [start, setStartPage] = useState(0);
const [end, setEndPage] = useState(payloads.limit);
const [users, setUsers]= useState([]);
useEffect(() => {
fetchInitialData();
}, []);
const fetchInitialData = async () => {
try {
let res = await getInitialUserRequests();
setUsers(res.data.data)
payloads.count = res.data.size;
getPager(res.data.size, payloads.page); /* res.data.size means total number of records */
} catch (err) {
console.log("error")
}
};
const getPager = (count, page) => {
let totalPages = Math.ceil(count / payloads.rowsPerPage);
/* facing issues in the following conditions */
if (totalPages <= 5) {
setStartPage(1);
setEndPage(totalPages);
} else {
if (page <= 5) {
setStartPage(1);
setEndPage(5);
}
if (page >= end - start) {
setStartPage(end - start);
setEndPage(end - start + 4);
}
if (page + 4 >= totalPages) {
setStartPage(totalPages - 4);
setEndPage(totalPages);
}
}
};
const handleChangePage = async (e, newPage) => {
payloads.page = newPage;
try {
let res = await getPageByPageUserRequests(newPage);
setUsers(res.data.data)
getPager(payloads.count, newPage);
} catch (err) {
console.log("error")
}
};
return (
<>
<Container className="mt--7" fluid>
<Row>
<div className="col">
<Card className="shadow">
<CardHeader className="border-0">
<h3 className="mb-0">Users</h3>
</CardHeader>
<Table className="align-items-center table-flush" responsive>
<thead className="thead-light">
<tr>
<th scope="col">Username</th>
<th scope="col">E-mail</th>
<th scope="col">Phone Number</th>
</tr>
</thead>
<tbody>
{users.length > 0 ? (
users.map((user) => (
<tr key={user.id}>
<td>{user.username}</td>
<td>{user.email}</td>
<td>{user.phone}</td>
</tr>
))
) : (
<tr className="text-center">
<th colSpan="5">No Data Found!!</th>
</tr>
)}
</tbody>
</Table>
<CardFooter className="py-4">
<nav aria-label="...">
<Pagination
className="pagination justify-content-end mb-0"
listClassName="justify-content-end mb-0"
>
<PaginationItem
className={payloads.page <= 1 && `disabled`}
>
<PaginationLink
href="#"
onClick={(e) => handleChangePage(e, payloads.page - 1)}
tabIndex="-1"
>
<i className="fas fa-angle-left" />
<span className="sr-only">Previous</span>
</PaginationLink>
</PaginationItem>
{[
...Array(
Math.ceil(payloads.count / payloads.rowsPerPage)
),
].map((page, i) => {
if (i >= start && i < end) {
return (
<PaginationItem
className={i === payloads.page ? `active` : ""}
key={i}
>
<PaginationLink
tag="button"
onClick={(e) => handleChangePage(e, i)}
>
{i}
</PaginationLink>
</PaginationItem>
);
}
})}
<PaginationItem
className={
payloads.page ==
Math.ceil(payloads.count / payloads.rowsPerPage) ?
`disabled`:''
}
>
<PaginationLink
href="#"
onClick={(e) => handleChangePage(e, payloads.page + 1)}
>
<i className="fas fa-angle-right" />
<span className="sr-only">Next</span>
</PaginationLink>
</PaginationItem>
</Pagination>
</nav>
</CardFooter>
</Card>
</div>
</Row>
</Container>
</>
);
};
i usually use a simple mathematical formula for this
data?.slice(pageNumber * rowsPerPage, pageNumber * rowsPerPage + rowsPerPage)?.map((row, index){
<div key={index}>{row}</div>
})

React editing specific item

When I hit the Edit button I trigger a flag for enabling editing. The desired behaviour would be to enable editing only on one item at the time. However, all the item get enabled for editing by the flag.
<li key={recipe._id} className="list__item recipe" id={randomString()}>
<Card body id={`card-${recipe._id}`} >
<CardTitle name={randomString()}><input type="text" name="name" defaultValue={recipe.name} readOnly={disabled} className="recipe__name" onChange={(e) => {changeItem(e, recipe)}} /></CardTitle>
<CardImg top width="100%" src={require('../assets/318x180.svg')} alt="Card image cap" />
<input id={String(recipe._id)} className="toggle" type="checkbox" onChange={(e) => {handleChange(e)}} />
<label htmlFor={String(recipe._id)} className="lbl-toggle bold">Ingredients</label>
<ul key={recipe._id} className="ingredients expand-content" id='ingredientList'>
{iselect()}
</ul>
<table>
<tbody>
<tr>
<td className={'center'}><span className={'bold'}>Actual Cost:</span></td>
<td className={'center'}><span className={'bold'}>Proposed Price:</span></td>
</tr>
<tr>
<td className={'center'}><span className="recipe__cost">£{cost(Math.floor(Math.random()*10))}</span></td>
<td className={'center'}><span className="recipe__proposed">£{proposed(cost(Math.floor(Math.random()*10)))}</span></td>
</tr>
</tbody>
</table>
<Button color="primary" onClick={() => { saveEditRecipe(recipe); }} className={`bold save-button ${disabled ? "hidden" : "show"}`}>Save</Button>
<Button color="primary" onClick={(e) => { editRecipe(e, recipe); }} id={`card-${recipe._id}`} className={'edit-button'}>Edit</Button>
<Button color="danger" onClick={() => { deleteRecipe(recipe); }} className={'delete'}>X</Button>
</Card>
</li>
// /client/src/components/Recipe.js
import React, { useState, useEffect} from "react";
import { Card, Button, CardTitle, CardText, CardImg } from 'reactstrap';
import { FaPen} from "react-icons/fa";
import './Recipe.css';
// SERVICES
import recipeService from '../services/recipeService';
import ingredientService from '../services/ingredientService';
function Recipe() {
const initialEditState = { id: null, name: '', ingredients: null }
const [recipes, setrecipes] = useState(null);
const [newRecipe, setNewRecipe] = useState({});
const [ingredients, setingredients] = useState(null);
const [fields, setFields] = useState([{name:"", quantity: null}]);
const [editFields, setEditFields] = useState([{ name: "", quantity: null }]);
const [disabled, setDisabled] = useState(true);
const [currentRecipe, setCurrentRecipe] = useState(initialEditState)
// const inputRef = useRef();
// function handleGamClick() {
// setDisabled(!disabled);
// }
useEffect(() => {
if(!recipes) {
getRecipes();
}
if(!ingredients) {
getIngredients();
}
if(currentRecipe._id !== undefined) {
setDisabled(false);
}
// console.log(disabled);
}, [recipes, ingredients, currentRecipe])
const getRecipes = async () => {
let res = await recipeService.getAll();
setrecipes(res);
}
const getIngredients = async () => {
let res = await ingredientService.getAll();
setingredients(res);
}
// const loadIngredients = (recipe) => {
// let initial = recipe.ingredients;
// let ingredients;
// const values = [...editFields];
// for (var i in initial){
// ingredients = initial[i].ingredients;
// }
// initial.map((ingredient) => {
// setEditFields(ingredient);
// }
// )
// setEditFields(values);
// console.log(values);
// }
// Method for calculating recipe cost
const cost = (nameKey, ingredient) => {
return 20;
}
// Method for calculating recipe proposed price
const proposed = (recipe) => {
return recipe * 2.5;
}
// Method for creating recipe
const createRecipe = async (recipe) => {
const ingredients = fields;
const object = {...newRecipe, ingredients }
recipeService.create(object);
window.location.reload();
}
// Method for
const changeItem = (e, recipe) => {
recipe[e.target.name] = e.target.value;
return recipe;
}
// Method for editing recipe
const editRecipe = async (e, recipe) => {
setCurrentRecipe(recipe);
// if (e.target) {
// setDisabled(false);
// }
// setEditFields(recipe.ingredients);
// const ingredients = editFields;
// const object = { recipe, ingredients }
// if (recipe._id === currentRecipe._id) {
// setDisabled(false);
// }
// console.log(editFields);
// recipeService.edit(object);
// window.location.reload();
}
const saveEditRecipe = async (recipe) => {
setCurrentRecipe(initialEditState);
setDisabled(true);
}
// Method for deleting recipe
const deleteRecipe = async (recipe) => {
if (window.confirm('Are you sure you wish to delete this item?')) {
recipeService.delete(recipe);
window.location.reload();
}
}
// Method for
const handleChange = (e) => setNewRecipe({
...newRecipe,
// [e.target.name]:e.target.name === "name" ? e.target.value : Number(e.target.value ),
[e.target.name]:e.target.value
});
// Method for
function handleChanger(idx, event) {
// console.log(recipes)
// console.log(event.target.name);
const values = event.target.className.includes("edit") ? [...editFields] : [...fields];
if (event.target.name === "name") {
values[idx].name = event.target.value;
} else {
values[idx].quantity = event.target.value;
}
event.target.className.includes("edit") ? setEditFields(values) : setFields(values);
}
// Method for
function handleAdd(e, idx) {
console.log(idx);
const values = e.target.className.includes("edit") ? [...editFields] : [...fields];
values.push({name:"", quantity: null});
e.target.className.includes("edit") ? setEditFields(values) : setFields(values);
}
// Method for
function handleRemove(e, i) {
const values = e.target.className.includes("edit") ? [...editFields] : [...fields];
values.splice(i, 1);
e.target.className.includes("edit") ? setEditFields(values) : setFields(values);
}
// rendering ingredients dropdown
const renderIngredientName = (ingredient) => {
let initial =[];
for (ingredient in ingredients) {
initial.push(ingredients[ingredient].name);
}
let createOption = (option) => {
return <option key={option.toLowerCase()} defaultValue={option.toLowerCase()} >{option.toLowerCase()}</option>;
}
return initial.map(createOption);
}
// Method for
const randomString = (recipe) => {
return String("expand"+Math.floor(Math.random()*100))
}
// Rendering recipes
const renderRecipe = recipe => {
const iselect = () => {
let initial = recipe.ingredients;
function splice(idx){
return initial.splice(idx, 1)
}
return initial.map((ingredient, idx) =>
<li key={`${ingredient}-${idx}`}>
<select name="name" className="edit recipe__ingredient__name" value={ingredient.name || ""} disabled={disabled} onChange={(e) => {handleChanger(idx, e)}}>
{renderIngredientName()}
</select>
<input type="number" name="quantity" id="quantity" className="recipe__ingredient__quantity" disabled={disabled} defaultValue={ingredient.quantity} />
{/* <span className={"float"}><Button color="danger" onClick={() => splice(idx)} className={'edit delete-ingredient'}>x</Button></span> */}
</li>
);
}
// eslint-disable-next-line
const edit = () => {
let edit = [{name:"", quantity: null}];
return edit.map((ingredient, idx) =>
<li key={`${ingredient}-${idx}-${recipe._id}`}>
<select name="name" className="edit recipe__ingredient__name" value={ingredient.name || ""} onChange={(e) => {handleChanger(idx, e)}}>
<option value=""></option>
{renderIngredientName()}
</select>
<input type="number" name="quantity" className="edit recipe__ingredient__quantity" value={ingredient.quantity || ""} placeholder="grams" onChange={e => handleChanger(idx, e)} />
<span className={'float'}>
{ idx === edit.length-1 ?
(<Button color="primary" className={'edit create-ingredient'} onClick={(e) => handleAdd(e, idx)}>+</Button>)
:
(<Button color="danger" onClick={(e) => handleRemove(e, idx)} className={'edit delete-ingredient'}>x</Button>)
}
</span>
</li>
)
}
return (
<li key={recipe._id} className="list__item recipe" id={randomString()}>
<Card body id={`card-${recipe._id}`} >
<CardTitle name={randomString()}><input type="text" name="name" defaultValue={recipe.name} readOnly={disabled} className="recipe__name" onChange={(e) => {changeItem(e, recipe)}} /></CardTitle>
<CardImg top width="100%" src={require('../assets/318x180.svg')} alt="Card image cap" />
<input id={String(recipe._id)} className="toggle" type="checkbox" onChange={(e) => {handleChange(e)}} />
<label htmlFor={String(recipe._id)} className="lbl-toggle bold">Ingredients</label>
{/* <span className="bold">Ingredients:</span> */}
<ul key={recipe._id} className="ingredients expand-content" id='ingredientList'>
{iselect()}
{/* {editFields.map((ingredient, idx) => {
return (
<li key={`${ingredient}-${idx}-${recipe._id}`}>
<select name="name" className="edit recipe__ingredient__name" value={ingredient.name || ""} onChange={(e) => {handleChanger(idx, e)}}>
<option value=""></option>
{renderIngredientName()}
</select>
<input type="number" name="quantity" className="edit recipe__ingredient__quantity" value={ingredient.quantity || ""} placeholder="grams" onChange={e => handleChanger(idx, e)} />
<span className={'float'}>
{ idx === editFields.length-1 ?
(<Button color="primary" className={'edit create-ingredient'} onClick={(e) => handleAdd(e, idx)}>+</Button>)
:
(<Button color="danger" onClick={(e) => handleRemove(e, idx)} className={'edit delete-ingredient'}>x</Button>)
}
</span>
</li>
);
})} */}
</ul>
<table>
<tbody>
<tr>
<td className={'center'}><span className={'bold'}>Actual Cost:</span></td>
<td className={'center'}><span className={'bold'}>Proposed Price:</span></td>
</tr>
<tr>
<td className={'center'}><span className="recipe__cost">£{cost(Math.floor(Math.random()*10))}</span></td>
<td className={'center'}><span className="recipe__proposed">£{proposed(cost(Math.floor(Math.random()*10)))}</span></td>
</tr>
</tbody>
</table>
<Button color="primary" onClick={() => { saveEditRecipe(recipe); }} className={`bold save-button ${disabled ? "hidden" : "show"}`}>Save</Button>
<Button color="primary" onClick={(e) => { editRecipe(e, recipe); }} id={`card-${recipe._id}`} className={'edit-button'}>Edit</Button>
<Button color="danger" onClick={() => { deleteRecipe(recipe); }} className={'delete'}>X</Button>
</Card>
</li>
);
};
return (
<div className="recipe">
<ul className="list">
{(recipes && recipes.length > 0) ? (
recipes.map(recipe => renderRecipe(recipe))
) : (
<p>No recipes found</p>
)}
<li key='new' className="add__item list__item recipe">
<Card body>
<CardTitle><input type="text" name="name" id="name" placeholder="New Recipe" className="recipe__name" onChange={(e) => {handleChange(e)}} /></CardTitle>
<CardImg top width="100%" src={require('../assets/318x180.svg')} alt="Card image cap" />
<span className={'bold'}>ingredients
{/* <span className={'float'}><Button color="primary" className={'create-ingredient'} onClick={(e) => handleAdd(e)}>+</Button></span> */}
</span>
<ul className="ingredients" id='ingredientList'>
{fields.map((field, idx) => {
return (
<li key={`${field}-${idx}`}>
<select name="name" className="recipe__ingredient__name" value={field.name || ""} onChange={(e) => {handleChanger(idx, e)}}>
<option value=""></option>
{renderIngredientName()}
</select>
<input type="number" name="quantity" className="recipe__ingredient__quantity" value={field.quantity || ""} placeholder="grams" onChange={e => handleChanger(idx, e)} />
{/* <span className={'float'}><Button color="danger" onClick={(e) => handleRemove(e, idx)} className={'delete-ingredient'}>x</Button></span> */}
<span className={'float'}>
{idx === fields.length - 1 ?
(<Button color = "primary" className = { 'create-ingredient' } onClick = { (e) => handleAdd(e) }>+</Button>)
:
(<Button color="danger" onClick={(e) => handleRemove(e, idx)} className={'delete-ingredient'}>x</Button>)
}
</span>
</li>
);
})}
</ul>
<CardText><span className={'bold'}>Calculated Cost:</span></CardText>
<CardText><span className={'bold'}>Calculated Price:</span></CardText>
<Button color="success" className={'new-recipe, bold'} onClick={() => { createRecipe(newRecipe); }}>Create new recipe</Button>
</Card>
</li>
</ul>
</div>
);
}
export default Recipe;
You didn't share your full code, but it should look something like this:
state = {
editableId:null
}
editRecipe = (event,recipe) => {
this.setState(({editableId:recipe._id}))
}
<Button disabled={!this.state.editableId == recipe._id} color="primary" onClick={(e) => { editRecipe(e, recipe); }} id={`card-${recipe._id}`} className={'edit-button'}>Edit</Button>
When you want no items to be editable, reset the state.editableId to null

Resources