How to avoid Maximum update depth exceeded Reactjs - reactjs

I tried to display some csv content in reactjs application using below code. when i click the button it should call the api and get the response.i need to show the response in table formate and when i submit i need to save the data. Getting response and Saving is working fine for me but my issue is when i click the side menu to for fetch the selected data my state not updating properly to display the selected value data i need to click more than three times. so i tried with useeffect its with dependency its wokring fine . but its showing memory issue. i am new to react i tried many ways its showing maximum exceed error. so how to fix it in below code
const Parameters = ({ selectedtestcaseId, hideTestTab }) => {
const [csvattachment, setcsvattachment] = useState({})
const [slectedattachementId, setslectedattachementId] = useState()
const [viewcsv, setviewcsv] = useState()
const [csvHead, setcsvHead] = useState([])
const [csvformFields, setcsvformFields] = useState([{}])
const [formatData, setformatData] = useState([])
const formatCsv = () => {
console.log(csvHead)
if (viewcsv) {
let splitVlaues = viewcsv?.split('\r\n')
setcsvHead(splitVlaues[0]?.split(','))
let allData = []
for (let i = 1; i < splitVlaues.length - 1; i++) {
let a = splitVlaues[i].split('"');
let filteredValue = a.filter(x => x && x != ',');
allData.push(filteredValue)
}
setformatData(allData)
var newval = formatData.map(val => {
return val.reduce((result, field, index) => {
result[csvHead[index]] = field;
return result;
}, {})
})
setcsvformFields(newval)
}
}
useEffect(() => {
formatCsv()
}, [csvformFields])
return (
<table className="table table-striped table-bordered parameter-display">
<thead>
<tr>
{csvHead.map((val, i) => <td key={i}>{val}</td>)}
</tr>
</thead>
<tbody>
<>
{csvformFields?.map((d, index) => (
<tr>
{Object.keys(d).map(prop => (
<>
<td><input type="text" name={prop} value={d[prop]} onChange={(e) => handleFormChange(index, e)} /></td>
</>
))}
</tr>
))}
</>
</tbody>
</table>
)
}
My in input is
in variable "viewcsv" ie const [viewcsv, setviewcsv] = useState()
/
criteriass,criteriaCountry,criteriaCountryState
"5G,NewZealand","5G,New Zealand ","5G,New Zealand "
"5G,NewZealand","5G,New Zealand ","5G,New Zealand "
"5G,NewZealand","5G,New Zealand ","5G,New Zealand "
/
2) const [csvHead, setcsvHead] = useState([])
csvHead hold ['criteriass','criteriaCountry']
3) const [formatData, setformatData] = useState([])
formatData and allData hold
[
[
"5G,NewZealand",
"5G,New Zealand "
]
]
4) const [csvformFields, setcsvformFields] = useState([{}])
csvformFields and newval hold
[
{
"criteriass": "5G,NewZealand",
"criteriaCountry": "5G,New Zealand "
}
]
My issue is after i try to use csvformFields in my html its not loading
after use useEffeect with csvformFields as dependency its working. but throwing infinite loop.
How to fix the above. How to make state change reflect immeditaly
I tried to call "csvformFields" using useEffect its thrwoing maximum exceed error and also i tried with "csvHead " varibale still the same.. How can i fix the maxmium exceed error in the above code . where i am making the mistake. which variable i need to add as dependency array in useEffect and why??
below is the error which i am getting
*
Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render
*

The issue comes from your useEffect
useEffect(() => {
formatCsv()
}, [csvformFields])
Here you call out a function called formatCsv whenever the value of csvformFields changes. Inside the formatCsv function you call out setcsvformFields that changes the value of csvformFields. That creates an infinite loop:
useEffect calls formatCsv -> formatCsv changes csvformFields value -> useEffect dependency array trigers and calls the formatCsv function -> formatCsv changes csvformFields value -> so on and so on.
The fix is to fix the dependency array or useEffect. The formatCsv only depend on 3 values - viewcsv formatData and csvHead. They what define csvformFields value. So dependency array should be set to those - not the csvformFields.
useEffect(() => {
formatCsv()
}, [viewcsv, formatData, csvHead])
BUT that wont solve the issue as the infinite loop will still persist. Its down to the flaw of formatCsv function. inside you have setState functions for setformatData and setcsvHead states. Why? I got no clue.
Setting a state and then in the function trying to get the newest value? that wont work. Once a function is executed the values of states inside the function cycle will remain the same no matter how much u change em. so just make them into a variable.
Overall you need to refactor the entire flow. The code is a mess - no proper formatting, dosent follow the standard programing namings, etc. + you HEAVILY rely on SOOO many different states its absurd.
tl;dr; the issue is by setting the wrong useEffect dependency, having too many states and a bad formatCsv function design.

Related

The useEffect won't change the state LIVE when clicked but content does change on reload

I'm trying to change my page's content when clicking on the link but the thing is it only changes on reload and not directly when clicked.
function Home( { category } ) {
const [news, setNews] = useState([]);
useEffect(() => {
async function newsArticles(){
const request = await axios.get(category);
console.log(request.data.articles);
setNews(request.data.articles);
return request;
}
newsArticles();
}, [category]);
return (
<div className='home'>
{news.map(ns => (
<NewsCard className='homeNewsCard' key = {ns.source.id} title = {ns.title} description = {ns.description} image = {ns.urlToImage} link = {ns.url}/>
))}
</div>
)
}
Since the effect captures the values at the time the component is rendered, any asynchronous code that executed in the effect will also see these values rather than the latest values. Example :
useEffect(()=>{
asyncFunc(props.val)
.then((res)=> setData(res + props.oth))
},[props.val]);
props.oth is used when the promise resolves, but the effecct captures the values of props.val and prips.oth at the time of render , the value of props.oth had changed during the time it took for the promise to reoslve , the code would see the old value
Solution :
you can either use reference to store the value of props.oth so your code will always see the latest value or you can add props.oth back to dependency array and return a cleanup function .
Example :
useEffect(()=>{
let cncl = false
asyncFunc(props.val)
.then((res)=>{
if(!cncl)
setData(res + props.oth)})
return ()=> cncl = true}
,[props.val, props.oth])
cncl flag is raised before running the effecct again (when props.val or props.oth have changes comapred with the previous render).

React - useState appending to the array changes previously appended objects

Hi all,
I have a small react app that is creating (mapping from an array) new tabs (and panels) when there is a new message over the websocket.
There is an initial setup, that is hardcoded for the test purposes which sets up 2 tabs on load, any new ones should be appended to these two.
const INITIAL_ARRAY= [
{
id: 1,
child_component_config: {...}
},
{
id: 2,
child_component_config: {...}
}
];
const template = {
child_component_config: {...}
}
The code, simplifed:
const [current_array, setNewArray] = useState( INITIAL_ARRAY);
export default function ParentComponent() {
useEffect(() => {
const client = new ws...
client.onConnect = function (frame) {
var message = clientDesk.subscribe( '/topic/desks/4', function (message) {
// on message
var new_tab = TEMPLATE;
new_tab.id = Math.max( ...current_array.map( elem => elem.id ) ) + 1;
setActiveTab(new_tab.id);
setNewArray([...current_array, new_tab]);
}
}
}, [ current_array ]);
const tabs = map tabs
cosnt panels = map panels
return(
{tabs}
{panels}
)
The problem:
On first message from the WS the third element is added to the array properly (example-1)
, fourth one is added properly but it also overwrites the third element (example-2)
making them exactly the same. After fourth it gets strange, where some are overwritten and some are not.
I've tried:
Moving the state updating out of useEffect or removing useEffect completly
Storing current_array in a temp var before any logic
Adding a counter to track which tab's id is the latest -> tracking state of just one number works
const [tab_count, setTabCount] = useState( INITIAL_ARRAY.lenght );
Using counter to try to force rendering
Setting up a fixed number of objects in the initial array and just update which ever is needed (with and without counter)
Updating based on the previous value
setNewArray( prevArray => {
logic
return [...prevArray, new_tab];
}
After the first WS message, if the code is changed/saved and webpack compiled, the next message will add a new element to the array properly.
EDIT - Solved:
Managed to solve this by building a new object (instead of using the template) before adding it to the array.

Adding an item to array in React fails when using sockets

I've an array in React (using hooks), and I'm using socket.io to get notified on new items.
I've defined the Socket listener on the initializing useEffect, I'm getting notified when new objects should be added, but on this listener handler -
The current array is always empty
When I add the new object to the beginning of the array (using the spread operator) - the array will hold only the new object.
Any idea why?
function RecommendationsFeed() {
const [recommendations, setRecommendations] = useState<Recommendation[]>([]);
useEffect(() => {
const socket = socketIOClient(SOCKET_URL);
socket.on('recommendation', (newRecommendation: Recommendation) => {
newRecommendation.id = Math.floor(Math.random() * 10000) + 1;
const mergedRecommendationsList = [newRecommendation, ...recommendations];
setRecommendations(mergedRecommendationsList);
});
}, []);
return (
<div>
{ recommendations.map( currRec => <RecommendationFeedItem rec={currRec} key={currRec.id}/> ) }
</div>
)
}
I've also tried to add the recommendations state to the parameter list of the useEffect, but then it caused the listener to get processed multiple times.
useEffect(() => {
...
}, [recommendations]);
I found the issue.
Referring to the existing recommendations array in the set did the trick - by changing the update to:
setRecommendations((recommendations) => [newRecommendation, ...recommendations]);

Using a function inside .map() in React , does not work properly

I tried using a function inside the map() function but yet it does not give the output required. I will explain the problem after these code sections.
map function code segment
{bookings.map((booking) =>(
<tr>
<th scope = "row">{number++}</th>
<td>{booking.tourId}</td>
<td>{booking.bookingDate}</td>
<td>{booking.arrivalDate}</td>
<td>{booking.country}</td>
<td>{GuideAssigned(booking.tourId)}</td>
<td><Button color="warning" style = {{padding: "5px 5px 5px 5px" , width : "80px" , marginBottom : "8px"}}
onClick = {()=>{
history.push(`/assign-guide/${booking.username}`);
}}
>Assign Guide</Button>
</td>
</tr>
))}
The problem here is that the function gets called only for one tour ID , which is the first one in the table.The function does not get called for the other Tour ID's.Am I doing something wrong here?
Below is the function called
function GuideAssigned(tid){
axios.get(`http://localhost:8070/assignedGuides/get/${tid}`).then((res)=>{
console.log(res.data.guideId);
setGuide(res.data.guideId);
if (typeof guide == 'undefined'){
return "Not Assigned";
}
}).catch((err)=>{
console.log(err);
})
return guide;
}
This works only for the first tour ID which gets passed as you can see below.(Guide Assigned field is same for all).
You can't call asynchronous code in the render of a React component. The reason they are all the same is likely because you've one guide state, set by setGuide(res.data.guideId);, and it's also very likely it's the last mapped element setting this value since it's the last to do a GET request.
You will want to make these GET requests when the bookings array reference updates. Use an useEffect hook for this.
const [guides, setGuides] = useState({});
useEffect(() => {
bookings.forEach(({ tourId }) => {
axios.get(`http://localhost:8070/assignedGuides/get/${tourId}`)
.then(res => {
setGuides(guides => ({
...guides,
[tourId]: res.data.guideId,
}));
})
});
}, [bookings]);
Use the mapped tourId to access the correct guides guideId value.
{bookings.map((booking) =>(
<tr>
...
<td>{guides[booking.tourId]}</td> // <-- access guide id by tour id
...
</tr>
))}

Updating nested useState seems to modify the original data

So I have an implementation of a Text Field input alongside a table in which I'm trying to update the state of staged Data before I submit the data to an API.
In the Dialogs parent component, I have the data defined which I want to show in a table as the original state.
The current problem I'm having is the inputted data is somehow updating the original data's state even though I'm not directly touching this data.
Below is a reproduction of it on Codesandbox, So when you open the link typing into the edit value field should not update the current stock field and I don't see why it is.
CodeSandBox
Here is the callback that modifies the state:
const handleUpdateDip = (value, tank) => {
const newData = stagedData;
const foundIndex = newData.dips.findIndex((d) => d.tank === tank);
if (foundIndex !== -1) {
newData.dips[foundIndex].currentStockValue = Number(value);
setStage({
...stagedData,
dips: newData.dips
});
}
};
So yeah this one seems weird to me and I've been banging my head against the keyboard trying to understand whats going on with it since last night so any help would be appreciated!
You are mutating the current object. Try this
setStage((stage) => {
const foundIndex = stage.dips.findIndex((d) => d.tank === tank);
return {
...stage,
dips: stage.dips.map((d, index) => {
if (foundIndex === index) {
return { ...d, currentStockValue: Number(value) };
}
return d;
})
};
});
Instead of this
const foundIndex = stagedData.dips.findIndex((d) => d.tank === tank);
if (foundIndex !== -1) {
stagedData.dips[foundIndex].currentStockValue = Number(value);
setStage({
...stagedData,
dips: stagedData.dips
});
}
I don't see why it's shouldn't update while the code tells it to do so! This line inside handleUpdateDip():
stagedData.dips[foundIndex].currentStockValue = Number(value);
You shouldn't directly mutate the state. You should make a copy of it first change whatever you want and then set the state to the new value e.g.:
const handleUpdateDip = (value, tank) => {
const foundIndex = stagedData.dips.findIndex((d) => d.tank === tank);
if (foundIndex !== -1) {
const newStagedData = { ...stagedData };
newStagedData.dips[foundIndex].currentStockValue = Number(value);
setStage(newStagedData);
}
};
stagedData.dips[foundIndex].currentStockValue = Number(value); this line updates the value of currentStockValue which is used in the "Current Stock" column.
It seems like the table cell left of the input field simply uses the same state that is changed in handleUpdateDip
<TableCell align="right" padding="none">
{row.currentStockValue}
</TableCell>
<TableCell align="right" padding="none">
<InputTextField
id="new-dip"
type="number"
inputProps={{
min: 0,
style: { textAlign: "right" }
}}
defaultValue={row.currentStockValue}
onChange={(event) =>
handleUpdateDip(event.target.value, row.tank)
}
/>
both are currentStockValue, which handleUpdateDips changes in this line
stagedData.dips[foundIndex].currentStockValue = Number(value);
I think I know what you're thinking. You think that on the one hand, you're updating your state in handleUpdateDip(event.target.value, row.tank) with setStage({...}), so you're only changing your state stagedData.
You value for the "Current Stock", however, is mapped to your data variable and not to stagedData.
So in the end your question is: Why ist data changing when you're only manipulating stagedData.
Of course it happens here: const [stagedData, setStage] = useState(() => data);
(btw you don't need to use a function here, const [stagedData, setStage] = useState(data); is fine). You pass in data by reference here, when your setState hits, the reference will be updated and so will your data.
(another BTW: don't call your state variable settings functions simply setState, this is something used by class components in React. Call them like the state you want to set, e.g. setStagedData).
Now, you can elimate this reference, since you only want the initial values anyways. You could do this by passing a copy, like this: const [stagedData, setStagedData] = useState({...data}); But this still won't work - I not really sure why because I don't know enough about the inner workings of useState, but the reason probably is because it's only a shallow copy instead of a deep copy (you can read more about this here).
But if we do a deep copy and pass this in, it works and your original data will stay untouched. You can deep copy by basically stringifying and then parsing it again (which will not copy any methods the object has, just as a warning).
const copy = JSON.parse(JSON.stringify(data));
const [stagedData, setStagedData] = useState(copy);
And just like that your current stock will stay the same:
I forked your CodeSandBox, so you can see it for yourself.

Resources