I am using Material-UI Data-Grid, and I'm running a tutorial grid for server-side data access. This is written in React, and I'm having a problem that the loading circle is permanently spinning. I set a breakpoint in React.useEffect and I see it getting hit over and over and over. handlePageChange doesn't seem to be participating in the infinite loop.
Thinking about this a little, I feel that the state is changing which triggers the whole thing to execute again, hence the loop. What is the condition supposed to be to stop the thing?
import * as React from 'react';
import { DataGrid } from '#material-ui/data-grid';
function loadServerRows(page, data) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(data.rows.slice(page * 5, (page + 1) * 5));
}, Math.random() * 500 + 100); // simulate network latency
});
}
export default function App() {
const data = {
rows: [
{ id: 1, col1: 'Hello', col2: 'World' },
{ id: 2, col1: 'XGrid', col2: 'is Awesome' },
{ id: 3, col1: 'Material-UI', col2: 'is Amazing' },
],
columns: [
{ field: 'col1', headerName: 'Column 1', width: 150 },
{ field: 'col2', headerName: 'Column 2', width: 150 },
],
rowLength: 100,
maxColumns: 6
}
const [page, setPage] = React.useState(0);
const [rows, setRows] = React.useState([]);
const [loading, setLoading] = React.useState(false);
const handlePageChange = (params) => {
setPage(params.page);
};
React.useEffect(() => {
let active = true;
(async () => {
setLoading(true);
const newRows = await loadServerRows(page, data);
if (!active) {
return;
}
setRows(newRows);
setLoading(false);
})();
return () => {
active = false;
};
}, [page, data]);
return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid
rows={rows}
columns={data.columns}
pagination
pageSize={5}
rowCount={100}
paginationMode="server"
onPageChange={handlePageChange}
loading={loading}
/>
</div>
);
}
In your code data changes on each render. As an option, you can store data with useRef (or just make it global moving out of component):
import React from 'react'
import { DataGrid } from '#material-ui/data-grid'
function loadServerRows(page, data) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(data.rows.slice(page * 5, (page + 1) * 5))
}, Math.random() * 500 + 100) // simulate network latency
})
}
function App() {
const data = React.useRef({
rows: [
{ id: 1, col1: 'Hello', col2: 'World' },
{ id: 2, col1: 'XGrid', col2: 'is Awesome' },
{ id: 3, col1: 'Material-UI', col2: 'is Amazing' },
],
columns: [
{ field: 'col1', headerName: 'Column 1', width: 150 },
{ field: 'col2', headerName: 'Column 2', width: 150 },
],
rowLength: 100,
maxColumns: 6
})
const [page, setPage] = React.useState(0)
const [rows, setRows] = React.useState([])
const [loading, setLoading] = React.useState(false)
const handlePageChange = (params) => {
setPage(params.page)
}
React.useEffect(() => {
let active = true;
(async () => {
setLoading(true)
const newRows = await loadServerRows(page, data.current)
if (!active) {
return
}
setRows(newRows)
setLoading(false)
})()
return () => {
active = false
}
}, [page, data])
return (
<div style={{ height: 400, width: '100%' }}>
<DataGrid
rows={rows}
columns={data.current.columns}
pagination
pageSize={5}
rowCount={100}
paginationMode="server"
onPageChange={handlePageChange}
loading={loading}
/>
</div>
)
}
Related
I have a question regarding useState.
When I create a usestate like this =>
const [dataChart, setDataChart] = useState(dataState());
Suddenly an error message appears in the console
(Uncaught ReferenceError: Cannot access 'dataState' before initialization)
Why is that about and what solution can you give me? Thank you
MyCode =
const [filterdata, setFilterdata] = useState([]);
const [query, setQuery] = useState("");
const [dataProvinsi, setDataProvinsi] = useState([]);
const [dataKota, setDataKota] = useState([]);
const [dataKecamatan, setDataKecamatan] = useState([]);
const [dataKelurahan, setDataKelurahan] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const [isLoadingKota, setIsLoadingKota] = useState(false);
const [isLoadingKecamatan, setIsLoadingKecamatan] = useState(false);
const [selectedValue, setSelectedValue] = useState("");
const [dataChart, setDataChart] = useState(dataState());
const provinsiRef = useRef([]);
const kotaRef = useRef([]);
const kecamatanRef = useRef([]);
const getDataAllProvinsi = () => {
setIsLoading(true);
getStreetDallProvinsi()
.then((resolve) => {
setDataProvinsi(resolve);
setFilterdata(resolve);
console.log(resolve);
})
.catch((reject) => {
console.log(reject);
})
.finally(setIsLoading(false));
};
const handlesearch = (event) => {
const getSearch = event.target.value;
if (getSearch.length > 0) {
const searchdata = dataProvinsi.filter((item) =>
item.provinsi.toLowerCase().includes(event.target.value.toLowerCase())
);
setDataProvinsi(searchdata);
} else {
setDataProvinsi(filterdata);
}
setQuery(getSearch);
};
const handleProvinsi = async (index) => {
try {
const provinsi = provinsiRef.current[index].dataset.prov;
setIsLoading(true);
const result = await getStreetallKota(provinsi);
setDataKota(result);
console.log(result);
} catch (error) {
console.log("salah");
} finally {
setIsLoading(false);
}
};
const handleKota = async (provinsi, index) => {
try {
const kota = kotaRef.current[index].dataset.city;
setIsLoadingKota(true);
const result = await getStreetallKecamatan(provinsi, kota);
setDataKecamatan(result);
console.log(result);
} catch (error) {
console.log("salah");
} finally {
setIsLoadingKota(false);
}
};
const handleKecamatan = async (provinsi, kota, index) => {
try {
const kecamatan = kecamatanRef.current[index].dataset.camat;
setIsLoadingKecamatan(true);
const result = await getStreetallKelurahan(provinsi, kota, kecamatan);
setDataKelurahan(result);
console.log(result);
} catch (error) {
console.log("salah");
console.log(error);
} finally {
setIsLoadingKecamatan(false);
}
};
useEffect(() => {
getDataAllProvinsi();
}, []);
const colorCode = "#0066FF";
const colorFont = "#8E9093";
const dataState = () => ({
data: {
dataProv: {
labels: dataProvinsi.map((o) => o.provinsi),
datasets: [
{
fill: true,
label: null,
backgroundColor: colorCode,
borderColor: colorCode,
borderWidth: 2,
borderRadius: 12,
data: dataProvinsi.map((o) => o.total_street),
},
],
},
dataKota: {
labels: dataKota.map((o) => o.kota),
datasets: [
{
fill: true,
label: null,
backgroundColor: colorCode,
borderColor: colorCode,
borderWidth: 2,
borderRadius: 12,
data: dataKota.map((o) => o.total_street),
},
],
},
dataKecamatan: {
labels: dataKecamatan.map((o) => o.kecamatan),
datasets: [
{
fill: true,
label: null,
backgroundColor: colorCode,
borderColor: colorCode,
borderWidth: 2,
borderRadius: 12,
data: dataKecamatan.map((o) => o.total_street),
},
],
},
},
options: {
plugins: {
legend: {
display: false,
labels: {
font: {
color: colorFont,
},
},
},
},
scales: {
x: {
grid: {
display: false,
},
beginAtZero: false,
ticks: {
color: colorFont,
},
},
y: {
grid: {
display: false,
},
beginAtZero: true,
ticks: {
color: colorFont,
},
},
},
},
});
const plugins = [
{
beforeDraw: function (chart) {
if (chart.chartArea) {
let ctx = chart.ctx;
let chartArea = chart.chartArea;
let barArray = chart.getDatasetMeta(0).data;
ctx.fillStyle = "#B2D1FF85";
for (let i = 0; i < barArray.length; i++) {
const { x, width } = barArray[i];
ctx.fillRect(
x - width / 2,
chartArea.top,
width,
chartArea.bottom - chartArea.top
);
}
}
},
},
];
useEffect(() => {
setDataChart(dataState());
}, [selectedValue]);
You're making a call to dataState() method in your initialization well before it's defined (notice how dataState is declared something like 50 lines below where you're calling it). dataState as an undefined variable can't be invoked. Either put the definition for dataState before your useState invocation, use a better initial state for dataChart, or as it appears to me, dataChart can be derived from your other state variables and thus should not be a separate state.
the issue is in the error message itself, 'Uncaught ReferenceError: Cannot access 'dataState' before initialization'. you are trying to use dataState function before its declaration. quick solution is you can get the dataState function to outside of the component itself and declare it above the file.
and to use it as the state, your function should return something out of it also. and here you've violated the state management also. I would suggest you move the dataState function inside a useMemo hook. like this, if it's calculating anything using any state variables from props, otherwise I don't see a point to assign those values to another state also.
const dataChart = useMemo(() => ({
data: {
dataProv: {
labels: dataProvinsi.map((o) => o.provinsi),
datasets: [
{
fill: true,
label: null,
backgroundColor: colorCode,
borderColor: colorCode,
borderWidth: 2,
borderRadius: 12,
data: dataProvinsi.map((o) => o.total_street),
},
],
},
dataKota: {
labels: dataKota.map((o) => o.kota),
datasets: [
{
fill: true,
label: null,
backgroundColor: colorCode,
borderColor: colorCode,
borderWidth: 2,
borderRadius: 12,
data: dataKota.map((o) => o.total_street),
},
],
},
dataKecamatan: {
labels: dataKecamatan.map((o) => o.kecamatan),
datasets: [
{
fill: true,
label: null,
backgroundColor: colorCode,
borderColor: colorCode,
borderWidth: 2,
borderRadius: 12,
data: dataKecamatan.map((o) => o.total_street),
},
],
},
},
options: {
plugins: {
legend: {
display: false,
labels: {
font: {
color: colorFont,
},
},
},
},
scales: {
x: {
grid: {
display: false,
},
beginAtZero: false,
ticks: {
color: colorFont,
},
},
y: {
grid: {
display: false,
},
beginAtZero: true,
ticks: {
color: colorFont,
},
},
},
},
}), [])
You should return something in the dataState to assign it to a useState.
useState is used to provide some value to a state so it must receive some value from the dataState function.
I'm implementing a MUI datagrid with inline editing. Whenever a cell loses focus, the code sets the mode to 'view' (as it should) but then immediately the processRowUpdate callback is called (to send data to the endpoint) it sets the cell mode back to 'edit' meaning that the cell remains in edit mode.
Does anyone know why this is happening?
Maybe something to do with this processRowUpdate error logged to console?:
TypeError: Cannot read properties of undefined (reading 'id')
at getRowId (productTable.js:213:1)
at getRowIdFromRowModel (gridRowsUtils.js:17:1)
at useGridRows.js:106:1
at Array.forEach (<anonymous>)
at Object.updateRows (useGridRows.js:105:1)
at apiRef.current.<computed> [as updateRows] (useGridApiMethod.js:14:1)
at useGridCellEditing.new.js:334:1
Code:
export default function FullFeaturedCrudGrid(props) {
const [rows, setRows] = React.useState([]);
const [cellModesModel, setCellModesModel] = React.useState({})
const [selectedCellParams, setSelectedCellParams] = React.useState(null);
const { tableName } = props
const [snackbar, setSnackbar] = React.useState(null);
const handleCloseSnackbar = () => setSnackbar(null);
React.useEffect(() => {
console.log('useEffect called')
axios.get(`http://localhost:8000/mvhr/all`)
.then((response) => {
setRows(response.data);
})
}, [])
React.useEffect(() => {
console.log('cellModesModel',cellModesModel)
});
const handleCellFocus = React.useCallback((event) => {
const row = event.currentTarget.parentElement;
const id = row.dataset.id;
const field = event.currentTarget.dataset.field;
setSelectedCellParams({ id, field });
}, []);
const handleDeleteClick = (id) => () => {
axios.delete(`http://localhost:8000/delete_mvhr/${id}`
).then(() => {
setRows(rows.filter((row) => row.id !== id));
setSnackbar({ children: tableName + ' successfully deleted', severity: 'success' });
})
};
const handleCancel = () => {
if (!selectedCellParams) {
return;
}
const { id, field } = selectedCellParams;
setCellModesModel({
...cellModesModel,
[id]: {
...cellModesModel[id],
[field]: { mode: GridCellModes.View },
},
});
};
const processRowUpdate = React.useCallback(
(newRow) => {
axios.put(`http://localhost:8000/mvhr/`, newRow)
.then((response) => {
const updatedRow = { ...newRow, isNew: false };
setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row)));
setSnackbar({ children: tableName + ' successfully saved', severity: 'success' });
return updatedRow
})
});
const handleProcessRowUpdateError = React.useCallback((error) => {
setSnackbar({ children: error.message, severity: 'error' });
}, []);
const columns = [
{ field: 'description', headerName: 'description', width: 180, editable: true },
{ field: 'elec_efficiency', headerName: 'elec_efficiency', type: 'number', editable: true },
{ field: 'heat_recovery_eff', headerName: 'heat_recovery_eff', type: 'number', editable: true },
{ field: 'range_low', headerName: 'range_low', type: 'number', editable: true },
{ field: 'range_high', headerName: 'range_high', type: 'number', editable: true },
{ field: 'superseded', headerName: 'superseded', type: 'boolean', editable: true },
{
field: 'actions',
type: 'actions',
headerName: 'Actions',
width: 100,
cellClassName: 'actions',
getActions: ({ id }) => {
return [
<GridActionsCellItem
icon={<DeleteIcon />}
label="Delete"
onClick={handleDeleteClick(id)}
color="inherit"
/>,
];
},
},
];
return (
<Box
sx={{
height: '100vh',
width: '100%',
'& .actions': {
color: 'text.secondary',
},
'& .textPrimary': {
color: 'text.primary',
},
}}
>
<StripedDataGrid
rows={rows}
columns={columns}
processRowUpdate={processRowUpdate}
onProcessRowUpdateError={handleProcessRowUpdateError}
onCellEditStop={handleCancel}
cellModesModel={cellModesModel}
onCellModesModelChange={(model) => setCellModesModel(model)}
components={{
Toolbar: AddToolbar,
}}
componentsProps={{
toolbar: { setRows, setSnackbar, tableName },
cell: {
onFocus: handleCellFocus,
},
}}
experimentalFeatures={{ newEditingApi: true }}
getRowClassName={(params) =>
params.indexRelativeToCurrentPage % 2 === 0 ? 'even' : 'odd'
}
/>
{!!snackbar && (
<Snackbar
open
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
onClose={handleCloseSnackbar}
autoHideDuration={6000}
>
<Alert {...snackbar} onClose={handleCloseSnackbar} />
</Snackbar>
)}
</Box>
);
}
Solved it. I refactored the code to return the updatedRow synchronously rather than as a callback to the axios request:
const processRowUpdate = React.useCallback(
(newRow) => {
const updatedRow = { ...newRow, isNew: false };
setRows(rows.map((row) => (row.id === newRow.id ? updatedRow : row)));
setSnackbar({ children: tableName + ' successfully saved', severity: 'success' });
axios.put(`http://localhost:8000/mvhr/`, newRow)
return updatedRow
});
I am using react for a school project. The dynamic data does not binds to Options from Ant Design, react js. The result is an empty option list. In actual the data comes from an external API. For testing purpose I assigned the data the state variable. The data comes in 2D array, so I am mapping through the data twice.
Result is:
import { useEffect, useState } from "react";
import { Select } from "antd";
const { Option } = Select;
const Complete = () => {
const [list, setPersons] = useState([
[
{
id: 1,
personName: "Owan",
},
{
id: 2,
personName: "More",
},
{
id: 3,
personName: "Jaila",
},
{
id: 4,
personName: "Eerov",
},
],
[
{
id: 5,
personName: "Rell",
},
{
id: 6,
personName: "Juko",
}
]
]);
useEffect(() => {
console.log(list);
}, []);
return (
<Select
showSearch
style={{ width: 200 }}
placeholder="Select a person"
optionFilterProp="children"
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{list.map((l) => {
l.map((person) => {
console.log(person);
<Option value={person.id}>
{person.personName}
</Option>;
});
})}
</Select>
);
};
export default Complete;
You just needed to update small one using ES6 feature here the script to combine 2 dimensional array to single array
{[].concat(...list).map((l) => <Option value={l.id}>{l.personName}</Option>)}
Here the full script:
import { useEffect, useState } from "react";
import { Select } from "antd";
const { Option } = Select;
const Complete = () => {
const [list, setPersons] = useState([
[
{
id: 1,
personName: "Owan",
},
{
id: 2,
personName: "More",
},
{
id: 3,
personName: "Jaila",
},
{
id: 4,
personName: "Eerov",
},
],
[
{
id: 5,
personName: "Rell",
},
{
id: 6,
personName: "Juko",
}
]
]);
useEffect(() => {
console.log(list);
}, []);
return (
<Select
showSearch
style={{ width: 200 }}
placeholder="Select a person"
optionFilterProp="children"
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{[].concat(...list).map((l) => <Option value={l.id}>{l.personName}</Option>)}
</Select>
);
};
export default Complete;
visit live example demo
Try with the following code:
import { useEffect, useState } from "react";
import { Select } from "antd";
const { Option } = Select;
const Complete = () => {
const [list, setPersons] = useState([
[
{
id: 1,
personName: "Owan"
},
{
id: 2,
personName: "More"
},
{
id: 3,
personName: "Jaila"
},
{
id: 4,
personName: "Eerov"
}
],
[
{
id: 5,
personName: "Rell"
},
{
id: 6,
personName: "Juko"
}
]
]);
useEffect(() => {
console.log(list);
}, [list]);
return (
<Select
showSearch
style={{ width: 200 }}
placeholder="Select a person"
optionFilterProp="children"
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{list.map((l) => (
<>
{l.map((person) => (
<Option value={person.id}>{person.personName}</Option>
))}
</>
))}
</Select>
);
};
export default Complete;
This is my code:
import { SetCommonButtons} from "../../../../Utils/Constants";
const Main = () => {
const setButton = (cell, row) => {
return SetCommonButtons(row)
};
const Columns = [
{
dataField: "status",
text: "Action",
editable: false,
formatter: setButton,
},
];
const FetchData = () => {
List(Id).then((response) => {
});
};
};
export default Main;
How can i refresh the react-table upon editing/deleting a data (server-side based), Here's my code so far
import React,{useState,useEffect} from 'react'
import { useTable, usePagination } from 'react-table'
// import {TableContainer} from './?'
import Table from './TableContainer'
import axios from 'axios';
// Let's add a fetchData method to our Table component that will be used to fetch
// new data when pagination state changes
// We can also add a loading state to let our table know it's loading new data
function App() {
const columns = React.useMemo(
() => [
{
Header: 'Name',
columns: [
{
Header: 'name',
accessor: 'name',
},
{
Header: 'Last Name',
accessor: 'lastName',
},
],
},
{
Header: 'Info',
columns: [
{
Header: 'Age',
accessor: 'age',
},
{
Header: 'Visits',
accessor: 'visits',
},
{
Header: 'Status',
accessor: 'status',
},
{
Header: 'Profile Progress',
accessor: 'progress',
},
{
Header: 'Action',
accessor: '',
id: 'edit',
accessor: 'id',
Cell: ({value}) => (<button onClick={() => editData(value)} data-id={value}>Edit</button>)
},
],
},
],
[]
)
// We'll start our table without any data
const [data, setData] = React.useState([])
const [loading, setLoading] = React.useState(false)
const [pageCount, setPageCount] = React.useState(0)
const [ totalPages, setTotalPages ] = useState(0);
const [ sizePerPage, setSizePerPage ] = useState(10);
const [ page, setPage ] = useState(0);
const [ filtered, setFiltered ] = useState("");
const fetchIdRef = React.useRef(0)
const fetchData = React.useCallback(({ pageSize, pageIndex,filtered }) => {
console.log(filtered);
setLoading(true);
axios.get(`http://127.0.0.1:8000/api/schools?q=${filtered}&sizePerPage=${pageSize}&page=${pageIndex+1}`)
.then((res)=> {
setData(res.data.data);
setSizePerPage(res.data.meta.per_page)
setPage(res.data.meta.current_page)
setPageCount(Math.ceil(res.data.meta.total / pageSize))
setLoading(false)
});
}, [])
function editData(name) {
setLoading(true);
console.log(name)
console.log(sizePerPage);
axios.delete(`http://127.0.0.1:8000/api/schools/${name}`)
.then((res) =>{
// fetchData(sizePerPage);
})
}
return (
<Table
columns={columns}
data={data}
fetchData={fetchData}
loading={loading}
pageCount={pageCount}
filtered={filtered}
filterable
/>
)
}
export default App
When I call the fetch data upon the response, it can't access the callback param of pageSize,pageIndex, filtered I've already try to call it with params but it gives me an error that the pageSize,pageIndex are now undefined
What am I doing wrong?