I am learning react-query and I meet some problems. I want to use the data I get from fetching data by useQuery, but I get data as undefined. Here is my code:
import React from "react";
import { useQuery } from "react-query";
import { fetchData } from "./providesData";
const Home = () => {
const {data} = useQuery("fetchData", fetchData, {
onSuccess: () => {
console.log("Get data!");
console.log(data); // undefined
}
});
return <div></div>;
};
export default Home;
But I see in react-query devtools that the fetch is succeeded and the data is here. So I think I do not access the data in my onSuccess callback properly. So how can we get access to the data in the callback? I tried:
const query = useQuery("fetchData", fetchData, {
onSuccess: () => {
console.log("Get data!");
console.log(query.data); // undefined
}
});
but still no luck.
I read the documentation and found this:
onSuccess: (data: TData) => void
So I tried this in my code:
const {data} = useQuery("fetchData", fetchData, {
onSuccess: (data: TData) => {
console.log("Get data!");
console.log(data); // success
}
});
This time it works. But I do not understand why... And the code editor also warned me:
Type annotations can only be used in TypeScript files.ts(8010)
Can anyone show me the right way to do it? Thank you so much!
Here is a demo.
I just did this:
useQuery("fetchData", fetchData, {
onSuccess: (data) => {
console.log("Get data!");
console.log(data);
}
});
The onSuccess callback function is called only when the data has been retrieved from the query. Carefully notice that this data is not the one that you're destructing from the useQuery return object, but the one that has been passed to the callback on successful retrieval of data from your API. Since, it is a callback function, you don't need to check for waiting/loading cases, as it will eventually be called if your request succeeds.
In your case, the first data variable will return undefined as soon as the Home component mounts, because the useQuery call
is an asynchronous call which means that data returned from the useQuery call will need to be resolved from a Promise. Thus, it will not be directly available as soon as the Home component mounts, but only after the query resolved successfully. Also, react-query useQuery hook calls the onSuccess(data) callback with the data received before the actual data object(the one returned by the useQuery hook) is set and returned from the hook.
Since useQuery is an asynchronous call (internally), it also provides request in-flight flags like isLoading, isFetching, etc. which you can use to null-check for data, errors, or any other purpose suitable.
Related
I have a React hook that returns a request functions that call an API
It has the following code:
export const useGetFakeData = () => {
const returnFakeData = () =>
fetch('https://fake-domain.com').then(data => console.log('Data arrived: ', data))
return returnFakeData
}
And then I use this hook in component something like this
const getFakeData = useGetFakeData()
useEffect(() => getFakeData(), [getFakeData])
How to achieve this effect in react-query when we need to return a request function from custom hook?
Thanks for any advice!
Digging in docs, I find out that React-Query in useQuery hook provide a refetch() function.
In my case, I just set property enabled to false (just so that the function when mount is not called automatically), and just return a request-function like this
export const useGetFakeData = () => {
const { refetch } = useQuery<void, Error, any>({
queryFn: () =>
fetch('https://fake-domain.com').then(data => console.log('Data arrived: ', data)),
queryKey: 'fake-data',
enabled: false,
})
return refetch
}
You can use useMutation hook if you want to request the data using the imperative way. The data returned from the hook is the latest resolved value of the mutation call:
const [mutate, { data, error }] = useMutation(handlefunction);
useEffect(() => {
mutate(...);
}, []);
I think you are just looking for the standard react-query behaviour, which is to fire off a request when the component mounts (unless you disable the query). In your example, that would just be:
export const useGetFakeData = () =>
useQuery('fakeData', () => fetch('https://fake-domain.com'))
}
const { data } = useGetFakeData()
Please be advised that this is just a bare minimal example:
if you have dependencies to your fetch, they should go into the query key
for proper error handling with fetch, you'll have to transform the result to a failed Promise
I am using the react-query package in my react app. I have a pattern going which uses useQuery to get data from an api with axios. Multiple endpoints work except a GET search/params endpoint. Axios returns the expected data but once loading is complete in useQuery, undefined is returned.
import React from "react";
import { useQuery } from "react-query";
import { api } from "../../../api";
export const useSearch = (query) => {
const searchResults = useQuery(
api.clubhouse.searchCacheKey(query),
api.clubhouse.search({ params: { query: query } })
);
let searchData = [];
let isLoading = searchResults.isLoading;
if (!isLoading) {
searchData = searchResults.data;
}
return { searchData, isLoading };
};
Once isLoading: false, searchResults becomes undefined and I see this error TypeError: config.queryFn.apply is not a function. If I log inside the api.clubhouse.search (which contains the axios call) I can see the correct data. This pattern works fine for other api calls as well...
I'm guessing that this may be your issue:
api.clubhouse.search({ params: { query: query } })
The search function here I am guessing is returning a promise, but you need to pass in a function to useQuery that returns a promise instead of the promise itself. So, maybe make a change like this:
const searchResults = useQuery(
api.clubhouse.searchCacheKey(query),
() => api.clubhouse.search({ params: { query: query } })
);
I made a function component to return data from API. Purpose is to use it throughout my whole app. The code below will show you the structure only (actual code is with graphQL and contains security credentials)
async function GetData() {
console.log("function called")
try {
return await fetch('https://www.7timer.info/bin/astro.php?lon=113.2&lat=23.1&ac=0&unit=metric&output=json&tzshift=0')
} catch (err)
{ console.log('error fetching data from Source', err) }
}
export default GetData
I call it like the following:
Import:
import { useState} from 'react';
import GetData from "./components/getData";
Inside the component:
const [data, setData]= useState([]);
function pullData(){
GetData().then((result)=>{
setData(result);});
}
pullData()
What this does it get the data but put requests continually (output in console "function called"). I know it's for the react state. but I just need one result at a time (and any update supplied by graphQL), that's all.
Is there any way?
Your function is called continuously because it will run at each re-render and is being called at every re-render.
Consider wrapping your logic in a useEffect hook to control when the function will be called: https://reactjs.org/docs/hooks-effect.html
useEffect(() => {
const pullData = async () => {
GetData().then((result) => {
setData(result);
});
}
pullData()
}, [])
If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument.
I'm having issue fetching data and setting them to state in a functional component using useEffect and useState.
My problem is that I would like to keep the data fetching done with axios async/await in a separate file for improving application scalability but then I don't understand how to update the state in case the promise is resolved (not rejected).
In particular I'm trying to retrieve from the promise an array of table rows called data in state, but I can't figure out how to set the result of the responce in the state
Here's the code in the component file:
const [data, setData] = React.useState([]);
useEffect(() => {
const { id } = props.match.params;
props.getTableRows(id).then((res) => {
setData(res);
});
//or is it better:
//props.getTableRows(id).then(setData); ?
}, []);
and my action.js:
export const getTableRows = (id, history) => async (dispatch) => {
try {
const res = await axios.get(`/api/test/${id}`);
dispatch({
type: GET_TEST,
payload: res.data.rows,
});
} catch (error) {
history.push("/test");
}
};
In the above picture it can be seen that the rows array inside the promise response called in action.js is present.
This code unfortunately doesn't work, error: Uncaught (in promise) TypeError: Cannot read property 'forEach' of undefined
I've found out another solution which is the define the promise in the useEffect method like this:
useEffect(() => {
const { id } = props.match.params;
const fetchData = async () => {
const result = await axios.get(`/api/test/${id}`);
setData(result.data.rows);
};
fetchData();
}, []);
this code is working in my app but as I said I don't like having the promises in the components files I would like instead to have them all the promise in action.js for app scalability (in case url change I don't have to change all files) but in that case I don't know where to put the setData(result.data.rows); which seems the right choise in this last example
Any suggestions?
Thanks
You still need to use async/await. The .then() is executed when the value is returned, however your function will continue rendering and won't wait for it. (causing it to error our by trying to access forEach on a null state). After it errors the promise via .then() will update the values and that is why you can see them in the console.
useEffect(() => {
async function getData() {
const { id } = props.match.params;
await props.getTableRows(id).then((res) => {
setData(res);
});
}
getData()
}, []);
Additionally, before you access a state you can check for null values (good practice in general).
if (this.state.somestate != null) {
//Run code using this.state.somestate
}
I don't see you return anything from getTableRows. You just dispatch the result, but hadn't return the res for the function call.
And it will be helpful if you provided error trace.
Somewhat new to React and hooks in React. I have a component that calls a communications hook inside of which a call to an API is made with AXIOS and then the JSON response is fed back to the component. The issue I'm having is the component is calling the hook like six times in a row, four of which of course come back with undefined data and then another two times which returns the expected JSON (the same both of those two times).
I did a quick console.log to double check if it was indeed the component calling the hook mulitple times or it was happening inside the hook, and it is the component.
How do I go about only have the hook called only once on demand and not multiple times like it is? Here's the part in question (not including the rest of the code in the widget because it doesn't pertain):
export default function TestWidget() {
//Fetch data from communicator
console.log("called");
const getJSONData = useCommunicatorAPI('https://jsonplaceholder.typicode.com/todos/1');
//Breakdown passed data
const {lastName, alertList, warningList} = getJSONData;
return (
<h1 id="welcomeTitle">Welcome {lastName}!</h1>
);
}
export const useCommunicatorAPI = (requestAPI, requestData) => {
const [{ data, loading, error }, refetch] = useAxios('https://jsonplaceholder.typicode.com/todos/1', []);
console.log("data in Communicator:", data);
return {data};
}
I would use the useEffect hook to do this on mount and whenever any dependencies of the request change (like if the url changed).
Here is what you will want to look at for useEffect
Here is what it might look like:
const [jsonData, setJsonData] = React.useState({})
const url = ...whatver the url is
React.useEffect(() => {
const doFetch = async () => {
const jsonData = await useAxios(url, []);;
setJsonData(jsonData)
}
doFetch();
}, [url])
...use jsonData from the useState
With the above example, the fetch will happen on mount and if the url changes.
Why not just use the hook directly?
export default function TestWidget() {
const [{ data, loading, error }, refetch] =
useAxios('https://jsonplaceholder.typicode.com/todos/1', []);
return (<h1 id="welcomeTitle">Welcome {lastName}!</h1>);
}
the empty array [] makes the hook fire once when called
Try creating a function with async/await where you fetch the data.
Here can you learn about it:
https://javascript.info/async-await