How to invalidate react-query whenever state is changed? - reactjs

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]
)

Related

useEffect does not store state for Firebase data on initial load and only shows up after fast refresh

UPDATE
#Chris's answer below helped me with my issue. Installed the React Firebase Hooks package and set up my code as seen below. Everything is working flawlessly now! :)
const [value] = useCollection(collection(db, `${sessionUserId}`), {
snapshotListenOptions: { includeMetadataChanges: true },
})
useEffect(() => {
if (value) {
const allData = JSON.parse(
JSON.stringify(
value?.docs.map((doc) => ({
id: doc.id as string,
resultData: doc.data() as DocumentData,
}))
)
)
setAllLogs(allData)
}
}, [value])
What am I using in my project? NextAuthjs, Firebase, Recoiljs
I have a useRecoilState hook to save logs that users have created.
const [allLogs, setAllLogs] = useRecoilState(modalAllLogs)
The modalAllLogs looks like this:
export const modalAllLogs = atom<Array<DocumentData>>({
key: 'allLogs',
default: [],
})
Here is the issue. I have a useEffect hook that grabs the data the user has from the database and assigns setAllLogs to that data. Note: I * do not * have the state updating anywhere else.
useEffect(() => {
const docQuery = query(
collection(db, `${sessionUserId}`),
orderBy('timestamp', 'desc')
)
const unsubscribe = onSnapshot(
docQuery,
(snapshot: QuerySnapshot<DocumentData>) => {
const data = JSON.parse(
JSON.stringify(
snapshot.docs.map((doc: QueryDocumentSnapshot<DocumentData>) => ({
id: doc.id as string,
resultData: doc.data() as DocumentData,
}))
)
)
setAllLogs(data)
}
)
return unsubscribe
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [db])
What happens is, on initial render, allLogs returns an empty array, which makes sense, but stays empty until I make any edit within the same file the useEffect hook is in, save it, and NextJS triggers it's fast render, only then does allLogs suddenly display the data properly. I did a console.log(snapshot) and it is empty on every initial render as well.
Whenever I add data using the inputs I have created, Firebase stores all the data properly and correctly. The only issue is when the data is not loaded in on initial render. Other than that, I have no other issues.
I am not sure what I am doing wrong here. Maybe it is the RecoilState that I am utilizing wrong?
I have tried many things from other forums - changing the dependency to allLogs (which gave me an infinite loop), tried using getDocs() instead of onSnapshot(), and etc.
The problem here is, that the useEffect isn't triggered because the "db" constant doesn't change.
Have a look at these custom hooks for Firebase/firestore https://github.com/CSFrequency/react-firebase-hooks/tree/master/firestore
or lift the onSnapshot up to get real-time results. But in the last case the renders won't be triggered so I think it's the best to make use of custom hooks.

How to use zustand to store the result of a query

I want to put the authenticated user in a zustand store. I get the authenticated user using react-query and that causes some problems. I'm not sure why I'm doing this. I want everything related to authentication can be accessed in a hook, so I thought zustand was a good choice.
This is the hook that fetches auth user:
const getAuthUser = async () => {
const { data } = await axios.get<AuthUserResponse>(`/auth/me`, {
withCredentials: true,
});
return data.user;
};
export const useAuthUserQuery = () => {
return useQuery("auth-user", getAuthUser);
};
And I want to put auth user in this store:
export const useAuthStore = create(() => ({
authUser: useAuthUserQuery(),
}));
This is the error that I get:
Error: Invalid hook call. Hooks can only be called inside of the body
of a function component. This could happen for one of the following
reasons.
you can read about it in the react documentation:
https://reactjs.org/warnings/invalid-hook-call-warning.html
(I changed the name of some functions in this post for the sake of understandability. useMeQuery = useAuthUserQuery)
I understand the error but I don't know how to fix it.
The misunderstanding here is that you don’t need to put data from react query into any other state management solution. React query is in itself a global state manager. You can just do:
const { data } = useAuthUserQuery()
in every component that needs the data. React query will automatically try to keep your data updated with background refetches. If you don’t need that for your resource, consider setting a staleTime.
—-
That being said, if you really want to put data from react-query into zustand, create a setter in zustand and call it in the onSuccess callback of the query:
useQuery(key, queryFn, { onSuccess: data => setToZustand(data) })

React-query, query doesn't update arguments

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.

Why is my React UseEffect (that uses Firebase onSnapshot) in an infinite loop, constantly mounting and unmounting?

I have a header (like facebook I suppose) and there is a notifications bell. I want to listen for new notifications on Firbase Firestore.
I've set up a useEffect, but theres an infinite loop for some reason, and I'm not sure why?
Here is the useEffect/useState:
const [subbedToNotifications, setSubbedToNotifications] = useState(false);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
useEffect(() => {
var unsubscribe = () => {
console.log("this function does nothing");
};
console.log("subscribing to notifications");
if (
userData !== null &&
auth.currentUser.uid !== null &&
!subbedToNotifications
) {
setSubbedToNotifications(true);
unsubscribe = db
.collection("users")
.doc(auth.currentUser.uid)
.onSnapshot((snapshot) => {
dispatch({
type: "SET_USERDATA",
userData: snapshot.data(),
});
});
}
// Detach listener
return () => {
console.log("unsubbing from notifications");
unsubscribe();
setSubbedToNotifications(false);
};
}, [subbedToNotifications]);
So, when the component mounts and whenever subbedToNotifications changes, it'll run this use effect. The IF wrapped around the call to Firebase requires userData (where I store things such as username, profile picture, bio etc.), firebase auth to not be empty, as well as subbedToNotifications to be false. When the call to firebase is made, subbedToNotifications is set to true, so Therefore that shouldn't be called again and should only be subscribed once.
Also the return unsubscribes from firebase, however the console logs were repeating:
console.log("subscribing to notifications") and console.log("unsubbing from notifications") constantly, racking up 6k reads to the database.
Can anyone spot my mistake as to why the useEffect is getting into a loop, or perhaps why the Header is constantly mounting and unmounting?
TIA!
Try changing [subbedToNotifications] to []

React.js issues with re-rendering when trying to show notifications in a context from Apollo useQuery

So this is my what I am trying to do. I have created a global notifications context with a reducer to change the state.
My issue case now is, I am using Apollo hooks to do a query useQuery and when there is an error I want to show it in my global notifications.
So to do this, I need to dispatch to the reducer to show the notification, change the title and the message in the notification.
This triggers a state change on the global notification context which triggers a rerender which triggers the useQuery again. All this triggers an infinite rerender if you have an error response every time.
Does someone have a suggestion to have a global notifications system that could work for this?
The only solution I have left is to import the notification on every page where I need a notification.
Code:
const [state, dispatch] = useContext(NotificationsContext);
const { loading, error, data } = useQuery(GET_USERS);
useEffect(() => {
if (error) {
dispatch({
type: 'ERROR',
payload: {
title: 'Query Error',
subtitle: error.message,
},
});
}
if (!loading && data) {
const newData = [];
data.getUsers.users.forEach((user) => {
const newUserRow = {};
headers.forEach((header) => newUserRow[header] = user[header]);
newData.push(newUserRow);
});
setRows(newData);
}
}, []);
I think you are using functional component and you can solve this by using useQuery inside the useEffect hook also do not forget to pass empty array as a 2nd argument of useEffect.
Hope this will solve your problem.
https://medium.com/#felippenardi/how-to-do-componentdidmount-with-react-hooks-553ba39d1571

Resources