Hi I'm a beginner in reactjs, I'm trying map array and insert the file into another array, and after insert, I map the file into the table, but I got Error "Maximum update depth exceeded"
This is my code
import React, { useEffect, useState } from "react";
import "../Components.css";
import { MDBDataTable } from "mdbreact";
import AuthService from "../../Services/AuthService";
// import AuthService from "../Services/AuthService";
export default function Dataadmin() {
const [Searchfile, setSearchfile] = useState([]);
const [data, setData] = useState({
columns: [
{
label: "No",
field: "no",
sort: "asc",
},
{
label: "Title",
field: "title",
sort: "asc",
},
{
label: "Singer",
field: "singer",
sort: "asc",
},
{
label: "Genre",
field: "genre",
sort: "asc",
},
{
label: "Country",
field: "country",
sort: "asc",
},
{
label: "Action",
field: "action",
sort: "asc",
},
],
rows: [],
});
AuthService.getalldata().then((res) => {
setSearchfile(res.data);
});
useEffect(() => {
Searchfile.map((item, index) => {
const cloned = { ...data };
cloned.rows.push({
no: index + 1,
title: item.title,
singer: item.singer,
genre: item.genre,
country: item.country,
action: (
<>
<button className="btn-action">
<i className="fas fa-pencil-alt"></i>
</button>
<button className="btn-action" style={{ marginLeft: "1vh" }}>
<i className="far fa-trash-alt"></i>
</button>
</>
),
});
setData(cloned);
});
}, [Searchfile, data]);
return (
<div className="div-admin">
<div className="table-adminss">
<MDBDataTable
className="mytable-admin"
striped
bordered
small
data={data}
/>
</div>
</div>
);
}
Can someone explain to me why I get an error and how to fix it? , hope you guys understand what I'm asking :D
There are two scenarios in react in which a component re-renders
When the state changes (In this case, an example is searchFile,)
When the props changes ( The properties passed to the component)
The function
AuthService.getalldata().then((res) => {
setSearchfile(res.data);
});
is called each time the component renders calling the setSearchfile. So once the setSearchfile is called the component re-renders once again calling the same function mentioned above(Authservice.getAllData()).This process repeats. So this will result in an infinite loop which the browser cannot handle. Hence you get the above error.
Moving the (Authservice.getAllData()) into the method of useEffect should solve the maximum update depth exceeded.
It causes because of you set setData inside a loop.
One you can do, you can store the array inside a const and setData(...data, *that const*) outside of the loop. you don't need loop here actually. As far I know MDDataTable map data itself.
according to your code you can simply do it,
useEffect(() => {
setData(...data, Searchfile) //if your Searchfile contains data
});
}, [Searchfile, data]);
Hope you got this.
Related
Why is my setSelected useState not accepting the data from the .map function? I have the following react js code:
const [ selected, setSelected ] = useState(null)
const sectionItems = [
{ id: "1", title: "title1", description: "description1" },
{ id: "2", title: "title2", description: "description2" },
{ id: "3", title: "title3", description: "description3" },
]
I am mapping through the sectionItems and rendering a modal, based on if selected has an item or not:
{sectionItems.map((section, index) => {
return (
<div key={section.id} className="processSection1" onClick={setSelected(section) >
<div className="processTitle" >{section.title}</div>
</div>
)
})}
{selected ? <Modal title={selected.title} description={selected.description} /> : " "}
Problem: Why cant I pass the data into setSelected? Or the more precise question is, how can I render the modal with each sectionItem?
Also am getting this error: Too many re-renders. React limits the number of renders to
prevent an infinite loop.
you have to use onClick like this
onClick={()=>setSelected(section)}
If you want to add a value to a function you should use an inline function inside the onClick. Right now you are triggering the function for each rendering at render time.
Change:
onClick={setSelected(section)}
to:
onClick={() => setSelected(section)}
We are using MUI DataGrid in our React based project.
At the moment I am trying to save/persist state of columns after toggling visibility of some columns with DataGrid's toolbar column menu, as currently after re-render it is back to default column setup.
I would like to know how could I access state of DataGrid/visibility state of columns in DataGrid so I can adjust/save it/reuse it later?
So far I meddled a bit with a apiRef, but all I got from apiRef.current was empty object. I am adding below some basic codeSandbox example to show how I tried to access it.
https://codesandbox.io/s/datagridprodemo-material-demo-forked-189j9?file=/demo.js
Maybe there is better/different approach, or I just need to create the state somehow. We would like to persist the state of the columns as user preference possibly in a future so this is vital for that to happen.
All suggestions are welcome and I thank you beforehand.
Fortunately, the DataGrid API provides the columnVisibilityModel and onColumnVisibilityChange props.
See this code sandbox for a simple example of controlling the columnVisibilityModel: https://codesandbox.io/s/mui-datagrid-oncolumnvisibilitychange-savestate-u1opzc?file=/src/App.tsx:1960-1984
Here is the code for a simple implementation. Your initial state may vary. Also, note that I could not figure out how to get DataGridPro to call onColumnVisibilityChange unless columnVisibilityModel was initially undefined. Bug, or my mistake, I am uncertain.
import "./styles.css";
import React from "react";
import {
DataGrid,
GridRowsProp,
GridColDef,
GridCallbackDetails,
MuiEvent,
GridColumnVisibilityModel,
GridColumnVisibilityChangeParams
} from "#mui/x-data-grid";
import { Button } from "#mui/material";
const rows: GridRowsProp = [
{ id: 1, col1: "Hello", col2: "World" },
{ id: 2, col1: "DataGridPro", col2: "is Awesome" },
{ id: 3, col1: "MUI", col2: "is Amazing" }
];
const columns: GridColDef[] = [
{ field: "col1", headerName: "Column 1", width: 150 },
{ field: "col2", headerName: "Column 2", width: 150 }
];
const initialVisibilityModel = { col1: true, col2: true };
export default function App() {
// it is strange, but in order for DataGridPro to call onColumnVisibilityChange, columnVisibilityModel must be undefined initially
const [
currentGridColumnVisibilityModel,
setCurrentGridColumnVisibilityModel
] = React.useState<GridColumnVisibilityModel | undefined>(undefined);
const [mySavedValue, setMySavedValue] = React.useState<
GridColumnVisibilityModel | undefined
>(undefined);
const onColumnVisibilityChange = React.useCallback(
(
params: GridColumnVisibilityChangeParams,
event: MuiEvent<{}>,
details: GridCallbackDetails
): void => {
console.log("params", params);
setCurrentGridColumnVisibilityModel((s) => ({
// per the DataGridPro strangeness, we must marry in initial state only the first update
...(s ? s : initialVisibilityModel),
[params.field]: params.isVisible
}));
},
[]
);
const saveACopyOfGridState = () => {
setMySavedValue(currentGridColumnVisibilityModel || initialVisibilityModel);
};
const loadSavedCopyOfGridState = () => {
setCurrentGridColumnVisibilityModel(mySavedValue || initialVisibilityModel);
};
const currentVisibilityAsText =
`${Object.keys(currentGridColumnVisibilityModel ?? {}).map(
(key) => `{${key}:${currentGridColumnVisibilityModel?.[key]}}`
)}` || "empty";
const savedVisibilityAsText =
`${Object.keys(mySavedValue ?? {}).map(
(key) => `{${key}:${mySavedValue?.[key]}}`
)}` || "empty";
return (
<div style={{ height: 300, width: "100%" }}>
<DataGrid
rows={rows}
columns={columns}
columnVisibilityModel={currentGridColumnVisibilityModel}
onColumnVisibilityChange={onColumnVisibilityChange}
/>
<div>
<Button onClick={saveACopyOfGridState} variant="contained">
SAVE CURRENT COLUMN VISIBILITY STATE
</Button>
<Button
onClick={loadSavedCopyOfGridState}
variant="contained"
color="warning"
>
LOAD SAVED COLUMN VISIBILITY STATE
</Button>
</div>
<p>Current filter state: {currentVisibilityAsText}</p>
<p>Saved filter state: {savedVisibilityAsText}</p>
</div>
);
}
I made a hook to persist column settings to localStorage. It uses callbacks on the API ref object. Usage:
function MyGrid() {
const apiRef = useGridApiRef()
usePersistColumnSettings(apiRef, 'customers-grid')
return <DataGrid apiRef={apiRef} />
}
I have a datagrid using MUI and I have came across a use case where I need to hide one of the columns if I do not have a certain role. Here is the code.
const hideColumn = () => {
const globalAdmin = auth.verifyRole(Roles.Admin);
if(!globalAdmin){
return true;
}
return false;
};
const columns = [
{ field: 'id', headerName: 'ID', width: 100 },
{ field: 'name',
headerName: 'Client code',
flex: 1,
hide: hideColumn,
renderCell: (params) => {
return params.getValue("name");
},
},
];
I'm confused on why this is not working. If I just use hide:true or hide:false it works but I need have an if statement to check the credentials first and this can't be done in the renderCell (or at least I can't get it to work). Does anyone know how to do this correctly?
You should call it as function hide: hideColumn()
Or you can create a function to retrieve your columns and pass props to this function. Then you just have to push your columns according to criteria:
export default function getColumns (product, isHide, t) {
let columns = [
{
field: 'organizationName',
headerName: headerNameOrganization,
flex: 0.3,
},
]
if (!product) {
columns.push(
{
field: 'status',
headerName: headerNameStatus,
flex: 0.2,
filterable: false,
})
}
return columns
}
Use the prop columnVisibilityModel of <DataGrid /> to control the column visibility dynamically.
The prop can also have a listener.
You can use the onColumnVisibilityModelChange prop to listen to the changes to the visible columns and update the prop accordingly.
(MUI doc)
At first, I was using the initialState (as the ff), but MUI said in their documentation:
The initialState can only be used to set the initial value of the state, the grid will not react if you change the initialState value later on.
If you need to fully control specific models, use the control props instead...
<DataGrid
columns={columns}
rows={rows}
loading={!users.length}
initialState={{
columns: {
columnVisibilityModel: {
myColumn1: isAdmin(user),
myColumn2: isAdmin(user),
},
},
}}
/>
I wanna do this in one of my conditional functions, but it's completely erroring out. Even if I assign it to a variable it says "cannot find namespace 'document'"
document.getElementById("dropdown").selectedIndex = "1";
And when I put that in my function it says Object is possibly null, so how do I reference this in a react tsx file?
I have a select statement that I need to dynamically select the default value based on some conditions. If those conditions are true, then it will run this document selector to select the index of the specified value. I just need to find a way to run a function that selects the default value of this select statement though, that's my goal
<Select
value={action}
variant="outlined"
classes={{ select: classes.selectOutlined }}
id="dropdown"
displayEmpty
inputProps={{ 'aria-label': 'Action' }}
onChange={(event) => handleActionChange(event.target.value as string)}
>
<MenuItem value="Action" disabled>
Choose an Action
</MenuItem>
{actions.map((action) => {
return <MenuItem key={action.text} value={action.text}>{action.text}</MenuItem>
})}
</Select>
And then this is the function that creates those menu items, as well as the conditionals to set the default value:
const actions = useMemo(() => {
let allActions: Array<{ text: string, value: string }> = [
{ text: 'Notify SME for Review', value: 'sme' },
{ text: 'Return for Review', value: 'review' },
{ text: 'Notify Sales for Review', value: 'notify' },
{ text: 'Release to Agent', value: 'release' }
];
if (groups.includes('SME')) {
allActions = [{ text: 'Notify Sales for Review', value: 'notify' }, { text: 'Return for Review', value: 'review' },]
} else if (groups.includes('IT')) {
allActions = [{ text: 'Notify SME for Review', value: 'sme' },]
} else if (groups.includes('Sales')) {
allActions = [{ text: 'Release to Agent', value: 'release' }]
}
// This will select the second item in the select element when it's IT or Sales
if (groups.includes('IT') || groups.includes('Sales')) {
const dropdown = document.getElementById('dropdown')!.selectedIndex = "1";
}
return allActions;
}, [groups]);
The problem you're facing is solved by understanding React unidirectional data flow. Your source of truth should come from above, always.
In more concrete words, you wouldn't read or write directly to your #select element, but instead you'd have an state value for it, which would be your source of truth:
const MyComponent = ({ groups }) => {
const [selectedIndex, setSelectedIndex] = useState(0) // or the default value
/**
* In here, you'll let react know that you want some code to run, when
* this property changes in the outside world
*/
useEffect(() => {
if (groups.includes('IT') || groups.includes('Sales')) {
setSelectedIndex(1)
}
}, [groups]);
// ...
return (
<Select
value={selectedIndex}
....other props...
>
... children
</Select>
)
}
Basically, you don't use document.getElementId.
You'll need to tell typescript that you're creating a browser app via tsconfig.json. You can add a lib property with the value dom. An example:
{
"compilerOptions": {
"lib": ["es5", "es6", "dom"], // <-- here
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "es6",
"moduleResolution": "node",
"jsx": "react"
},
"include": ["./src/**/*"]
}
This will allow typescript to recognize the document as a valid interface.
Once that's out of the way, an idiomatic approach at selecting DOM nodes in React function components is with useRef.
import React, { useRef, useEffect } from 'react'
const SomeComponent = ({ innerRef }) => (
<div ref={innerRef}>some div</div>
)
const ParentComponent = () => {
const ref = useRef()
useEffect(() => {
if(ref.current) {
console.log(ref.current.selectedIndex)
}
}, [ref.current])
return <SomeComponent innerRef={ref} />
}
A ref doesn't have to be passed from a parent. It can be used in the same doc. I just added that in case your use case required it.
I want to make simple messaging using socket.io and react useEffect() for a setup but I have no idea on how to properly manage/update state from inside of useEffect.
This is my code:
function App() {
const [messages, setMessages] = useState([{ type: "system", text: "Please stay nice!" }]);
useEffect(() => {
socket.current.on("messageSent", (data) => {
setMessages([...messages, { type: "you", text: data.message }]);
});
socket.current.on("receiveMessage", (data) => {
setMessages([...messages, { type: "partner", text: data.message }]);
});
}, []);
return (
<>
<span className="container">
<Chat messages={messages} />
</span>
</>
);
}
The point is I don't want to re-run useEffect(), I want it to run once at the beginning to set-up socket callbacks.
Right now when I try to access messages from inside of my useEffect I got only the starting value which is [{ type: "system", text: "Please stay nice!" }] also setMessages doesn't refresh props of my parent component and child component has only access to this starting value [{ type: "system", text: "Please stay nice!" }].