I have a subcomponent Viewer that uses a refetch function passed down to its parent Homescreen.
The lazyQuery in homescreen is structured as follows:
const [getById, {loading, error, data, refetch}] = useLazyQuery(GET_BY_ID);
This will get an object from my mongoDB by its id, and when I need to call it again and reload data into my custom activeObject variable, I use the follow function:
const refetchObjects= async () => {
const {loading, error, data } = await refetch();
if (error) { console.log(error);}
if (data) {
activeObject = data.getRegionById;
}
}
However, sometimes the return object of await refetch(); is undefined and I'm not sure why.
Related
This is my second stackoverflow post regarding useQuery hooks. In this issue, i have a fetcher function that does not want to return the data. I also have an onSuccess function, however, it still returns undefined regardless of onSuccess. I was hoping to get some assistance on how useQuery should work so that the onSuccess allows me to work with the data from the fetcher function. The link to the original post from where this code derivated from is How to wait for useQuery data to finish loading to start working with it NextJS.
async function fetchDocs() {
const queryCollection = query(collection(db, loc))
const snapshot = await getDocs(queryCollection);
let arr = []
snapshot.forEach(doc => {
console.log(doc.data())
arr.push(doc.data())
})
return Promise.all(arr)
}
const {data, status} = useQuery({queryKey: ['firestoreData', db]}, {queryFn: () => fetchDocs()}, {
onSuccess: (dataCollection) => {
// do something here, not in useEffect
console.log(data)
return dataCollection
}
});
hi i'm beginner of react and react-query.
below code,
it is correctly working!!
const { data, isLoading, isError, error } = useQuery("comments", () => {
return fetchComments(post.id);
});
and it is not working.
const { data, isLoading, isError, error } = useQuery("comments", fetchComments(post.id))
What is the difference between these?
In first case, you are giving a function that will call fetchComments as a callback function. react-query will take that function and call it, which will call fetchComments
in second case, you are immidietaly running fetchComments function and passing returned value as param, and react-query is trying to run whatever fetchComments will return, which i assume is Promise and not a function
under some specific circumstances, you can just pass the function refernce without calling it, but you will be unable to pass any props:
const { data, isLoading, isError, error } = useQuery("comments", fetchAllComments)
Your first solution is the correct one.
and suggestion, you can use Arrow function without return, for me its more readable
const { data, isLoading, isError, error } = useQuery(["comments", post.id], () => fetchComments(post.id));
I have a helper function to clean up the response from a useQuery call to an api.
Here is the function:
export const cleanResponse = function (data) {
let x = [];
let y = [];
data.forEach((item) => {
x.push(item.title);
y.push(item.price);
});
return { titles: x, prices: y };
};
In my main components I'm using useQuery to get the data then applying the above function to clean it up and prepare it for plotting:
const {
data: products,
isLoading,
isError,
} = useQuery(['products'], () => {
return axios('https://dummyjson.com/products/').then((res) => res.data);
});
const cleanData = cleanResponse(products.products);
const titles = cleanData?.titles;
const prices = cleanData?.prices;
Then I'm rendering a simple bar chart passing the data as props:
<BarChart titles={titles} prices={prices} />
I'm getting the following error:
Uncaught TypeError: Cannot read properties of undefined (reading 'products')
What am I missing here? Should my cleanResponse function be async because it's accepting data from an api?
The error is due to initial state you can do as below to prevent error.
cleanResponse(products?.products || [])
while the query is still running, you component renders with data:undefined.
one way to solve this is to check for undefined before you do your transformation. Other approaches are doing the data transformation at a different time, e.g. in the query function after fetching:
const {
data,
isLoading,
isError,
} = useQuery(['products'], () => {
return axios('https://dummyjson.com/products/')
.then((res) => res.data)
.then((data) => cleanResponse(data.products))
});
That way, the cleaned data will be stored in the cache already and data returned from useQuery will be the cleaned data.
Other approaches include using the select option. You can read more about that in my blog post about react-query data transformations.
I'm developing the front-end for my spring boot application. I set up an initial call wrapped in a useEffect() React.js function:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get(
'/myapi/' + auth.authState.id
);
setData(data);
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
The data returned isn't comprehensive, and needs further call to retrieve other piece of information, for example this initial call return an employee id, but if I want to retrieve his name and display it I need a sub-sequential call, and here I'm experiencing tons of issues:
const getEmployeeName = async id => {
try {
const name = await fetchContext.authAxios.get(
'/employeeName/' + id
);
console.log((name["data"])); // <= Correctly display the name
return name["data"]; // return an [Object promise],
} catch (err) {
console.log(err);
}
};
I tried to wrap the return call inside a Promise.resolve() function, but didn't solve the problem. Upon reading to similar questions here on stackoverflow, most of the answers suggested to create a callback function or use the await keyword (as I've done), but unfortunately didn't solve the issue. I admit that this may not be the most elegant way to do it, as I'm still learning JS/React I'm open to suggestions on how to improve the api calls.
var output = Object.values(data).map((index) =>
<Appointment
key={index["storeID"].toString()}
// other irrelevant props
employee={name}
approved={index["approved"]}
/>);
return output;
Async functions always return promises. Any code that needs to interact with the value needs to either call .then on the promise, or be in an async function and await the promise.
In your case, you should just need to move your code into the existing useEffect, and setState when you're done. I'm assuming that the employeeID is part of the data returned by the first fetch:
const [name, setName] = useState('');
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get(
"/myapi/" + auth.authState.id
);
setData(data);
const name = await fetchContext.authAxios.get(
'/employeeName/' + data.employeeID
);
setName(name.data);
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
// ...
var output = Object.values(appointmentsData).map((index) =>
<Appointment
key={index["storeID"].toString()}
// other irrelevant props
employee={name}
approved={index["approved"]}
/>);
return output;
Note that the above code will do a rerender once it has the data (but no name), and another later when you have the name. If you want to wait until both fetches are complete, simply move the setData(data) down next to the setName
I wanted to use a function as a react hook to wrap fetch requests to an API.
My current hook:
export function useAPI(url, options={}) {
const [auth, setAuth] = useGlobal('auth');
const [call, setCall] = useState(undefined);
const apiFetch = async () => {
const res = await fetch(url, {
...options,
});
if (!res.ok)
throw await res.json();
return await res.json();
};
function fetchFunction() {
fetch(url, {
...options,
});
}
useEffect(() => {
// Only set function if undefined, to prevent setting unnecessarily
if (call === undefined) {
setCall(fetchFunction);
//setCall(apiFetch);
}
}, [auth]);
return call
}
That way, in a react function, I could do the following...
export default function LayoutDash(props) {
const fetchData = useAPI('/api/groups/mine/'); // should return a function
useEffect(() => {
fetchData(); // call API on mount
}, []);
render(...stuff);
}
But it seems react isn't able to use functions in hooks like that. If I set call to fetchFunction, it returns undefined. If I set it to apiFetch, it executes and returns a promise instead of a function that I can call when I want to in the other component.
I initially went for react hooks because I can't use useGlobal outside react components/hooks. And I would need to have access to the reactn global variable auth to check if the access token is expired.
So what would be the best way to go about this? The end goal is being able to pass (url, options) to a function that will be a wrapper to a fetch request. (It checks if auth.access is expired, and if so, obtains a new access token first, then does the api call, otherwise it just does the API call). If there's another way I should go about this other than react hooks, I'd like to know.
Instead of putting your function into useState, consider using useCallback. Your code would look something like this:
export function useAPI(url, options={}) {
const [auth, setAuth] = useGlobal('auth');
function fetchFunction() {
fetch(url, {
...options,
});
}
const call = useCallback(fetchFunction, [auth]);
const apiFetch = async () => {
const res = await fetch(url, {
...options,
});
if (!res.ok)
throw await res.json();
return await res.json();
};
return call
}
The returned function is recreated whenever auth changes, therefore somewhat mimicking what you tried to do with useEffect