How to use onSuccess with useQueries in react-query? - reactjs

import { useQueries } from "react-query";
import axios from "axios";
const fetchFriend = id => {
return axios.get(`http://localhost:4000/friends/${id}`);
};
const useDynamicFriends = friendIds => {
const queryResult = useQueries(
friendIds.map(id => {
return {
queryKey: ["friends", id],
queryFn: () => fetchFriend(parseInt(id)),
}
})
);
const isLoading = queryResult.some(result => result.isLoading)
return {isLoading, queryResult};
}
export default useDynamicFriends;
I need to use an onSuccess method just like we can use in useQuery, that will run only after all api call is done.

You can use queryCache.subscribe to achive that
queryCache.subscribe(() => {
const allQueriesResolved = queryResult.every(result => !result.isLoading);
if (allQueriesResolved) {
// Run your desired logic here
}
});
and import
import { useQueries, queryCache } from "react-query";

Related

React Query Invalid hook call when I introduce useQueryClient

I am experiencing the below error when I introduce useQueryClient? Any ideas why this may be?
I am trying to invalidateQueries for a queryKey onSuccess of the useUpdateEmployee hook.
bundle.js:1427 Uncaught 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:
Component
import { useFetchEmployee, useUpdateEmployee } from '../Users/Usershooks';
const User = () => {
const userData = {
name: 'test'
};
const { data } = useFetchEmployee(userID);
const { mutate } = useUpdateEmployee(userID, userData);
const saveChangesOnClick = () => {
mutate();
};
return (
<div>
...
</div>
);
};
export default User;
HookFile
import axios from 'axios';
import { useMutation, useQuery, useQueryClient } from 'react-query';
const queryClient = useQueryClient();
export const useFetchEmployers = () => useQuery(['fetchEmployers'], () => axios.get('https://jsonplaceholder.typicode.com/users')
.then(response => response.data));
export const useFetchEmployee = (userID: any) => useQuery(['fetchEmployers', userID], () => axios.get(`https://jsonplaceholder.typicode.com/users/${userID}`)
.then(response => response.data));
export const useUpdateEmployee = (userID: any, userData: any) => useMutation(
() => axios.put(`https://jsonplaceholder.typicode.com/users/${userID}`, userData)
.then(response => response.data),
{
onSuccess: () => {
console.log("success");
queryClient.invalidateQueries(['fetchEmployers']);
}
}
);
useQueryClient is a hook, it has to be initialized in a React component or in a custom hook. Just move it inside the useUpdateEmployee.
export const useUpdateEmployee = (userID: any, userData: any) => {
const queryClient = useQueryClient();
return useMutation(
...,
onSuccess: () => {
queryClient.invalidateQueries(['fetchEmployers']);
}
);
};

useParam outside react component

I am trying to pass a variable value which uses useParam hook so i can pass it to my api which set outside of the component function.
VesselComponent.js :
import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { fetchComponents } from "../../../features/componentsSlice";
import TreeItem from "#mui/lab/TreeItem";
import TreeView from "#mui/lab/TreeView";
import ExpandMoreIcon from "#mui/icons-material/ExpandMore";
import ChevronRightIcon from "#mui/icons-material/ChevronRight";
import { Link, Outlet, useParams } from "react-router-dom";
import axios from "axios";
export const api = async () => {
try {
const res = await axios.get(
// here
`http://127.0.0.1:8000/api/maintenance/${vesselId}`
);
return res.data;
} catch (error) {
console.log(error);
}
};
function VesselComponents() {
// this line
const vesselId = useParams();
const { components, error, loading } = useSelector(
(state) => state.components
);
// rest of the code
You can try to pass a param to api that would help you have vesselId from other places including useParams
export const api = async (vesselId) => {
try {
const res = await axios.get(
// here
`http://127.0.0.1:8000/api/maintenance/${vesselId}`
);
return res.data;
} catch (error) {
console.log(error);
}
};
Here is how we call it
const vesselId = useParams();
api(vesselId);
You can only use react hooks at the top level inside a component. You shouldn't call useParams in your api function. Instead, you should pass it to your api function and use some state to store the response from your API. Something like this:
export const api = async (vesselId) => {
try {
const res = await axios.get(
// here
`http://127.0.0.1:8000/api/maintenance/${vesselId}`
);
return res.data;
} catch (error) {
console.log(error);
}
};
function VesselComponents() {
// this line
const vesselId = useParams();
const [vesselData, setVesselData] = useState();
const { components, error, loading } = useSelector(
(state) => state.components
);
const fetchVesselData = async () => {
try {
const res = await api(vesselId);
setVessesData(res);
} catch (e) {
// handle error
}
}
useEffect(() => {
fetchVesselData()
});

React-redux store state is empty

I have axios making a get request to fetch a request a list of vessels and their information,
i am trying to use redux slice, and populate the data using dispute, however the state is always empty dispute not having any errors
the vesselSlice :
import { createSlice } from "#reduxjs/toolkit";
import { api } from "../components/pages/screens/HomeScreen";
const vesselSlice = createSlice({
name: "vessels",
initialState: {
vessels: [],
},
reducers: {
getVessels: (state, action) => {
state.vessels = action.payload;
},
},
});
export const vesselReducer = vesselSlice.reducer;
const { getVessels } = vesselSlice.actions;
export const fetchVessels = () => async (dispatch) => {
try {
await api
.get("/vessels")
.then((response) => dispatch(getVessels(response.data)));
} catch (e) {
return console.error(e.message);
}
};
the HomeScreen.js :
import React, { useEffect } from "react";
import VesselCard from "../../VesselCard";
import axios from "axios";
import { useDispatch, useSelector } from "react-redux";
import { fetchVessels } from "../../../features/vesselSlice";
export const api = () => {
axios
.get("http://127.0.0.1:8000/api/vessels/info")
.then((data) => console.log(data.data))
.catch((error) => console.log(error));
};
function HomeScreen() {
const { vessels, isLoading } = useSelector((state) => state.vessels);
const dispatch = useDispatch();
// This part:
useEffect(() => {
fetchVessels(dispatch);
}, [dispatch]);
return (
<div>
Fleet vessels :
<div className="fleet-vessels-info">
{vessels.map((vessel) => (
<VesselCard vessel={vessel} />
))}
</div>
</div>
);
}
export default HomeScreen;
You receive dispatch this way. It is not being received from any middleware.
export const fetchVessels = async (dispatch) =>{}
If you want to continue using your approach, call function this way
useEffect(() => {
fetchVessels()(dispatch);
}, [dispatch]);
Api function error
export const api = async () => {
try {
const res = await axios.get("http://127.0.0.1:8000/api/vessels/info");
return res.data;
} catch (error) {
console.log(error);
}
};
export const fetchVessels = () => async (dispatch) => {
try {
await api()
.then((data) => dispatch(getVessels(data)));
} catch (e) {
return console.error(e.message);
}
};

How to remove data on logout when using firebase and react query?

When my user logs out, I want to remove all user data from the app, but I'm having trouble implementing this.
I have a custom useUserData() hook that gets the user's data. getUser() is a callable cloud function. This is my code so far.
import { useEffect, useState } from "react"
import { useQuery, useQueryClient } from "react-query"
import { getUser } from "Services/firebase/functions"
import firebase from "firebase/app"
export default function useUserData(){
const [ enabled, setEnabled] = useState(false)
const queryClient = useQueryClient()
useEffect(_ => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
setEnabled(Boolean(user))
if (!user){
// remove data
}
else queryClient.invalidateQueries("user", { refetchActive: true, refetchInactive: true })
})
return unsubscribe()
}, [])
return useQuery(
"user",
() => getUser().then(res => res.data),
{
enabled
}
)
}
Edit:
It seemed that I was handling my effect cleanup wrong. This seems to be working.
import { useEffect, useState } from "react"
import { useQuery, useQueryClient } from "react-query"
import { getUser } from "Services/firebase/functions"
import firebase from "firebase/app"
export default function useUserData(){
const [ enabled, setEnabled] = useState(false)
const queryClient = useQueryClient()
useEffect(_ => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
setEnabled(Boolean(user))
if (!user) {
queryClient.removeQueries("user")
}
})
return _ => unsubscribe()
}, [])
return useQuery(
"user",
() => getUser().then(res => res.data),
{
enabled
}
)
}
Weirdly enough, the query still fetches once after logging out, when the query should already be disabled.
queryClient.removeQueries("user")
will remove all user related queries. It's a good thing to do on logout. You can clear everything by calling removeQueries without parameters.
Here's my current implementation which seems to work fine.
import { useEffect, useState } from "react"
import { useQuery, useQueryClient } from "react-query";
import firebase from "firebase/app"
export default function useAuthenticatedQuery(key, func, options){
const [ enabled, setEnabled] = useState(false)
const queryClient = useQueryClient()
useEffect(_ => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
setEnabled(Boolean(user))
if (!user){
queryClient.setQueryData(key, _ => undefined)
queryClient.removeQueries(key, _ => undefined)
}else
queryClient.invalidateQueries(key, { refetchActive: true, refetchInactive: true })
})
return _ => unsubscribe()
// eslint-disable-next-line
}, [])
return useQuery(
key,
func,
{
...options,
enabled
}
)
}
I use it just like the regular useQuery hook:
import useAuthenticatedQuery from "Hooks/useAuthenticatedQuery"
export default function useUserData(){
return useAuthenticatedQuery(
"user",
() => getUser().then(res => res.data)
)
}

React TypeError when creating custom hook

I have following hook
import axios from "axios";
import {useKeycloak} from "#react-keycloak/web";
import {useEffect, useState} from "react";
export const useAdminApi = () => {
const {keycloak} = useKeycloak();
const [axiosInstance, setAxiosInstance] = useState(undefined);
useEffect(() => {
let instance = axios.create({
baseURL: `${process.env.REACT_APP_ADMIN_API_URL}`,
headers: {
Test: 'test',
Authorization: 'Bearer ' + keycloak.token,
}
});
setAxiosInstance(instance);
return () => {
setAxiosInstance(undefined);
}
}, [keycloak.token]);
const getUsers = ({query}) => {
return axiosInstance.get(`/users${query}`)
};
const getUserDetail = ({userId}) => {
return axiosInstance.get(`/users/${userId}`)
};
const deleteUser = ({userId}) => {
return axiosInstance.delete(`/users/${userId}`)
};
return {
getUsers,
getUserDetail,
deleteUser
}
};
When I log instance it's logged with all config
From useAdminApi I'd like to export functions like getUserDetail, deleteUser, ...
Then in other component, I'd like to use this functions so I have following:
const UserForm = () => {
const {getUserDetail} = useAdminApi();
useEffect(() => {
if (!userId) {
setIsNew(true);
} else {
setIsNew(false);
getUserDetail({userId})
.then(result => setUserData(result.data))
.catch(error => pushError(push, error));
}
}, [userId]);
...
}
However, when I display the UserForm I'm getting following error: TypeError: Cannot read property 'get' of undefined which is pointing to this line return axiosInstance.get(`/users/${userId}`)
Can somebody please tell me what's wrong with this approach?
You're setting axiosInstance's initial value as undefined but TypeScript doesn't infer the type you want. useState is a generic function, so what you can do is pass the type yourself.
import axios, { AxiosInstance } from 'axios';
const [axiosInstance, setAxiosInstance] = useState<AxiosInstance | undefined>(
undefined
);
Then in your functions you still need to check if axiosInstance is undefined.
If you have TypeScript 3.7 or higher you can achieve this with Optional Chaining.
const getUsers = ({ query }: any) => {
return axiosInstance?.get(`/users${query}`);
};

Resources