React: Component randomly doesn't refresh after state changes - javascript

I am working on a CRUD (express, mongoose, mongodb) which is mostly working as expected... except when it comes to rendering the entries after deleting X amount of them.
Not deleting
Sometimes I can delete 10 entries without any issues, until the component is empty, and other times I delete some entries and then the page just stop rending the updated data and enters an infinite loading state in the browser; only when I hard reload the browser the page renders the latest data, yet it does delete the entry , it just freezes it seems!
From React:
useEffect(() => {
queryClient.invalidateQueries(["allUsers"]);
});
const mutation = useMutation({
mutationFn: async (userid) => {
return await axios.delete("/api/deleteuser", { data: { userid: userid } });
},
});
const handleDelete = (userid) => {
mutation.mutate(userid);
navigate("/", { replace: true });
};
From Mongoose
const deleteUser = async (req, res) => {
try {
await newUser.findOneAndDelete({ userid: req.body.userid });
} catch (error) {
return error;
}
};
Tried invalidating the query cache at the delete function but the result is the same. It just happens randomly, as if the request was not fulfilled... in dev tools/network the request is never fulfilled but the data, whenever it does work, is updated.Network/pending
Edit: I'm using a mongoDB free account... I've read sometimes the response times are not the best, perhaps it is the reason?

useEffect without a dependency array is pretty pointless. It will run every time the component is rerendered.
I assume you are using react-query. Try moving your queryClient.invalidate to onSuccess or onSettled in your useMutation config object. This will ensure your query is invalidated only after the mutation is done.
https://react-query-v3.tanstack.com/guides/mutations#mutation-side-effects

Related

Firebase's onSnapshot inside a useEffect; why does this infinitely loop?

I'm getting documents from a collection in Firebase and I was wondering if the following useEffect would loop. Is this good practice?
useEffect(() => {
const routineRef = collection(db, "routines", session?.user?.id!, currentRoutine.name);
const unsubscribe = onSnapshot(routineRef, (docsSnap) => {
setWeightsHistorySnapshot(docsSnap.docs);
// Infinite loop because this fills up my console
console.log("Current data: ", docsSnap.docs);
});
return () => unsubscribe();
}, [weightsHistorySnapshot, currentRoutine.name, session?.user?.id]);
From the docs of onSnapshot
An initial call using the callback you provide creates a document snapshot immediately with the current contents of the single document.
You set the state using setWeightsHistorySnapshot, and have weightsHistorySnapshot in your dependencies array, which in turn changes on every call. This causes the hook to run over and over.

How to tell React Query fetchQuery to make a new GET request and not used the already cached response?

I have the following function that makes a GET request for my user information and caches it using react query's fetchQuery so that every call after the first one does not make a GET request and instead just pulls the data from the cache.
export const getVegetables = async () =>
await queryClient.fetchQuery(['getVegetables'], async () => {
try {
const { data } = await request.get('/vegetables');
return data;
} catch (error) {
throw new Error('Failed to fetch vegetables');
}
});
The problem is that now I actually want to make a new GET request in order to check if the user data has changed, but calling getVegetables() pulls from the cache. How can I instruct fetchQuery to make a fresh GET request and not used the cache?
A slight modification to your function will allow you to first invalidate the query (which will remove it from the cache).
export const getSelf = async (skipCache = false) => {
if(skipCache) { queryClient.invalidateQueries(['getSelf']); }
return queryClient.fetchQuery(['getSelf'], async () => {
try {
const { data } = await request.get('/users/me');
// #todo: This sideloads a bunch of stuff, that we could cache
return data;
} catch (error) {
throw new Error('Failed to fetch user information');
}
});
}
In case of using fetchQuery, you can set cacheTime to 0 in query options, so every time you call it, it will suggest that cache is outdated and fetch fresh data, but I'd suggest you to use useQuery.
Here you can read about difference between useQuery and fetchQuery
The best way is to use useQuery hook and invalidate that query.
import { useQueryClient } from '#tanstack/react-query'
// Get QueryClient from the context
const queryClient = useQueryClient()
queryClient.invalidateQueries({ queryKey: ['getSelf'] })
After invalidation, it will immediately fetch fresh data.
fetchQuery will always fetch unless there is data in the cache that is considered fresh. This is determined by the staleTime setting.
staleTime defaults to 0 - which means "immediately stale". So the code you are showing that is calling fetchQuery should always fetch - unless you have a global staleTime set. You're not showing this in your code, but I guess this must be the reason. Note that fetchQuery doesn't know about staleTime being set by other observers (created by useQuery).
Now if you have a globally set staleTime and that is affecting fetchQuery, but you still want to always fetch, the best thing you can do is pass staleTime: 0 directly to fetchQuery. Again, this is the default behaviour, so it's only necessary if you have a global staleTime set:
await queryClient.fetchQuery(
['getSelf'],
async () => { ... },
{ staleTime: 0 }
)

How to pass a document fetched from firestore to local state in react

I have some documents in firebase firestore.
I am using this javascript function to fetch those
const fetchName = async () => {
console.log("fetchName fired");
const snapshot = await db
.collection("Users")
.doc(user?.uid)
.collection("details")
.doc("data")
.get();
// Data is a local state variable
// SetData is used to manipulate Data
setData(snapshot.data());
console.log("Snapped Data: ",snapshot.data());
console.log("Fetched Data: ",Data);
};
This is the state variable
const [Data, setData] = useState("");
I'm passing fetchName() function to useEffect hook.
useEffect(()=>
{
console.log("UseEffect Fired");
if(user!==null)
{
console.log("User not NUll, User->",user);
fetchName();
}
},[user])
The Issue
UseEffect is working correctly and firing fetchName() every time user logs in or logs out.
Here the main problem is When use effect is fired I can see snapshot.data() fetching the correct data but it is not assigning it to setData(). From hit and trial I have seen setData() is fired only the time I make any changes to my code or restart the server.
Desired Result
But I don't want that I want everytime snapshot.data() fetches the data it should assign it to Data using setData()
If you are worried about the 2nd line of console.log inside the fetchName, you're thinking in a wrong way.
So state updates are asynchronous.
For example,
const handleEvent = e => {
setState(e.target.value);
console.log(state);
}
This is not going to log the most updated value - this is happening because state updates are asynchronous, so synchronous behavior after a state update shouldn't rely on the state variable to get the most updated value for it.
For deeper explanation, check here and here

Updating State In React With React Hooks

I've been having a difficult time updating state inside of my React application lately using the useState hook.
If I define my state in a provider as -
const [session, setSession] = useState({});
const [sessionId, setSessionId] = useState(0);
And then try to set it using the setSession
setSession(response.data);
It always comes back as the default value. This all happens inside of the provider component - i.e. I'm trying to access the information within other functions in that same provider.
However, if I store that data in localStorage, for example, I have no issues accessing it whatsoever.
localStorage.setItem("session", JSON.stringify(response.data));
I've verified that the information coming from the server is an object, and that the correct data. There's no errors or promises, just the object containing the response. If I put the snippet the setSession(response.data) and localStorage.setItem("session", JSON.stringify(response.data)) next to each other, the setSession leaves the session value as {} whereas setting the local storage works perfectly. Both are using the same API response, same data
// This is the method on my component that I'm trying to use to update the state
const updateStateAfterSessionInitialization = async data => {
setSession(data)
localStorage.setItem("session", JSON.stringify(data));
setSessionId(data.id);
// both of these log a value of `{}` and `0` despite the values being set above
console.log(session)
console.log(sessionId)
closeStartSessionModal();
// If I redirect my application like this, it works fine. The ID is the correct value being returned by the server
window.location = "/#/reading-sessions/" + data.id;
}
// All of this code below is wrapped in a function. None of this code is being executed at the top level
let response = await axios({
method: method,
data:data,
url: url,
headers: headers
});
await updateStateAfterSessionInitialization(response.data);
Literally all of the data is working perfectly fine. The server responds with the correct data, the correct data is stored the session in local storage. If I redirect using the ID from the object from the server, it works fine. But if I try to update the state of the component and access the state properly, it just just doesn't work, and as a result I'm having to try to find ways of working around setting the state.
Is there something that I'm misunderstanding here?
The code that I'm working with is here - https://github.com/aaronsnig501/decyphr-ui/commit/ef04d27c4da88cd909ce38f53bbc1babcc3908cb#diff-25d902c24283ab8cfbac54dfa101ad31
Thanks
The misunderstanding you have here is an assumption that state updates will reflect immediately which is incorrect
State update is async and will only refect in the next render cycle. If you try to update state and log it in the next line, you wouldn't see and updated state
// This is the method on my component that I'm trying to use to update the state
const updateStateAfterSessionInitialization = async data => {
setSession(data)
localStorage.setItem("session", JSON.stringify(data));
setSessionId(data.id);
// both of these log a value of `{}` and `0` despite the values being set above
console.log(session) // This is expected to log previous value
console.log(sessionId) // This is expected to log previous value
closeStartSessionModal();
window.location = "/#/reading-sessions/" + data.id;
}
Now localStorage is synchronous and hence its update is reflected immediately
If you wish to see if the update to state was done correctly you could write a useEffect that depends on it
useEffect(() => {
console.log('State session/sessionId updated');
}, [session, sessionId])
Now depending on what you are trying to achieve you would need to modify your code in line with the above statement that state update calls are asynchronous.
Also setSession doesn't return a promise so you can't just use async await with it. You need to make use of useEffect to take an action on state update
For Example:-
import React, { useState, useEffect } from "react";
import axios from "axios";
function App() {
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios("https://jsonplaceholder.typicode.com/posts");
console.log(result.data);
setData(result.data);
};
fetchData();
}, []);
return (
<ul>
{data.map(res => (
<li>{res.title}</li>
))}
</ul>
);
}
export default App;
Check your type of response.data and defined the types
array - []
objects - {}
string - ""
number - 0 in useState
setState is async, so new value will apply on the next rerender not in the next line.

Cancel request if there is new one(promises)

I send request to server everytime user types something. I use debounce for the 400ms delay:
type = debounce((text) => {
this.props.actions.loadInfo(text)
}, 400);
When I type something, stop and start again and repeat it, several requests are send and I receive irrelevant data. I use promises:
export const loadInfo = (text) => dispatch => {
loadData(text).then(result => {
dispatch(showUserData(result));
});
};
export const loadData = async (text) => {
const tabData = await axios.get(`url&query=${text}`);
return tabData;
}
I need somehow cancel previous request if user sends the new one(when he typed something), what is the best way to do that? I expected debounce will help me but not. I use axios. This is not duplicate of questions here, I checked provided solutions but thet don't help me
The problem is similar to this one. Axios cancellation API can be used to cancel old requests. This should be done in a function that does a request (loadData) and has direct access to Axios, it may be also debounced:
let cancelObj;
export const loadData = debounce((text) => {
if (cancelObj) {
this.cancelObj.cancel();
}
cancelObj = CancelToken.source();
return axios.get(`url&query=${text}`, {
cancelToken: this._fetchDataCancellation.token
}).catch(err => {
// request wasn't cancelled
if (!axios.isCancel(err))
throw err;
});
}, 200);
Since Redux is used, other solutions may involve it, they depend on how Redux is used.
Even I tried to use debounce function in my code but the problem is that if user types very fast stop and then again start typing, in that case, your input values get updated and UI get distorted, to avoid this I used XMLHttpRequest and its abort() to cancel the previous calls, if calls do not succeed then it will be canceled,
you can try this solution, https://stackoverflow.com/a/55509957/9980970

Categories