i have this small piece of code where i think i guess I'm doing it wrong
const [orders, setOrders] = useState([])
useEffect(() => {
fetch('/getOrders',{
headers:{
"Content-Type":"application/json",
"Authorization": "Bearer "+localStorage.getItem("jwt")
}
}).then(res=>res.json())
.then(orderList=>{
setOrders(orderList)
//console.log("myAdmin",orderList)
}).catch(err=>{
console.log("Error in Catch",err)
})
}, [orders])
Here the data is updated overtime by itself and i want my state updated every time the fetch data is different to the existing state , but regardless if the data is different or not, the state keep updating making the component re-render infinitely. Is there a solution for this?
Issue
Anytime an effect unconditionally updates a value that is in its dependency array it will cause render looping.
useEffect(() => {
fetch('/getOrders', { ... })
.then(res=>res.json())
.then(orderList=>{
setOrders(orderList); // <-- updates state, triggers rerender
})
.catch(err=>{
...
});
}, [orders]); // <-- updated state triggers effect callback
Possible Solution
It appears you are wanting to fetch every time the component renders and upon successful response (200 OK) check if the orders array is actually different. If it is then update state, otherwise ignore the update.
Remove the dependency array so the useEffect hook callback is called each time the component renders.
Check if the fetch request response is ok before accessing the response JSON data.
Check the order response data against state. If the arrays are different lengths, update state, otherwise you will need to check element by element to determine if the array is different or not. *
Updated effect hook
const [orders, setOrders] = useState([])
useEffect(() => {
fetch("/getOrders", {
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + localStorage.getItem("jwt")
}
})
.then((res) => {
if (!res.ok) {
throw new Error("Fetch is not OK");
}
return res.json();
})
.then((orderList) => {
if (orders.length !== orderList.length) {
setOrders(orderList);
} else {
for (let i = 0; i < orders.length; i++) {
// *** Your actual equality condition may be different, i.e. if they are objects ***
if (orders[i] !== orderList[i]) {
setOrders(orderList);
return;
}
}
}
})
.catch((err) => {
console.error("Error in Catch", err);
});
});
* This is an O(n) check every time so an optimization that can be made may be to also return and store an "update" hash. If the hash is the same as the previous request, skip state update. This could be something as simple as generating a new GUID for the update in backend and returning it with data.
Basicaly [orders] in your code means: call what inside useEffect each time orders change, and each time useEffect is called , it update again the orders ... and you get your infinite loop
useEffect(() => {
}, [orders])
you should change [orders] into [].
res=>res.json() will create a new refrance to your object, and changing your referance to your object means it changed, for example [1,2,3] if you pass again [1,2,3] it will not be the same objct even thow it looks the same. v=[1,2,3], you pass v, and then v[0]=-1, passe v again it will be considred the same since it has the same referance.
Best way to fix this is to compare them manualy depending on your case, and when they aren't the same, you use somthing like [...orders] which will create new refrence and it will invoke useEffect again.
try to set state with prevState argument, so you will not have to include any dependency.
setOrders((prevState) => ({
...prevState, orderList
}))
Related
I am attempting to perform a series of Axios requests inside the useEffect() of a react component. I am aware that these requests are asynchronous, and I should maintain a piece of "loading" state that specifies if series of requests have been completed.
const [state, updateState] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
let innerstate = []
allRespData.map(single_response => {
axios.post("<URL>", {
raw_narrative: single_response[index].response
})
.then((response) => {
innerstate.push(response.data)
});
})
updateState(innerstate)
setLoading(false)
}, []);
if (loading)
return (<h3> Loading </h3>)
else {
console.log(state)
return (<h3> Done </h3>)
}
I would expect the output from the above code to be a list containing the data of each response. Unfortunately, I think that data only arrives midway through the console.log() statement, as initially an empty list [] is logged, however the list is expandable- therein my expected content is visible.
I am having a hard time doing anything with my state at the top, because the list length is constantly 0, even if the response has already loaded (loading == false).
How can I assert that state has been updated? I assume the problem is that the loading variable only ensures that a call to the updateState() has been made, and does not ensure that the state has actually been updated immediately thereafter. How can I ensure that my state contains a list of response data so that I can continue doing operations on the response data, for example, state.forEach().
You're not awaiting any of the requests, so updateState will get called before any of the responses have had time to come back. You'll be setting the state as [] every time. You also need to return your axios.post or the data won't get passed to .then
There are lot of nicer ways to handle this (I'd recommend looking at the react-query library, for example). However, to make this work as it is, you could just use Promise.all(). Something like:
useEffect(() => {
Promise.all(
allRespData.map(single_response =>
axios
.post('<URL>', { raw_narrative: single_response[index].response })
.then(response => response.data)
.catch(error => {
// A single error occurred
console.error(error);
// you can throw the error here if you want Promise.all to fail (or just remove this catch)
})
)
)
// `then` will only be called when all promises are resolved
.then(responses => updateState(responses))
// add a `.catch` if you want to handle errors
.finally(() => setLoading(false));
}, []);
I am using the useEffect hook for a get request using axios and consequently updating the state with some of the received data. This is my useEffect hook and the function inside:
const [expressions, setExpressions] = useState(expressionData.data);
const getFilteredExp = () => {
console.log(catFilter);
let config = {
method: 'get',
url: `/expressions/category/${catFilter}`,
headers: {}
};
axios(config)
.then((response) => {
console.log(response.data);
const newExp = response.data.payload.data;
console.log(expressions);
setExpressions(newExp);
console.log(expressions);
})
.catch((err) => {
console.error(err);
})
}
useEffect(() => {
if (!catFilter) {
setExpressions(expressionData.data)
console.log(expressionData.data);
}
else {
getFilteredExp();
}
}, [catFilter])
catFilter is also a state variable. My expressions state variable is not updated directly after the setExpressions call but one render cycle after. Not sure how to fix this.The console logs inside the getFilteredExp function should highlight my problem perfectly. These console logs should show my problem Everytime I change the category filter (catFilter state variable), the page is re-rendered and the previous get request's results are updated in the state. Ideally, the logs should return with 5 items after the get request returns with an array of 5 items. This is clearly not shown in the console log.
Additionally, even though my state is updated a render-late, my page content that depends on the expressions state variable is not being updated and remains in its default state.
Any help would be much appreciated and I am sorry if my question is not very clear or follow some requirements, I am relatively new to this.
The 2 console.logs here will display the same value for expressions, because they are referencing the same copy of expressions:
console.log(expressions);
setExpressions(newExp);
console.log(expressions);
If you want to see the changed value of expressions, you can add a useEffect() that triggers after expressions has had a chance to change:
useEffect(() => {
console.log(expressions);
}, [expressions]);
I'm not sure if there's enough info here to give a specific solution to the other problem. Add more info if you are still stuck.
The 'myPosts' has an object with multiple posts inside it.. I wanted the user profile to immediately show the post after it is uploaded so I passed 'myposts' in the dependency array.
But the problem is that the component is re-rendering infinitely. How can I make it so that it re-renders once, only when a new post is uploaded? I can't understand why passing 'myposts' in the array is causing infinite renders instead of only once.
const [myposts, setPosts] = useState([]);
useEffect(() => {
fetch('/mypost', {
headers: {
cookie: 'access_key',
},
})
.then((res) => res.json())
.then((data) => {
// console.log(data);
setPosts(data.myposts);
});
}, [myposts]);
When fetch resolves, it modifies myposts, which triggers a fetch because it is listed as dependency of useEffect, which modifies myposts, and so it continues...
It seems that myposts depends on the result of the fetch, not the other way around. So I would suggest removing myposts from the dependency list.
The useEffect hook is called when myposts gets updated. In the final .then of your fetch, you're updating it via setPosts. The best way to fix this is by making the dependency array an empty array.
But this won't solve the issue of updating posts from the server, but this can also be done in a periodic function with setInterval. This would result in something like the code below.
const [myposts, setPosts] = useState([]);
const update = fetch('/mypost', {
headers: {
cookie: 'access_key',
},
})
.then((res) => res.json())
.then((data) => {
// console.log(data);
setPosts(data.myposts);
});
useEffect(() => {
update()
const interval = setInterval(update, 30000)
return () => clearInterval(interval)
}, []);
I have two used state arrays were going to call them queries, and messages.
const [queries, setQueries] = useState([]);
const [messages, setMessages] = useState([]);
I have it so when users type a search query it will add the query to a list of queries on screen. When the user deletes one of those queries or adds a new query, my use state will read the last query on the list and run a fetch request all within the useEffect.
useEffect(() => {
if (queries.length > 0) {
const [x] = queries.slice(-1);
axios
.get(`FETCH REQUEST`)
.then((res) => {
setMessages(res.data.messages);
})
.catch((err) => {
console.log(err);
});
} else {
setMessages([]); // <-- this right here is my problem!
}
}, [queries]);
The problem is everything works as it should until the there are no items in the array. It will briefly return an empty message array, then fetch request the last deleted item anyway somehow. Form what I can conclude this is where the problem is located, but I do not understand the why or how despite trying to force my result in various ways. Thank you for any help in advance!
The setState function works asynchronously, if required, would join multiple requests without any specific order to optimize the app performance. This could trigger useEffect multiple times and set your message state incorrectly. Instead, you can set the message state using a callback into the setQueries call.
function cbAddOrDeleteQuery() {
...
setQueries(_ => {
// newVal represents the updated queries
if (newVal.length > 0) {
setMessage(res.data.messages);
} else {
setMessage([]);
}
return newVal;
});
}
I am new in React.I just want to show records in the table and I fetch data like
const [allowances, setAllowances] = useState([]);
useEffect(() => {
fetch("http://127.0.0.1:8000/allowances/")
.then(data => {
return data.json();
})
.then(data => {
setAllowances(data);
})
.catch(err => {
console.log("error",err);
});
}, []);
Here how I check length=>
<div>{allowances.length}</div>
if i log the data in before setAllowances(data) ,data has 3 records.But when I check allowances.length, there are no records. its show like <div></div>. So I think this setAllowances is not working.right? what is wrong?
Update
This is my data of i logged before setAllowance=>
You are not setting the data correctly. As per the contents of data, it should be:
setAllowances(data.allowance);
For useEffect hooks to update every single time that your state changes, you need to pass it as a parameter. This happens by passing allowances within the square brackets after you set your callback to useEffect. Check out this link