Cannot call useQuery inside event handler (react-query, react-hook-form) - javascript

I have to call useQuery inside event handler. (user registration) and Im trying something like this.
const [postUser, { isError, isLoading, data }] = useQuery('user', () =>
axios.post(API_URL, { query: GET_TOKEN_QUERY }).then((res) => res.data)
)
const openModalCallback = React.useCallback(
(modalName) => {
onOpenModal(modalName)
},
[onOpenModal]
)
async function onSubmit(data: LoginInputs) {
if (data.checkbox) {
postUser()
}
}
But I get an error like this and cant resolve it. Help me please.

If you want to create/update/delete data or perform server side-effects (opposed to just read data from server), better use useMutation.
The hook returns a mutate() function which you can call onSubmit().

You cannot simply post the data with the useQuery hook, for that you have to use the useMutation() hook and it returns a function which you can call inside an event handler.

Related

how is it difference between queryFunction in react-query?

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

How to access data outside of a Promise

I'm making a react app that sends an API call to OpenWeather to get the weather data for a city (specified by the user). Here's what the request for that call looks like:
async function getAPI() {
const apiCall = await axios.get(apiLink).then(res => {
res = {
temp : res.data.main.temp - 273.15,
weatherIcon : res.data.weather[0].icon,
windSpeed : res.data.wind.speed
}
return res
});
return apiCall
}
const weatherData = getAPI()
Notice that I try to store the data I want from the API response in a variable called weatherData. That way I can simply call that variable whenever I need, heres an example of HTML code that uses this variable:
<p>
temperature is {weatherData.temp} Celcius
</p>
This results in weatherData.temp simply not showing up on the browser side for some reason. A console.log(weatherData) prints this in the console:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Object
temp: 29.53
weatherIcon: "04d"
windSpeed: 1.59
[[Prototype]]: Object
How do I extract the data from the promise in a way that allows me to easily refer to said data for use in HTML code?
Answer below is if you are using functional components and react hooks.
You can can go two directions:
Using a try catch block:
const fetchWeather = async () => {
try {
const res = await axios.get(apiLink);
console.log(res);
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// you can then set the data you need to your state to render it.
} catch (error) {
// handle error
}
}
Or you can use .then .catch
const fetchWeather = async () => {
axios.get(apiLink)
.then((res) => {
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// set the data you need from the respones to your state.
})
.catch((err) => {
// handle error
})
}
In both cases you can just call the function in your useEffect hook.
useEffect(() => {
fetchWeather()
}, [])
In general my preference goes to set the response you get from the Api into the local state (meaning the state of your page/component). And then rendering the state to your jsx.
So if you are using react hooks, your state could look like this:
const [weather, setWeather] = useState({});
Last Edit:
Finally you can just refer to your state within your jsx/html. Assuming your weather state looks like this:
{
temp: '50 degrees'
}
In your JSX you can just refer to it this way:
<>
<div>{weather.temp}</div>
</>

Fetch data in redux reducer and action - how to do it

I have problem to find out how to replace my fetch function into my action and reducer. Here is how does it look like without redux
async getData() {
const amount = this.props.amount;
if (amount === isNaN) {
return;
} else {
try {
await fetch(
`https://api.exchangeratesapi.io/latest?base=${this.props.base}`,
)
.then(res => res.json())
.then(data => {
const date = data.date;
const result = (data.rates[this.props.convertTo] * amount).toFixed(
4,
);
this.setState({
result,
date,
});
}, 3000);
} catch (e) {
console.log('error', e);
}
}
}
How should look action or reducer or both in redux to get the same result ?
Redux doesn't allow you to do api calls in actions or reducers. Instead, you can use libraries like redux-observable, redux-saga or redux-thunk. I personally recommend redux-observable.
If you still want to try doing it on your own, you'll have to create three actions - one for request, one for api call success and one for failure. You can write the same code and instead of setState dispatch appropriate actions before and after the call resolves.

Setting a useEffect hook's dependency within`useEffect` without triggering useEffect

Edit: It just occurred to me that there's likely no need to reset the variable within the useEffect hook. In fact, stateTheCausesUseEffectToBeInvoked's actual value is likely inconsequential. It is, for all intents and purposes, simply a way of triggering useEffect.
Let's say I have a functional React component whose state I initialize using the useEffect hook. I make a call to a service. I retrieve some data. I commit that data to state. Cool. Now, let's say I, at a later time, interact with the same service, except that this time, rather than simply retrieving a list of results, I CREATE or DELETE a single result item, thus modifying the entire result set. I now wish to retrieve an updated copy of the list of data I retrieved earlier. At this point, I'd like to again trigger the useEffect hook I used to initialize my component's state, because I want to re-render the list, this time accounting for the newly-created result item.
​
const myComponent = () => {
const [items, setItems] = ([])
useEffect(() => {
const getSomeData = async () => {
try {
const response = await callToSomeService()
setItems(response.data)
setStateThatCausesUseEffectToBeInvoked(false)
} catch (error) {
// Handle error
console.log(error)
}
}
}, [stateThatCausesUseEffectToBeInvoked])
const createNewItem = async () => {
try {
const response = await callToSomeService()
setStateThatCausesUseEffectToBeInvoked(true)
} catch (error) {
// Handle error
console.log(error)
}
}
}
​
I hope the above makes sense.
​
The thing is that I want to reset stateThatCausesUseEffectToBeInvoked to false WITHOUT forcing a re-render. (Currently, I end up calling the service twice--once for win stateThatCausesUseEffectToBeInvoked is set to true then again when it is reset to false within the context of the useEffect hook. This variable exists solely for the purpose of triggering useEffect and sparing me the need to elsewhere make the selfsame service request that I make within useEffect.
​
Does anyone know how this might be accomplished?
There are a few things you could do to achieve a behavior similar to what you described:
Change stateThatCausesUseEffectToBeInvoked to a number
If you change stateThatCausesUseEffectToBeInvoked to a number, you don't need to reset it after use and can just keep incrementing it to trigger the effect.
useEffect(() => {
// ...
}, [stateThatCausesUseEffectToBeInvoked]);
setStateThatCausesUseEffectToBeInvoked(n => n+1); // Trigger useEffect
Add a condition to the useEffect
Instead of actually changing any logic outside, you could just adjust your useEffect-body to only run if stateThatCausesUseEffectToBeInvoked is true.
This will still trigger the useEffect but jump right out and not cause any unnecessary requests or rerenders.
useEffect(() => {
if (stateThatCausesUseEffectToBeInvoked === true) {
// ...
}
}, [stateThatCausesUseEffectToBeInvoked]);
Assuming that 1) by const [items, setItems] = ([]) you mean const [items, setItems] = useState([]), and 2) that you simply want to reflect the latest data after a call to the API:
When the state of the component is updated, it re-renders on it's own. No need for stateThatCausesUseEffectToBeInvoked:
const myComponent = () => {
const [ items, setItems ] = useState( [] )
const getSomeData = async () => {
try {
const response = await callToSomeService1()
// When response (data) is received, state is updated (setItems)
// When state is updated, the component re-renders on its own
setItems( response.data )
} catch ( error ) {
console.log( error )
}
}
useEffect( () => {
// Call the GET function once ititially, to populate the state (items)
getSomeData()
// use [] to run this only on component mount (initially)
}, [] )
const createNewItem = async () => {
try {
const response = await callToSomeService2()
// Call the POST function to create the item
// When response is received (e.g. is OK), call the GET function
// to ask for all items again.
getSomeData()
} catch ( error ) {
console.log( error )
}
} }
However, instead of getting all items after every action, you could change your array locally, so if the create (POST) response.data is the newly created item, you can add it to items (create a new array that includes it).

$emit inside async method - Vue2

Here's my child's component async method:
async created () {
this.$parent.$emit('loader', true)
await this.fetchData()
this.$parent.$emit('loader', false)
}
fetchData does an axios get call, to fetch data from API. However in a vue-devtools (events tab) i can only see the events, after i change the code and it hot reloads. Also i've set up console.log() in a parent component:
mounted() {
this.$on('loader', (value) => {
console.log(value)
})
}
And i can see only false in a console. My purpose is to emit loader set to true (so i can show the loader), then set it to false, when data is fetched.
My fetchData method:
import http from '#/http'
fetchData() {
return http.getOffers().then((resp) => {
this.offersData = resp.data
})
}
Contents of http.js:
import axios from 'axios'
import config from '#/config'
const HTTP = axios.create({
baseURL: config.API_URL
})
export default {
/* calculator */
getOffers() {
return HTTP.get('/url')
}
}
If i directly use return axios.get() in async created(), then it works. Problem is in this imported http instance.
Final solution
One of the problems was using different lifecycles, thanks to Evan for mentioning this.
Another problem was with async / await usage, changes to a fetchData() method:
import http from '#/http'
async fetchData() {
await http.getOffers().then((resp) => {
this.offersData = resp.data
})
}
I had to make this method async and use await on axios request, since await is thenable, it does work. Also i've spotted an issue in https.js:
export default {
/* calculator */
getOffers() {
return HTTP.get('/url')
}
}
It returns HTTP.get(), not a promise itself, i could have used then here, and it would work, but, for flexibility purposes i didn't do that.
But, still, i don't get why it didn't work:
fetchData() {
return http.getOffers().then((resp) => {
this.offersData = resp.data
})
}
Isn't it already returning a promise, since it's chained with then... So confusing.
Retested again, seems like return is working, lol.
The issue here is that created on the child component is getting called before mounted on the parent component, so you're beginning to listen after you've already started your Axios call.
The created lifecycle event method does not do anything with a returned promise, so your method returns right after you begin the Axios call and the rest of the vue component lifecycle continues.
You should be able to change your parent observation to the created event to make this work:
created() {
this.$on('loader', (value) => {
console.log(value)
})
}
If for some reason you need to do something that can't be accessed in created, such as accessing $el, I'd suggest moving both to the mounted lifecycle hook.
I'd simply suggest restructuring your method, as there isn't really a need to make an async method since axios itself is asnychronus.
If you already have the fetchData method defined, and the goal is to toggle the loader state when a call is being made, something like this should do.
fetchData () {
this.$parent.$emit("loader", true)
axios.get(url)
.then(resp => {
this.data = resp
this.$parent.$emit("loader", false)
})
}
Of course these then statements could be combined into one, but it's the same idea.
Edit: (using the parent emit function)
fetchData () {
this.loader = true
axios.get(url)
.then(resp => this.data = resp)
.then(() => this.loader = false)
}
If what you are trying to achieve is to tell the direct parent that it's no longer loading, you would have to emit to the same instance like so
async created () {
this.$emit('loader', true)
await this.fetchData()
this.$emit('loader', false)
}
By removing the$parent, you will emit from the current component.
--Root
--My-page.vue
-Some-child.vue
Now you will emit from some-child.vue to my-page.vue. I have not tried, but theoretically what you are doing by emiting via parent: (this.$parent.$emit('loader', false)) You are emitting from my-page.vue to root.
So If you have a $on or #loader on the component like so: <Some-child #loader="doSomething"/>, This will never run due to you emitting from the parent.

Categories