React-query, query doesn't update arguments - reactjs

I use the react-query library to get my data.
When the user changes, I would love it if the previous user data was removed automatically & new data was fetched.
This does, not happen though, the api gets called a couple times more with the old userId, and only after 1 or 2 times re-focussing on the app, it will fetch the data with the proper new userId.
Here is the hook:
When I log some info in the getData function, I can see it being called a couple times with the old userId after logging out.
export default function useData() {
const {user} = useAuth();
const queryClient = useQueryClient();
useEffect(() => {
queryClient.removeQueries('data')
queryClient.invalidateQueries()
}, [user]);
return useQuery('data', () => getData(user!.uid), {
enabled: !!user,
})
}
Does anyone know how I can remove all data when my user changes?

All dependencies of your query should be in the query key, because react-query automatically refetches when the key changes. This is also in the docs here.
In your case, this means adding the user id:
useQuery(
['data', user?.uid],
() => getData(user!.uid),
{
enabled: !!user,
}
)
For clearing up old cache entries, I would possibly suggest setting cacheTime: 0 on your query, which means they will be garbage collected as soon as the last observer unmounts. Calling queryClient.removeQueries manually is also an option.

Related

How to invalidate react-query whenever state is changed?

I'm trying to refetch my user data with react-query whenever a certain state is changed. But ofcourse I can't use a hook within a hook so i can't figure out how to set a dependency on this state.
Current code to fetch user is:
const {data: userData, error: userError, status: userStatus} = useQuery(['user', wallet], context => getUserByWallet(context.queryKey[1]));
This works fine. But I need this to be invalidated whenever the gobal state wallet is changed. Figured I could make something like
useEffect(
() => {
useQueryClient().invalidateQueries(
{ queryKey: ['user'] }
)
},
[wallet]
)
but this doesn't work because useQueryClient is a hook and can't be called within a callback.
Any thoughts on how to fix this?
General idea is wallet can change in the app at any time which can be connected to a different user. So whenever wallet state is changed this user needs to be fetched.
thanks
useQueryClient returns object, which you can use later.
For example:
const queryClient = useQueryClient()
useEffect(
() => {
queryClient.invalidateQueries(
{ queryKey: ['user'] }
)
},
[wallet]
)

How do I asynchronously update a variable from a paginated API using React hooks?

I'm currently trying to fetch all of the properties for an object from an API, and display them in a table. The API will return up to 10 results at a time, and will return a value nextPageToken in the response body if there are more results to be fetched. My goal is to fetch the first 10 results, immediately display them in the table, and add to the table as I continue to hit the API. This was my first attempt at a solution:
const getProperties = async (id) => {
const properties = await Api.getProperties(id);
setProperties(properties.properties);
if (properties.nextPageToken) loadMoreProperties(id, nextPageToken);
};
const loadMoreProperties = async (id, nextPageToken) => {
const properties = await Api.getProperties(id, nextPageToken);
setProperties(prevProperties => {return [...prevProperties, properties.properties]});
if (properties.nextPageToken) loadMoreProperties(id, properties.nextPageToken);
};
(Note that the above is a simplification; in practice, there's more logic in getProperties that doesn't need to be repeated on subsequent calls to the API)
The problem that I'm running into with this solution is that when I'm calling loadMoreProperties, the setProperties call isn't yet finished. How can I enforce that the call to loadMoreProperties only happens after setting the previous set of properties? Is there an overall better pattern that I can follow to solve this problem?
You can use useEffect to trigger the page loads as a reaction to a completed state change:
const [page, setPage] = useState(); // will be {properties, nextPageToken}
// load first page whenever the id changes
useEffect(() => {
Api.getProperties(id)
.then(page => setPage(page)));
}, [id]);
// load next page (if there is one) - but only after the state changes were processed
useEffect(() => {
if (page?.nextPageToken == null) return;
Api.getProperties(id, page.nextPageToken)
.then(page => setPage(page)));
}, [id, page]
);
// this will trigger the re-render with every newly loaded page
useEffect(()=> setProperties(prev => [...(prev || []), page.properties]), [page]);
The first effect will cause an update to the state variable page.
Only after the state change is completed, the second effect will be triggered and initiate the fetch of the second page.
In parallel, the third effect will perform the changes to the state variable properties, that your table component depends on, after each successful page load and page state update, triggering a re-render after each update.
I think you should pass a callback parameter to your "setProperties" method, to make the second call after the value has been updated, like this :
setProperties(properties.properties, () => {
if (properties.nextPageToken)
loadMoreProperties(id, nextPageToken);
);
Hope it can help
My solution involves removing the loadMoreProperties method itself.
While calling the getProperties for the 1st time, you can omit the nextPageToken argument.
getProperties = async(id,nextPageToken) {
var result = await Api.getProperties(id,nextPageToken);
this.setState((state)=>(state.properties.concat(result.properties)), ()=>{
// setState callback
if(result.nextPageToken) {
this.getProperties(id, nextPageToken);
}
});
}

Firebase firestore read operation very high

So basically im making a CRUD app using react and firebase firestore for the backend.
My write and delete operation is doing well, there is no problem with it.
But my read operation have problem.
My web is getting all document from a collection in firebase using useEffect. So this only run whenever it first mount (when my web load first time) and when im changing "users" value when doing delete and create operation
this my code:
useEffect(() => {
const getUsers = async () => {
const querySnapshot = await getDocs(collection(db, "cobadata"));
setUsers(querySnapshot.docs.map((doc)=> ({...doc.data(), id: doc.id})))
};
getUsers();
}, [users]);
idk whats wrong but im getting a very high read operation when im test the web, its like every one read operation i do in my website, its getting like hundred operation in the firebase. i can see this in my firebase console, when im using the web just like 5 minute in my firebase console the read operation reaching 20k< operation.
can anyone help me how to deal with this, thanks!
You dont show all of your code here, so I will need to do some guessing.
Your useEffect has a dependency array that now is set to [users]. This means that every time the variable users changes your useEffect will rerender. Inside your useEffect you then set a new value to users by the setUsers function. Even if you get the same values returned from firebase regarding the current users, you still create a new array each time you read data. (querySnapshot.docs.map((doc)=> ({...doc.data(), id: doc.id}))). React only does a shallow comparison, meaning that the object reference has changed, and therefore users is different on each render.
First you need to decide when you want to run the useEffect and what should trigger it. If changes in the variable users is not the correct place to check, then I would remove users from the dependency array.
One solution could be to move the functionality in your effect into its own function and wrap it in an useCallbac. You can then call this function from an ´useEffect` on initial load, and after that simply load the effect whenever you delete or create users. Something like this.
const getUsers = useCallback(async () => {
const querySnapshot = await getDocs(collection(db, "cobadata"));
setUsers(querySnapshot.docs.map((doc)=> ({...doc.data(), id: doc.id})))
}, [collection])
useEffect(() => {
getUsers()
}, [getUsers]);
const createUser = () => {
...
getUsers()
}
const deleteUser = () => {
...
getUsers()
}
(PS! I would recommend adding the eslint-plugin-react-hooks to your eslint-config. This will give you some warning if your hooks are used wrong)

React-query: how to update the cache?

I have a very basic app that fetches a user and allows to change his name. I fetch the user with React query so I can benefit from the cache feature. It works.
However, when I want to update the user, I use a classic post request with axios. Once the user is updated in the database, I need to update the cache directly in the updateUser() function. I've seen tutorials on the web that use queryCache.setCache, but it doesn't work here. How to fix this? Or maybe there is a better way to handle such queries?
Also, I notice a huge number of rendering... (see the "user render" console.log in the app file).
For the convenience, I've made a small script on a codesandbox with the pokeapi:
https://codesandbox.io/s/api-service-syxl8?file=/src/App.js:295-306
Any help would be greatly appreciated!
So, I'll show you what I do:
const updateUser = async (userUpdates: User) => {
const data = await UserService.updateUser(userUpdates); // return axios data
return data;
}
// if you want optimistic updating:
const { mutate: mutateUser } = useMutation(updateUser, {
onMutate: async (userUpdates) => {
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
await queryClient.cancelQueries(['user', userUpdates.id]);
// Snapshot the previous value
const previousUser = queryClient.getQueryData(['user', userUpdates.id]);
// Optimistically update to the new value
queryClient.setQueryData(['user', userUpdates.id], userUpdates);
// Return a context with the previous user and updated user
return { previousUser, userUpdates }; // context
},
// If the mutation fails, use the context we returned above
onError: (err, userUpdates, context) => {
queryClient.setQueryData(['user', context.userUpdates.id], context.previousUser);
},
// Always refetch after error or success:
onSettled: (userUpdates) => {
queryClient.invalidateQueries(['user', userUpdates.id]);
}
});
// then to update the user
const handleUpdateUser = (userUpdates: User) => mutateUser(userUpdates);
This all comes from the docs:
Optimistic Updates

react-query: why is this one query always stale?

I've configured react-query with infinite stale time, per docs, like this:
<ReactQueryConfigProvider config={{
queries: {
staleTime: Infinity
}
}}>
Most of my queries appropriately never go stale, except one, my 'profile' query:
const getProfile = async () => {
if (!isAuthenticated()) {
return null;
}
try {
const response = await axios.get('/user/profile');
return response.data;
}
catch (error) {
errorCheck(error);
}
};
export const useProfile = () =>
useQuery('profile', getProfile);
This is the query that holds the current user's profile. isAuthenticated() is a synchronous call that checks to see if we have a user token (so I don't make API calls that I know will fail).
For some reason, in the react-query devtools window, this query shows as stale immediately. I really don't see what I'm doing differently with this one. Any suggestions for debugging this?
Here's what I think the issue was, and how I solved it.
Because I set staleTime: Infinity in my ReactQueryConfigProider, I expected all of my queries to never go stale.
What's different about this query is I invalidate it when something not driven by the UI happens.
I have a session timer in my code that, when the session expired, calls queryCache.invalidateQueries('profile') to trigger any UI displaying the profile to re-render.
It appears that if invalidateQueries is ever called outside the context of a query, the settings in ReactQueryConfigProider are not observed, so staleTime is set to the default, 0.
To resolve this, for the queries I need to invalidate on a timer, I added { staletime: Infinity } to the query explicitly:
export const useProfile = () => {
const { data: session } = useSession();
const userId = session?.userId;
return useQuery(['profile', userId], getProfile, { staleTime: Infinity });
};
I won't go so far as to say this is a bug in react-query, but this seems to be a workaround.
I ran into the same problem, and it was caused by a component that had a child component which used react-query. Check your component tree and make sure nothing uses useProfile() outside of <ReactQueryConfigProvider>.

Resources