useEffect clears, then refills my react hook - reactjs

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;
});
}

Related

RTK Query response state

I'm trying to convert some Axio code to RTK query and having some trouble. The 'data' response from RTK query doesn't seem to act like useState as I thought.
Original axio code:
const [ importantData, setImportantData ] = useState('');
useEffect(() => {
async function axiosCallToFetchData() {
const response = await axiosAPI.post('/endpoint', { payload });
const { importantData } = await response.data;
setImportantData(importantData);
}
axiosCallToFetchData()
.then((res) => res)
.catch((error) => console.log(error));
}, []);
const objectThatNeedsData.data = importantData;
New RTK Query code
const { data, isSuccess } = useGetImportantDataQuery({ payload });
if(isSuccess){
setImportantData(data.importantData);
}
const objectThatNeedsData.data = importantData;
This however is giving me an infinite render loop. Also if I try to treat the 'data' object as a state object and just throw it into my component as:
const objectThatNeedsData.data = data.importantData;
Then I get an undefined error because it's trying to load the importantData before it's completed. I feel like this should be a simple fix but I'm getting stuck. I've gone through the docs but most examples just use the if statement block to check the status. The API calls are being made atleast with RTK and getting proper responses. Any advice?
Your first problem is that you always call setImportantData during render, without checking if it is necessary - and that will always cause a rerender. If you want to do that you need to check if it is even necessary:
if(isSuccess && importantData != data.importantData){
setImportantData(data.importantData);
}
But as you noticed, that is actually not necessary - there is hardly ever any need to copy stuff into local state when you already have access to it in your component.
But if accessing data.importantData, you need to check if data is there in the first place - you forgot to check for isSuccess here.
if (isSuccess) {
objectThatNeedsData.data = data.importantData;
}
All that said, if objectThatNeedsData is not a new local variable that you are declaring during this render, you probably should not just modify that during the render in general.

What is the best way to execute a function after TWO or more async functions finish in React?

I have two functions that run asynchronously getting data from the API. Both of them are called from their own useEffect().
I have a third function that needs to run once those two functions have been fully completed.
How can this be accomplished?
Edit:
Both of the async functions look like this:
useEffect(() => {
fetchBudgetBucketsData();
}, [fiscalYear]);
useEffect(() => {
fetchBudgetBucketsData();
}, [fiscalYear]);
const fetchBudgetsData = async () => {
setIsFetchingBudgets(true);
const res = await getBudgets(orgID, `${parseInt(fiscalYear)}`, '', budgetType);
setIsFetchingBudgets(false);
if (isErrorResponse(res)) {
console.warn(res.details);
message.error(res.displayText);
return;
}
setBudgets(res.budgets);
};
const fetchBudgetBucketsData = async () => {
setIsLoadingBudgetBuckets(true);
if (orgID === undefined) {
return;
}
const res = await getBudgetBuckets(orgID, fiscalYear);
setIsLoadingBudgetBuckets(false);
if (isErrorResponse(res)) {
console.warn(res.details);
message.error(res.displayText);
return;
}
setBudgetBuckets(res.buckets);
};
Whenever the budget data or bucket data is updated, I want to call another function that checks for errors. However when the page loads, I need it to wait for both of those functions to be finished before it checks for errors.
Edit #2:
After some debugging, it looks like the issue might have to do with when React updates the state. Since I am trying to check for errors in data saved in the state.
One way could be chaining Promises.
Promise.all([ApiCall1, ApiCall2])
// At this point two promises above will be resolved
.then(() => ApiCall3)
Read more
I discovered the issue was caused by how React chooses when to update the state and not how I was calling these functions asynchronously.
I was able to call my Error check function by hooking it into the output of the data fetch calls. This makes sure that the error check only runs when either the budgets or buckets are edited and finished being changed.
useEffect(() => {
getWarningsAndErrors();
}, [budgets, budgetBuckets]) //Update errors whenever we edit budgets or buckets

React useState, setState in useEffect not updating array

I've seen this problem on SO, but I cannot seem to work out why it exists.
I am following the tutorial from here
I am using useState but when I try to update the state, the array is empty. I am using state to create initially an empty array. on message received, I am trying to add the message to the array, using the spread operator, which I have used countless times to add an object to an array (but never inside useEffect).
If I uncomment the commented lines, "chat" gets updated as it should do, but I cannot understand why the spread operator is not working and I need to use a useRef to make this work. I don't want to have loads of useRef for every corresponding useState (at least not knowing why it is necessary)
Can anyone see what I am doing wrong, or explain why I need the useRef?
const [chat, setChat ] = useState([]);
//const latestChat = useRef(null);
//latestChat.current = chat;
// ...
useEffect(() => {
if (connection) {
connection.start()
.then(result => {
console.log('Connected!');
connection.on('ReceiveMessage', message => {
const newMsg = {
user: message.user,
message:message.message
}
setChat([...chat, newMsg]); // issue with this line. chat is always empty
//const updatedChat = [...latestChat.current];
//updatedChat.push(message);
//setChat(updatedChat);
});
})
.catch(e => console.log('Connection failed: ', e));
}
}, [connection]);
you have two option here
add chat state to useEffect dependency array so it knows it depends on chat.
useEffect(() => {
if (connection) {
//...
setChat([...chat, newMsg]); // issue with this line. chat is always empty
//...
}
}, [connection, chat]);
use setState callback to update chat so you won't get stale data
useEffect(() => {
if (connection) {
//...
setChat((ch) => [...ch, newMsg]); // issue with this line. chat is always empty
//...
}
}, [connection]);
which the second way is more appropriate.

React useEffect infinite loop fetching data from an api

Hi I'm trying to make a twitter clone app. I am using React on the client side and Express on the server side and PostgreSQL as my database. So here's the problem, I'm trying to use the useEffect like this:
const [tweets, setTweets] = useState([]);
const getTweets = async () => {
const res = await api.get("/posts", {
headers: { token: localStorage.token },
});
setTweets(res.data);
};
useEffect(() => {
getTweets();
}, [tweets]);
I have no idea why it's looping infinite times, am I using it correctly though? I want the tweets to be updated every time I post a tweet. It's working fine but it's running infinite times. I just want it to re-render if a tweet got posted.
Here's my server code for getting all the posts:
async all(request: Request, response: Response, next: NextFunction) {
return this.postRepository.find({
relations: ["user"],
order: {
createdAt: "DESC",
},
});
}
The problem is every time you change the tweets it executes useEffect and changes the tweets and so long and so forth, so it's natural that it loops infinitely, the solution is to add a trigger that you set to true when a tweet gets posted, so the solution would be like this
const [tweets, setTweets] = useState([]);
const [isFetching, setIsFetching] = useState(false);
const getTweets = async () => {
const res = await api.get("/posts", {
headers: { token: localStorage.token },
});
setTweets(res.data);
};
useEffect(() => {
getTweets();
setIsFetching(false);
}, [isFetching]);
and set some logic to use setIsFetching(true) in order to execute the useEffect
PS: if you use an empty array in useEffect, it would execute only when the component is mounted (at the start)
useEffect(() => {
getTweets();
}, [tweets]); // [tweets means that hook works every time 'tweets' state changes]
so your getTweets function set tweets => as tweets are changed hook works again => call getTweets => ... = infinite loop
if you want to download tweets, use empty array instead - hook will work once then
Pass empty array as a second arg for calling it once otherwise for changing it on every tweet change it will re-trigger, so whenever state will change only then it will be re-rendered like Tarukami explained. One thing you can do is check the length like mentioned below so not to compare the whole object but just the length
useEffect(() => {
getTweets();
}, [tweets.length]);
This might raise an error react-hooks/exhaustive-deps lint error (that's a bypass you can use it).
But if you want more tighter check you can compare the ids on each re-render (create a hash/key/id from all element in the array and compare them on each render) like so [tweet id here]) // Only re-subscribe if id changes

React prevent re-fetch when url params are unchanged

I have an ExperienceList component that fetches all items on mount. The functionality I am trying to achieve will occur when the user clicks into one particular experience, then navigates back to the ExperienceList. Currently, a fetch is made each time.
I want to do a simple check to see if the cityname from url params matches the cityname found in redux store. If it's the same, bypass the fetch. If not, do it.
Here are 2 approaches that I have tried, without luck:
First Approach
useEffect(() => {
if (props.match.params.cityname !== list[0].city) {
readList(apiRequestParams)
}
}, [])
With the above approach, I get the console error :
Cannot read property 'city' of undefined.
I am not 100% sure on why list is unavailable, since I am able to log it to the console.
Here is the Second approach:
useEffect(() => {
readList(apiRequestParams)
}, [props.match.params.cityname])
This approach does not work either, but I am more baffled as to why this is. As is my understanding, the variables appearing in the dependencies array of useEffect will cause the function to execute if and only if they are different between function calls. In this case, I can guarantee that props.match.params.cityname is the same between 2 different renders, yet a fetch is made each time.
Any insight on how I can optimize this process?
The function readlist is actually the dep of useEffect, you have to tell the useEffect. or you can change it to the cityname param.
const [cityName, setcityName] = useState('cityname');
useEffect(() => {
function getFetchUrl() {
return 'https://...' + cityName;
}
async function fetchData() {
const result = await readList(getFetchUrl());
setData(result.data);
}
fetchData();
}, [cityName]);
or
const fetchData = useCallback(() => {
readList("https://" + cityName);
}, [cityName]);
useEffect(() => {
fetchData()
},[fetchData])

Resources