How to implement server-side pagination using react-table? - javascript

I am new to react-table. I am trying to implement server-side pagination but I am not getting the logic on how to detect the page change in the new react-table version. I am using fetch data proper I am unable to detect the change. Each time I click on the Next button I should be able to change the offset value in the API endpoint in the increments of 20 to fetch new data. I am unable to perform this operation. Kindly help.
import React, { useEffect, useState, useMemo } from 'react'
import { URLs } from "../../../Config/url";
import cookie from 'react-cookies';
import "./OrderManagementScreen.css"
import { useTable, usePagination, useSortBy } from 'react-table';
import styled from 'styled-components';
const Styles = styled.div`
padding: 1rem;
table {
border-spacing: 0;
border: 1px solid lightgray;
width: 100%;
text-align: "center" !important;
tr {
:last-child {
td {
border-bottom: 0;
text-align: "center" !important;
}
}
}
th {
padding: 3px;
box-shadow: 0px 5px 7px 2px lightgrey;
}
td {
padding: 5px;
}
th,
td {
margin: 0;
text-align: "center";
border-bottom: 1px solid #73737361;
border-right: 1px solid #73737361;
:last-child {
border-right: 0;
}
}
}
.pagination {
}
`;
const WrapperTable = styled.div`
background: #ffffff;
box-shadow: 3px 3px 2px 0px rgb(162 161 161 / 75%) !important;
border-radius: 5px;
`
const Table = ({ columns, data }) => {
// Use the state and functions returned from useTable to build your UI
const {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
prepareRow,
page, // Instead of using 'rows', we'll use page,
// which has only the rows for the active page
// The rest of these things are super handy, too ;)
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state: { pageIndex, pageSize, sortBy },
} = useTable(
{
columns,
data,
initialState: { pageIndex: 0 },
},
useSortBy,
usePagination
);
// const sorted = column.isSorted ? (column.isSortedDesc ? " 🔽" : " 🔼") : "";
// const sorted = column.isSorted ? (column.isSortedDesc ? {borderTop:"1px solid "} :{borderTop:"1px solid "}) : "";
// Render the UI for your table
return (
<>
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render("Header")}
{/* Add a sort direction indicator */}
<span>
{column.isSorted
? column.isSortedDesc
? " 🔽"
: " 🔼"
: ""}
</span>
</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>
{/*
Pagination can be built however you'd like.
This is just a very basic UI implementation:
*/}
<div className="pagination">
{/* <button
className="pagination-btn"
onClick={() => gotoPage(0)}
disabled={!canPreviousPage}
>
First
</button> */}
<button
className="pagination-btn"
onClick={() => previousPage()}
disabled={!canPreviousPage}
>
Previous
</button>
<span className="pagination-btn text-center">
Page{" "}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{" "}
</span>
<button
className="pagination-btn"
onClick={() => nextPage()}
disabled={!canNextPage}
>
Next
</button>
{/* <button
className="pagination-btn"
onClick={() => gotoPage(pageCount - 1)}
disabled={!canNextPage}
>
Last
</button> */}
{/* <span>
| Go to page:{' '}
<input
type="number"
defaultValue={pageIndex + 1}
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, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select> */}
</div>
</>
);
};
const OrderManagementScreen = () => {
const token = cookie.load("userObj").data.token;
//orderid, outletname, area, distributor, ordervalue, outlet type, discount group, salesofficer,order
const [tableData, SetData] = useState([]);
const [loading, setLoading] = React.useState(false);
const fetchIdRef = React.useRef(0);
const sortIdRef = React.useRef(0);
const columns = React.useMemo(
() => [
{
Header: "Order Id",
accessor: "id",
},
{
Header: "Outlet Name",
id: "outlet_detail",
accessor: data => {
let output = [];
data.outlet_detail.map(item => {
return output.push(item.name);
});
return output.join(', ');
}
},
{
Header: "Area",
id: "area",
accessor: data => {
let output = [];
data.outlet_detail.map(item => {
return output.push(item.area__name);
});
return output.join(', ');
}
},
{
Header: "Distributor",
id: "distributor",
accessor: data => {
let output = [];
data.outlet_detail.map(item => {
return output.push(item.distributor_name);
});
return output.join(', ');
}
},
{
Header: "Order Value",
accessor: "total_price",
},
{
Header: "Outlet Type",
id: "outlet_type__name",
accessor: data => {
let output = [];
data.outlet_detail.map(item => {
return output.push(item.final_value);
});
return output.join(', ');
}
},
{
Header: "Discount Group",
id: "discount__name",
accessor: data => {
let output = [];
data.outlet_detail.map(item => {
return output.push(item.discount__name);
});
return output.join(', ');
}
},
{
Header: "Sales Officer",
id: "sales_officer",
accessor: data => {
let output = [];
data.outlet_detail.map(item => {
return output.push(item.by_user__username);
});
return output.join(', ');
}
}
],
[]
);
const listdata = async () => {
const response = await fetch(`${URLs.orderUrl}?limit=20&offset=0`, {
headers: {
'Content-Type': 'application/json',
'Authorization': `Token ${token}`
}
})
const data = await response.json();
SetData(data);
}
const fetchData = React.useCallback(({ pageSize, pageIndex, sortBy }) => {
// This will get called when the table needs new data
// You could fetch your data from literally anywhere,
// even a server. But for this example, we'll just fake it.
// Give this fetch an ID
console.log(pageIndex);
console.log(pageSize);
const fetchId = ++fetchIdRef.current;
// Set the loading state
setLoading(true);
// We'll even set a delay to simulate a server here
setTimeout(() => {
// Only update the data if this is the latest fetch
if (fetchId === fetchIdRef.current) {
const startRow = pageSize * pageIndex;
const endRow = startRow + pageSize;
if (sortBy.length === 0) {
SetData(tableData.sort().slice(startRow, endRow));
} else {
SetData(
tableData
.sort((a, b) => {
const field = sortBy[0].id;
const desc = sortBy[0].desc;
if (a[field] < b[field]) {
return desc ? -1 : 1;
}
if (a[field] > b[field]) {
return desc ? 1 : -1;
}
return 0;
})
.slice(startRow, endRow)
);
}
// Your server could send back total page count.
// For now we'll just fake it, too
// setPageCount(Math.ceil(serverData.length / pageSize));
setLoading(false);
}
}, 1000);
}, []);
useEffect(() => {
listdata();
}, [])
return (
<div className="p-3 text-center">
<h4>Order Management</h4>
<WrapperTable>
<Styles>
<Table columns={columns} fetchData={fetchData} data={tableData} />
</Styles>
</WrapperTable>
</div>
)
}
export default OrderManagementScreen;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

First, you need to understand the basic difference between client-side pagination and server-side pagination.
In client-side pagination, we already have all the data for all the pages which we need to display in the table that means we know the total count as well (totalcount=pagesize*number of pages).
Now compare this with server-side pagination. We shall be getting the slice of data which we request means if we are keeping page size as 10 and we have 100 data at our server but since we requested 10 so we'll only get 10 items. Then how will the pagination component know what will be the total number of pages which he needs to display?
That's why we need a total count from the server as well when we are fetching the data.
But wait, do we need it every time? Well, it depends on your use-case. In general, we need the total count either for the first time or in case we are doing any find or filter.
Now coming to your solution-
In react-table if we did not explicitly set the flag manualPagination as true then it will process the number of pages based of your supplied data and the pagesize so it will auto handle the pagination. So we need to make this manualPagination as true in options we passed to useTable and also we need to supply the total number of pages that is pageCount. So this will be something like
useTable(
{
columns,
data,
initialState: { pageIndex: 0 },
},
useSortBy,
usePagination,
manualPagination: true,
pageCount: (totalcount/pagesize),//you can handle this calculation in your fetchdata method
);
and then add your fetch data call inside a new useEffect with your pageindex and the
pagesize as dependencies
React.useEffect(() => {
fetchData({ pageIndex, pageSize })
}, [fetchData, pageIndex, pageSize])
I hope this will solve your issue. This is also well explained in the react-table documentation with proper codeshare example. Check here

Related

Uncaught Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop in pagination

I have a data in a table and i want to add a pagination option to it. In the user interface, user should be able to choose data per page and page index that's why i added some handle methods to it and try to render it but i faced with the above error.
Whole jsx file is too long to share therefore i only shared codes which seemed important to me and replaced other codes with ... if you need anything else you can ask it in the comments.
function Activity(props) {
...
const variables = useMemo(() => ({
projectId,
language,
env: environment,
pageSize: 20,
filter,
...getSortFunction(),
}), [projectId, language, environment, filter, getSortFunction()]);
const {
data, hasNextPage, loading, loadMore, refetch,
} = useActivity(variables);
//pagination section
const [pagination, setPagination] = useState({
currentPage: 1,
dataPerPage: 10,
indexOfLastData: 9,
indexOfFirstData: 0,
})
const [totalPages, setTotalPages] = useState(1);
const [paginationString, setPaginationString] = useState(`Current page ${pagination.currentPage}, total number of data ${pagination.dataPerPage}`)
const handlePagination = (page, dataPerPage, currentPagination) => {
if (currentPagination.currentPage != page) handlePaginationCurrentPage(page, currentPagination);
if (currentPagination.dataPerPage != dataPerPage) handlePaginationDataPerPage(dataPerPage, currentPagination);
const dataToBeUsed = [...data].slice(pagination.indexOfFirstData, pagination.indexOfLastData);
setTotalPages(Math.ceil(3 / pagination.dataPerPage));
return dataToBeUsed;
}
const handlePaginationString = () => {
setPaginationString(`current page ${pagination.currentPage}, total number of data ${pagination.dataPerPage}`)
}
const handlePaginationCurrentPage = (page, pagination) => {
useEffect(() => {
setPagination({
...pagination,
currentPage: Number(page),
indexOfLastData: pagination.dataPerPage * Number(page) - 1,
indexOfFirstData: pagination.dataPerPage * Number(page) - pagination.dataPerPage
})
})
}
const handlePaginationDataPerPage = (dataPerPage, pagination) => {
useEffect(() => {
setPagination({
...pagination,
dataPerPage: dataPerPage,
indexOfLastData: dataPerPage * pagination.currentPage - 1,
indexOfFirstData: dataPerPage * pagination.currentPage - pagination.dataPerPage
})
})
}
const resetPagination = (e) => {
e.stopPropagation();
handlePagination(1, 10, pagination);
};
...
const renderActions = row => (
<ActivityActionsColumn
outdated={ isUtteranceOutdated(row.datum) }
datum={ row.datum }
handleSetValidated={ handleSetValidated }
onDelete={ handleDelete }
onMarkOoS={ handleMarkOoS }
data={ handlePagination(1, 10, pagination) }
getSmartTips={ utterance => getSmartTips({
nluThreshold, endTime, examples, utterance,
}) }
/>
);
...
const columns = [
{ key: '_id', selectionKey: true, hidden: true },
{
key: 'confidence',
style: { width: '51px', minWidth: '51px' },
render: renderConfidence,
},
{
key: 'intent',
style: { width: '180px', minWidth: '180px', overflow: 'hidden' },
render: renderIntent,
},
{
key: 'conversation-popup', style: { width: '30px', minWidth: '30px' }, render: renderConvPopup,
},
{
key: 'text',
style: { width: '100%' },
render: renderExample,
},
...(can('incoming:w', projectId) ? [
{
key: 'actions',
style: { width: '110px' },
render: renderActions,
},
] : []),
];
const renderTopBar = () => (
<div className='side-by-side wrap' style={ { marginBottom: '10px' } }>
...
<Accordion className='pagination-accordion'>
<Accordion.Title
active={ activeAccordion }
onClick={ () => handleAccordionClick() }
data-cy='toggle-pagination'
className='pagination-accordian-title'
>
<Icon name='dropdown' />
<span className='toggle-pagination'>
{ activeAccordion
? `Hide Pagination Options `
: `Show Pagination Options ` }
</span>
<span className="toggle-pagination pagination-string">
{ activeAccordion
? `${paginationString}`
: `${paginationString}` }
</span>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */ }
<span
data-cy='reset-pagination'
onClick={ e => resetPagination(e) }
role='button'
tabIndex='0'
className='reset-button'
>
<Icon name='redo' size='small' /> Reset
</span>
</Accordion.Title>
</Accordion>
</div>
);
return (
<>
{ !!openConvPopup && <ConversationSidePanel utterance={ openConvPopup } onClose={ () => setOpenConvPopup(false) } /> }
{ renderTopBar() }
{ data && data.length ? (
<>
<DataTable
ref={ tableRef }
columns={ columns }
data={ handlePagination(1, 10, pagination) }
hasNextPage={ hasNextPage }
loadMore={ loading ? () => { } : loadMore }
onScroll={ handleScroll }
selection={ selection }
onChangeSelection={ (newSelection) => {
setSelection(newSelection);
setOpenConvPopup(false);
} }
/>
)}
I can't use data in pagination beacuse it is used in so many places and in those places everything designed assumed data is in its full length so i should use it seperately (ex./ in handlePagination i get it using data.slice() function )
Thanks!
I changed
<DataTable
ref={ tableRef }
columns={ columns }
data={ handlePagination(1, 10, pagination) }
hasNextPage={ hasNextPage }
loadMore={ loading ? () => { } : loadMore }
onScroll={ handleScroll }
selection={ selection }
onChangeSelection={ (newSelection) => {
setSelection(newSelection);
setOpenConvPopup(false);
} }
/>
to
<DataTable
ref={ tableRef }
columns={ columns }
data={ dataToBeShowed }
hasNextPage={ hasNextPage }
loadMore={ loading ? () => { } : loadMore }
onScroll={ handleScroll }
selection={ selection }
onChangeSelection={ (newSelection) => {
setSelection(newSelection);
setOpenConvPopup(false);
} }
/>
and added
const [dataToBeShowed, setDataToBeShowed] = useState(data.slice(pagination.indexOfFirstData, pagination.indexOfLastData));
useEffect(() => {
setDataToBeShowed(data.slice(pagination.indexOfFirstData, pagination.indexOfLastData));
setPaginationString(`Current page ${pagination.currentPage}, total number of data ${pagination.dataPerPage}`)
setTotalPages(Math.ceil(data.length / pagination.dataPerPage));
}, [data, pagination.indexOfFirstData, pagination.indexOfLastData]);
This solved becuase I saw that handlePagination(1, 10, pagination) doesn't render in Activity but in Window which means It can't see the states, therefore i added dataToBeShown and assigned it to real data and since i added useEffect, it follows the change in data (real data).
(if you use onClick vs. you can simply add () => {handleOnBruh()} vs this way it binds itself with the file.)
Thanks!

Why is my react Table not updating the data?

I got a react Table that I want to delete or modify my data.
My code is below for all my cells.
deleteTableElement = (row) => {
let data = this.state.list.data;
data.splice(row.id, 1);
this.setState({
list:{
...this.state.list,
data:data
}
})
};
actionsCell = (columns) => {
columns.push({
Header: "Accion",
Cell: (props) => {
console.log(props);
return (
<div
style={{
display: "flex",
justifyContent: "space-around",
}}
>
<i
onClick={() => this.deleteTableElement(props.row)}
className="material-icons"
style={{ color: "red", cursor: "pointer" }}
>
delete
</i>
</div>
);
},
});
return columns;
};
In this case, I want to modify the item. The react table is not updating.
Whenever your component unexpectedly doesn't update, it's because you're mutating state. Literally 100% of the time:
let data = this.state.list.data;
data.splice(row.id, 1); // <- splice mutates an array, mutating state is bad
this.setState({
list:{
...this.state.list,
data:data
}
})
should be:
this.setState({
list:{
...this.state.list,
data:data.filter((d,i) => i !== row.id)
}
})
Here is a simple example of a table with deletable rows.
Pass your initial rows to the component as a prop and copy them to the state.
On click delete button, make a copy or rows without deleted row and update the state. The table will re-render.
const {useState} = React;
const MyTable = ({rows, columns}) => {
const [data, setData] = useState(rows); // Copy rows to the state
const deleteRow = index => {
// Create a copy of row data without the current row
const newData = [...data.slice(0, index), ...data.slice(index + 1)];
// Update state
setData(newData);
}
return (
<table cellSpacing="0" cellPadding="10">
<thead>
<tr>
{columns.map(column => (<th key={column}>{column}</th>))}
<th/>
</tr>
</thead>
<tbody>
{data.map((row, index) => (
<tr key={index}>
<td>{row.text}</td>
<td>{row.value}</td>
<td>
<button onClick={() => deleteRow(index)}>Delete Me !</button>
</td>
</tr>
))}
</tbody>
</table>
)
}
const tableRows = [
{
text: 'A',
value: 100,
},
{
text: 'B',
value: 200,
},
{
text: 'C',
value: 300,
},
{
text: 'D',
value: 400,
},
];
const tableColumns = ['Text', 'Count'];
ReactDOM.render(
<MyTable rows={tableRows} columns={tableColumns} />,
document.getElementById('container')
);
th, td {
border: 1px solid gray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
<div id="container">
</div>
thanks to #Adam and #MichaelRovinsky , theres the result and my problems are fixed with the function slice, the component state should be update with a copy
let data = this.state.list.data;
data.splice(row.id, 1);
const copy = data.slice();
this.setState({
list: {
...this.state.list,
data: copy,
},
});

How to make the visited step active?

I am making a simple react application and included react-stepper-horizontal library and things are fine.
Working Example:
Appropriate Code related to stepper:
const Form = () => {
.
.
.
const [currentPage, setCurrentPage] = useState(1);
const sections = [
{ title: 'Basic Details', onClick: () => setCurrentPage(1) },
{ title: 'Employment Details', onClick: () => setCurrentPage(2) },
{ title: 'Review', onClick: () => setCurrentPage(3) },
];
<Stepper
steps={sections}
activeStep={currentPage}
activeColor="red"
defaultBarColor="red"
completeColor="green"
completeBarColor="green"
/>
.
.
.
}
Steps to reproduce issue:
-> There are totally three steps 1,2,3 and each have different sections as Basic Details, Employment Details and Review respectively.
-> Now if user enter any of the input field in Step 1 and goes to Step 2 and fill some input fields there and goes to Step 3 to review it and if he comes back to the Step 1 again then the active state is lost in Step 3.
-> So now issue is if we want to go to step 3 then we need to again go three steps to reach last Step 3.
Requirement:
-> If user once visited any step then if he comes to any previous step then all the steps that he visited previously needs to be in active.
Eg:
-> If user landed in Step 1, then using next button , he reaches the Step 3 and if he wish to come back to Step 1 to modify some inputs and again if he wants to go to Step 3 for review step then it should be possible by clicking on the Step 3 because he already visited that step.
Kindly help me to achieve the result of making the steps in active state upto which the user visits.. If user visits Step 3 and goes back to step 1 on click of the Step 1 circle then there should be possibility to come back to Step 3 again as he already visited the Step 3..
Any solution without any library also welcomed.
This is a big issue if we have more steps (eg 7 steps). So please kindly help me.. A big thanks in advance..
Here's a simple implementation of the <Stepper /> component in question. The key is to have a tracker that tracks the visited steps internally, persist that information across re-renders.
Demoboard Playground
const { useState, useEffect, useMemo } = React;
const cx = classNames;
function range(a, b) {
return new Array(Math.abs(a - b) + 1).fill(a).map((v, i) => v + i);
}
function Stepper({ steps, activeStep, children }) {
const count = steps.length;
const listOfNum = useMemo(() => range(1, count), [count]);
const tracker = useMemo(() => {
let highestStep = 0;
function hasVisited(step) {
return highestStep >= step;
}
function addToBackLog(step) {
if (step > highestStep) highestStep = step;
}
return {
hasVisited,
addToBackLog,
getHighestStep() {
return highestStep;
},
};
}, []);
tracker.addToBackLog(activeStep);
const noop = () => {};
const prevStep = steps[activeStep - 2];
const currentStep = steps[activeStep - 1];
const nextStep = steps[activeStep];
return (
<div>
<div>
{" "}
{listOfNum.map((num, i) => {
const isActive = activeStep == num;
const isVisited = tracker.hasVisited(num);
const isClickable = num <= tracker.getHighestStep() + 1 || isVisited;
return (
<div
key={num}
className={cx("circle", {
active: isActive,
visited: isVisited,
clickable: isClickable,
})}
onClick={isClickable ? steps[i].onClick : noop}
>
{num}{" "}
</div>
);
})}{" "}
</div>{" "}
<h2> {currentStep && currentStep.title} </h2> <div> {children} </div>{" "}
<div className="footer">
{" "}
{prevStep ? (
<button onClick={prevStep.onClick}> prev </button>
) : null}{" "}
{nextStep ? <button onClick={nextStep.onClick}> next </button> : null}{" "}
</div>{" "}
</div>
);
}
function App() {
const [currentPage, setCurrentPage] = useState(1);
const sections = [
{
title: "Un",
onClick: () => setCurrentPage(1),
},
{
title: "Deux",
onClick: () => setCurrentPage(2),
},
{
title: "Trois",
onClick: () => setCurrentPage(3),
},
{
title: "Quatre",
onClick: () => setCurrentPage(4),
},
{
title: "Cinq",
onClick: () => setCurrentPage(5),
},
];
return (
<Stepper steps={sections} activeStep={currentPage}>
I 'm page {currentPage}{" "}
</Stepper>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
body {
color: #0f0035;
padding-bottom: 2rem;
}
.circle {
display: inline-flex;
height: 2em;
width: 2em;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: lightgrey;
margin: 0 0.5em;
color: white;
cursor: not-allowed;
}
.active {
background-color: rgba(50, 50, 250) !important;
}
.visited {
background-color: rgba(50, 50, 250, 0.5);
}
.clickable {
cursor: pointer;
}
.footer {
margin-top: 1em;
display: flex;
justify-content: space-between;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/classnames/2.2.6/index.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

React material-table is rerendering table with new data only if i update data twice

I am using material-table (https://material-table.com/#/) which is built with React.
I have data coming in as a prop to material-table like shown in the code below.
I usually click a button in the parent component to change the prop in the Performancetbl component. But when i click on the button once, table is not rerendering with new data. When I click on it one more time, it rerenders though. Why is that happening?
I tried to save props into a state variable state in Performancetbl component, but that did not change the behavior at all.
I also tried console.log(props.datas) to see if correct data is appearing the first time I click on the button. And it is indeed the correct value! Can you guys figure out why this is happening?
function Performancetbl(props) {
const options = {
...
};
console.log(props.datas)
return(
<div style={{ maxWidth: "100%" }}>
<MaterialTable
title="Overall"
data={props.datas}
columns={props.columns}
options={options}
components={props.components}
/>
</div>
);
}
export default Performancetbl;
Thanks!
The reason this is most likely happening to you is because you are rendering the table before data has arrived.
Please see the following demo on how to grab data from an API and pass it via props.
You can view a live demo here
ParentComponent.js
import React, { useState } from "react";
import AppTable from "./AppTable";
export default function ParentComponent() {
const [tableData, setTableData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const columns = [
{
title: "Id",
field: "id"
},
{
title: "UserId",
field: "userId"
},
{
title: "Title",
field: "title"
},
{
title: "Completed",
field: "completed"
}
];
const tableDiv = {
marginTop: "30px"
};
const shadowStyle = {
boxShadow: "0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19)"
};
const btnStyle = {
height: "40px",
width: "300px",
fontSize: "24px",
cursor: "pointer",
...shadowStyle
};
const headStyle = {
textAlign: "center",
padding: "20px",
backgroundColor: "lightcoral",
...shadowStyle
};
const sleep = time => {
return new Promise(resolve => setTimeout(resolve, time));
};
const fetchData = async () => {
setIsLoading(true);
// Add a timeout to give the appearance of long load times
await sleep(3000);
try {
const resp = await fetch("https://jsonplaceholder.typicode.com/todos");
const json = await resp.json();
setTableData(json);
} catch (err) {
console.trace(err);
alert(err.message + "\r\n\r\nSee console for more info.");
}
setIsLoading(false);
};
return (
<div>
<div style={headStyle}>
<h1>Click button to get data</h1>
<button style={btnStyle} onClick={fetchData}>
Click Me To Get API Data
</button>
</div>
<div style={tableDiv}>
<AppTable data={tableData} columns={columns} isLoading={isLoading} />
</div>
</div>
);
}
AppTable.js (uses material-table)
import React from "react";
import MaterialTable from "material-table";
import tableIcons from "./TableIcons.js";
export default function AppTable({ data, columns, ...rest }) {
return (
<MaterialTable
{...rest}
icons={tableIcons}
columns={columns}
data={data}
/>
);
}

sorting the table data when not using react state

I am trying to avoid using react state for better performance. However, i have no idea on how can i sort table data. I tried to have sort function in the column that needs sorting with the type("asc" or "desc") and based on name or title or like that. This is the configuration and code for my table
code
const sortBy = (data, key, type = "asc") => data.sort((a, b) => a[key].toLowerCase() < b[key].toLowerCase());
const columns = {
name: {
key: "name",
label: "Listing Name",
css: "color: #444; font-size: 1.1rem; font-weight: bold;",
content: (item: Object) => (
<NameColumn>
{item.url && <Avatar name={item.name} url={item.url} size={80} type="square" />}
<NameWrapper>
<Name>{item.name}</Name>
{item.location && <SubName color='#797979'>{item.location}</SubName>}
</NameWrapper>
</NameColumn>
)
},
agent: {
key: "agent",
label: "Agent",
sort: (data: Array<Object>, item: Object) => sortBy(data, item, 'desc'),
isSortable: true,
hideOnPhone: true
},
price: {
key: "price",
label: "Prices",
hideOnPhone: true
},
};
const userData = [
{
id: 1,
name: "Rahul",
location: 'Delhi',
agent: "hello man",
price: '$15000',
},
{
id: 2,
name: "Sachin Tendulkar",
location: 'Delhi',
agent: "Mumbai Inc",
price: '$15000',
},
];
const rowConfig = {
uniqueKey: "id",
css: `
height: 100px;
&:hover {
background-color: rgba(216, 216, 216, 0.2)};
}
`,
onClick: (e, item) => {
console.log("row was clicked", item);
}
};
type Props = {
location: Object
};
const Tables = ({ location }: Props) => {
const queries = new URLSearchParams(location.search);
return (
<Main>
<Table
rowConfig={rowConfig}
columns={columns}
data={userData}
totalPages={10}
currentPage={
queries.has("page") ? parseInt(queries.get("page"), 10) : 1
}
basePageLink={""}
/>
</Main>
);
};
export default Tables;
const Table = ({
columns,
data = [],
rowConfig: { uniqueKey = "id", css , onClick } = {},
currentPage,
totalPages,
basePageLink
}: Props) => {
const headerColumns = () =>
Object.keys(columns).map(key => (
<Th
key={key}
align={columns[key].align}
width={columns[key].width}
onClick={() => columns[key].isSortable && columns[key].sort(data, key)}
css={columns[key].cssHeader}
>
{columns[key].label ? columns[key].label : ""}
</Th>
));
const cell = (key, item) => (
<Td
key={key}
align={columns[key].align}
width={columns[key].width}
css={columns[key].css}
>
{columns[key].content ? columns[key].content(item) : item[key]}
</Td>
);
const row = (item: Object) => (
<Tr
key={item[uniqueKey]}
css={css}
onClick={onClick ? (e: Event) => onClick(e, item) : null}
>
{Object.keys(columns).map(key => cell(key, item))}
</Tr>
);
return (
<Main>
<T>
<thead>
<tr>{headerColumns()}</tr>
</thead>
<tbody>{data.map(i => row(i))}</tbody>
</T>
<TablePagination
currentPage={currentPage}
totalPages={totalPages}
basePageLink={basePageLink}
/>
</Main>
);
};
export default Table;
The way i am doing wont work cause i am not using the react state which will notify react that the state has changed so re-render the agent column(for now sorting is used in agent column only so).
Any suggesstions? Help would be appreciated!

Categories

Resources