I'm trying to build a React-Table which can make polling to a remote server every second to fetch newest data. I just followed what the author did in the doc (https://react-table.js.org/#/story/server-side-data) and tried integrate the polling function (setInterval) in "componentDidMount" but it FAILED.
The error message shows that when running "requestData" under "componentDidMount", "filtered" is undefined, whose length is not accessible. How can I fix that? Thank you.
import React from 'react';
import _ from 'lodash'
import ReactTable from "react-table";
import 'react-table/react-table.css'
const requestData = (pageSize, page, sorted, filtered) => {
return fetch(
'http://127.0.0.1:5000/agent',
{ method: 'GET'}
).then( res => res.json()
).then( filteredData => {
if (filtered.length) {
filteredData = filtered.reduce((filteredSoFar, nextFilter) => {
return filteredSoFar.filter(row => {
return (row[nextFilter.id] + "").includes(nextFilter.value);
});
}, filteredData);
}
const sortedData = _.orderBy(
filteredData,
sorted.map(sort => {
return row => {
if (row[sort.id] === null || row[sort.id] === undefined) {
return -Infinity;
}
return typeof row[sort.id] === "string"
? row[sort.id].toLowerCase()
: row[sort.id];
};
}),
sorted.map(d => (d.desc ? "desc" : "asc"))
);
const res = {
rows: sortedData.slice(pageSize * page, pageSize * page + pageSize),
pages: Math.ceil(filteredData.length / pageSize)
};
return res;
});
};
class AgentTable extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
pages: null,
// loading: true,
};
this.fetchData = this.fetchData.bind(this);
}
fetchData(state, instance) {
// this.setState({
// loading: true
// });
requestData(
state.pageSize,
state.page,
state.sorted,
state.filtered
).then(res => {
this.setState({
data: res.rows,
pages: res.pages,
// loading: false,
})
})
}
componentDidMount() {
setInterval(
() => requestData(
this.state.pageSize,
this.state.page,
this.state.sorted,
this.state.filtered
).then(res => {
this.setState({
data: res.rows,
pages: res.pages,
// loading: false,
})
}), 5000
);
}
render() {
const { data, pages, loading } = this.state;
return (
<div>
<ReactTable
columns={[
{
Header: "Agent ID",
accessor: "AGENTID"
},
{
Header: "Description",
accessor: "DESCRIPTION"
},
{
Header: "Domain",
accessor: "DOMAIN"
},
{
Header: "Register Time",
accessor: "REGTIME"
},
{
Header: "Status",
accessor: "STATUS"
},
]}
manual // Forces table not to paginate or sort automatically, so we can handle it server-side
data={data}
pages={pages} // Display the total number of pages
loading={loading} // Display the loading overlay when we need it
onFetchData={this.fetchData} // Request new data when things change
filterable
defaultPageSize={20}
className="-striped -highlight"
/>
</div>
);
}
}
export default AgentTable;
First off, you need to understand what the onFetchData callback is.
Taken from the docs, onFetchData is:
This function is called at componentDidMount and any time sorting, pagination or filterting is changed in the table
So what you're trying to achieve here won't work the way tried to.
Seeing as fetching data from a remote server every second isn't one of the conditions that invoke the onFetchData callback, you should try a different approach.
I forked React-Table's Simple Table example and added timed data requests here, this should help you get started.
Related
I am using MUIDatatable on a next js app. I have implemented server side pagination where I send the offset value and the limit as url params , and the data is returned as it should by the api -on each page change. However, the data is only displayed on the first page. When I navigate to the next page, i shows -"no matching records exist" - despite the data being returned by the api for that specific page.
Also when I click to go back to the first page, the page 2 data displays in a flash on the first page before it defaults to the actual page 1 data. Could someone point me to what I have missed ?
Datatable.jsx
const Datatable = () => {
const [data, setData] = useState([]);
const [isLoaded, setIsLoaded] = useState(false);
const [page, setPage] = useState(0);
const offset = page * 10
const getData = async () => {
try {
const response = await axios.get(`${process.env.url}/provider/api/v1/data&offset=${offset}&limit=10`)
setData(response.data.items)
setIsLoaded(true);
} catch (error) {
console.log(error)
setIsLoaded(true);
}
}
}
const columns = [name: "xxx", label: "XXXX", options: {}]
const options = {
viewColumns: true,
selectableRows: "multiple" ,
fixedSelectColumn: true,
tableBodyHeight: 'auto',
onTableChange: (action, tableState) => {
if (action === "changePage") {
setPage(tableState.page);
setIsLoaded(false);
} else {
console.log("action not handled.");
}
},
serrverSide: true,
count: 100,
textLabels: {
body: {
noMatch: isLoaded?"Sorry, no matching records exist"
:<div className="flex-grow-1 d-flex align-items-center justify-content-end">
<FadeLoader />
</div>,
toolTip: "Sort",
columnHeaderTooltip: column => `Sort for ${column.label}`
},
pagination: {
next: "Next Page",
previous: "Previous Page",
rowsPerPage: "Rows per page:",
displayRows: "of",
},
viewColumns: {
title: "Show Columns",
titleAria: "Show/Hide Table Columns",
},
}
}
useEffect(() => {
getData();
}, [page])
return (
<MUIDataTable
columns={columns}
data={data}
options={options}
/>
)
export default Datatable
setData(response.data.items) overrides the previous data, that is why you only see the data on the first page. You could fix this by changing it to.
setData(prevData => [...prevData, ...response.data.items]);
What worked for me here was I had not set serverSide: true, in the options
I'm using Paginated for showing data and the user can remove the item. user after a click on the button remove send request delete and get response success.
I want to remove the item in catch react-query.
I don't want to use method refetch
get all items on the server :
const useGetAll = () =>
useQuery(['applications/getAll', page], () => axios.get<GetAllApplication>('localhost:...', { params: { page } }), {
keepPreviousData: true,
})
interface response data
interface GetAllApplication {
hasError: boolean
data: {
meta: {
itemsPerPage: number
totalItems: number
currentPage: number
totalPages: number
sortBy: [['id', 'DESC']]
}
response: {
id: number
name: string
status: 'enable' | 'disable'
}[]
}
}
remove item request with useMutation :
const useRemoveApplication = () =>
useMutation('applications/remove', removeApplication, {
onSuccess({ message },id ) {
toast(message, { type: 'success' })
},
})
You should use queryClient's method setQueryData in your onSuccess.
Reference: react-query docs
Im getting an error Unhandled Rejection (Error): Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops. when I try to set state in my retrieveRoleMembers function not sure how to fix it, any feedback is appreciated!
class MainCard extends Component {
state = {
userResponseData:[] ,
roleResponseDataID:[]
}
handleChange = (tab) => {
window.alert(`Tab changed to: ${tab}`);
};
retrieveRoleMembers(){
var i,j;
for (i = 0; i < this.props.userRoleDataValue.length; i++) {
if(this.props.userRoleDataValue[i].role_id === this.state.roleResponseDataID.id){
for(j=0;j<this.state.userResponseData.length;j++){
if(this.props.userRoleDataValue[i].user_id === this.state.userResponseData[j].id)
{
this.setState({ // This is where the error is happening
outputRoleMembers: this.state.userResponseData[j],
})
}
}
}}}
componentDidMount() {
this.props.getComponentById(VIEW_ROLES, Roles, this.props.searchValue.value).then(() => {
return this.setState({
roleResponseDataID: this.props.roles.data,
cardHandle: false,
})
});
this.props.fetchComponent([IS_FETCHING_DBUSERS, FETCH_DBUSERS_SUCCESS], users)
.then(() => {
return this.setState({
userResponseData: this.props.users.data,
})
});
}
render() {
if (this.props.cardHandle) {
return null
}
else {
if (this.props.sendOptionSelected === 'Role') {
this.retrieveRoleMembers()
return (
<Card mr={'0px'}>
<Tabs defaultActiveTab="Members" onChange={this.handleChange} >
{/* Group of tabs */}
<Tabs.Tab label="Members">Members</Tabs.Tab>
<Tabs.Tab label="Access">Access</Tabs.Tab>
{/* Tab panels */}
<Tabs.Panel label="Members">
<Table
data={Array.isArray(this.state.outputRoleMembers) ? this.state.outputRoleMembers : [this.state.outputRoleMembers]}
defaultPageSize={[this.state.outputRoleMembers].length}
columns={
[
{
Header: 'Fisrt Name',
accessor: 'first_name'
},
{
Header: 'Last Name',
accessor: 'last_name'
}
]
}
sortable={false}
resizable={false}
showPagination={false}
onSortedChange={() => { }}
/>
</Tabs.Panel>
</Tabs>
</Card>
)
}
}
}
const mapStateToProps = state => {
return {
roles: state.roles.item,
users: state.users
}
}
export default connect(mapStateToProps, { getComponentById,fetchComponent })(MainCard);
and when I change retrieveRoleMembers to look like so, my code works but when I inspect the console log I see a infinite loop / renders for VIEW_DBUSERS
retrieveRoleMembers(){
var i;
for (i = 0; i < this.props.userRoleDataValue.length; i++) {
if(this.props.userRoleDataValue[i].role_id === this.state.roleResponseDataID.id){
this.props.getComponentById(VIEW_DBUSERS, users, this.props.userRoleDataValue[i].user_id).then(() => {
return this.setState({
outputRoleMembers: this.props.users.data,
})
});
}}}
The problem is you are calling function inside render method. That sets the State and calls the render method again and so on. So it created a loop.
Hence you get
Unhandled Rejection (Error): Maximum update depth exceeded
I put everything inside componentDidMount by making an async function.
componentDidMount() {
this.preFetchData();
}
preFetchData async () { // made this async function.. using await to make code sync
await this.props.getComponentById(VIEW_ROLES, Roles, this.props.searchValue.value);
await this.props.fetchComponent([IS_FETCHING_DBUSERS, FETCH_DBUSERS_SUCCESS], users);
this.setState({ roleResponseDataID: this.props.roles.data, cardHandle: false, userResponseData: this.props.users.data }, () => {
this.retrieveRoleMembers(); // call your method here
});
}
I am using react-admin and I need to control directly the store from one resource, in my case, the orders resource.
Everytime I run the GET_LISTit appends the new records in the list from the store, but, I would like to get a new list from the server and discard the old ones. Here`s where I retrieve the records:
dataProvider(GET_LIST, 'orders', {
filter: { updatedAt: filterDate }, // Get date from Filter.
sort: { field: 'updatedAt', order: 'DESC' },
pagination: { page: 1, perPage: 999 },
}).then(response => response.data)
So, I decided to manipulate the store directly and after some digging I saw this answer and this code from the source:
const dataReducer: Reducer<RecordSetWithDate> = (
previousState = initialState,
{ payload, meta }
) => {
if (meta && meta.optimistic) {
if (meta.fetch === UPDATE) {
const updatedRecord = {
...previousState[payload.id],
...payload.data,
};
return addRecords([updatedRecord], previousState);
}
if (meta.fetch === UPDATE_MANY) {
const updatedRecords = payload.ids.map(id => ({
...previousState[id],
...payload.data,
}));
return addRecords(updatedRecords, previousState);
}
if (meta.fetch === DELETE) {
return removeRecords([payload.id], previousState);
}
if (meta.fetch === DELETE_MANY) {
return removeRecords(payload.ids, previousState);
}
}
if (!meta || !meta.fetchResponse || meta.fetchStatus !== FETCH_END) {
return previousState;
}
switch (meta.fetchResponse) {
case GET_LIST:
case GET_MANY:
case GET_MANY_REFERENCE:
return addRecords(payload.data, previousState);
case GET_ONE:
case UPDATE:
case CREATE:
return addRecords([payload.data], previousState);
default:
return previousState;
}
};
So, based on that, I created a custom action to delete the old ids from my list and add the new ones retrieved from the data source:
import {GET_LIST, DELETE_MANY, FETCH_END } from 'react-admin';
export const UPDATE_ORDER_ADMIN = 'UPDATE_ORDER_ADMIN';
export const update_orders_admin = (data, oldIDS) => ({
type: UPDATE_ORDER_ADMIN,
payload: { data, ids: oldIDS },
meta: {
resource: 'orders',
optimistic: true,
fetch: DELETE_MANY,
fetchResponse: GET_LIST,
fetchStatus: FETCH_END,
},
});
And I am using this custom action after retrieve data from the backend:
dataProvider(GET_LIST, 'orders', {
filter: { updatedAt: filterDate }, // Get date from Filter.
sort: { field: 'updatedAt', order: 'DESC' },
pagination: { page: 1, perPage: 999 },
}).then(response => response.data)
.then(data => {
const ids = orders ? Object.keys(orders) : [];
update_orders_admin(data, ids);
this.setState({ isLoading: false })
return null;
});
However, the system is calling the DELETE action from backend, trying to delete the records from the database, while, what I would like is just delete these records from my view.
Any thoughts?
In your custom action you have the fetch set as DELETE_MANY which will do a loop over every id performing DELETE operation. Not sure if you implementation will work, but the current error is about that. You could try to remove the fetch ans see what happens, but I think without it he will not fetch records. If I'm not mistaken RA only adds new ids to data, however if data changed in the meantime I don't think it will replace the changed data for that you need to reimplement the data provider to change the update data behaviour which is similar to what you're trying.
Need help please... I have a fetchData function, getting the data from the DB Table Matricula, I just need to capture the records that have the date = Today
How can I only receive data where the date is the same as the current day?
class Matricula extends Component {
state = {
datos:[],
today: new Date()
}
componentDidMount = () => {
this.fetchData()
}
fetchData = async () => {
try {
const response = await getAll('matricula')
console.log("ver: ", response.data);
if (response.data.fecha.toLocaleString() === this.state.today.toLocaleDateString()) { // no se que me falta
this.setState({
status: "done",
datos: response.data,
});
}
} catch (error) {
this.setState({
status: "error"
});
}
};
render() {
const data = this.state.matriculas;
return (
<ReactTable
data={data}
contentEditable
filterable
collapseOnDataChange={false}
columns={[
{
Header: "Id",
accessor: "id"
},
{
Header: "Name",
accessor: "Name"
},
{
Header: "Date",
accessor: "date",
id: "date",
}
]
}
defaultPageSize={14}
className="-striped -highlight"
/>
)}
export default Matricula;
the getAll funcion is
export function getAll(entity){
return axios({
method: 'get',
baseURL: API_URL,
headers: headers(),
url: entity,
})
}
The optimal way would be to ask for the data that you need, that means asking only for the matriculas of today.
If you can't change this, what you should do is filter them before storing them in the state, something like this:
this.setState({
status: "done",
datos: response.data.filter((matricula)=>{
return matricula.date === this.state.today //not a proper dates comparison
}),
});
Here I'm assuming that your matriculas have an attribute date and I'm comparing it to your this.state.today to filter them out. Keep in mind that you should do a proper date comparison, and that depends on the format you are storing your data, this should help