I have an endpoint that generates and return a url for making payment online, it accepts only two arguments amount and currency.
initializeCardDeposit: builder.query({
query: ({ amount, currency }) => ({
url: "/initialize/card/deposit",
method: "POST",
body: {
amount,
currency,
},
}),
transformResponse: ({ data }) => data,
providesTags: ["cardDepositUrl"],
})
I want to get a new url anytime the the query is called with the same amount and currency, instead it returns a cached data.
I have tried dispatching invalidateTags("cardDepositUrl") and it didn't work.
1 -> First of all, you have to provide tagTypes to your service
2 -> You provideTags should have only the values mentioned inside tagTypes
3 -> invalidateTags[], should be called from another endpoint of the same service
Checkout this documentation -> https://redux-toolkit.js.org/rtk-query/usage/automated-refetching
or
Re-fetching on demand with refetch/initiate
In order to achieve complete granular control over re-fetching data, you can use the refetch function returned as a result property from a useQuery or useQuerySubscription hook.
Calling the refetch function will force refetch the associated query.
Alternatively, you can dispatch the initiate thunk action for an endpoint, passing the option forceRefetch: true to the thunk action creator for the same effect.
Force refetch example
import { useDispatch } from 'react-redux'
import { useGetPostsQuery } from './api'
const Component = () => {
const dispatch = useDispatch()
const { data, refetch } = useGetPostsQuery({ count: 5 })
function handleRefetchOne() {
// force re-fetches the data
refetch()
}
function handleRefetchTwo() {
// has the same effect as `refetch` for the associated query
dispatch(
api.endpoints.getPosts.initiate(
{ count: 5 },
{ subscribe: false, forceRefetch: true }
)
)
}
return (
<div>
<button onClick={handleRefetchOne}>Force re-fetch 1</button>
<button onClick={handleRefetchTwo}>Force re-fetch 2</button>
</div>
)
}
reference -> https://redux-toolkit.js.org/rtk-query/usage/cache-behavior#re-fetching-on-demand-with-refetchinitiate
You can use useLazyQuery instead of useQuery in your component and make use of trigger function returned by useLazyQuery hook. Trigger function always initiates a new request, even if cached result is available.
Inside your component
const [trigger,{data}]= useLazyInitializeCardDepositQuery();
When you want to fetch data, initiate request using trigger({amount,currency})
If you want to fetch data on initial render, you can add an useEffect hook
useEffect(()=>{
trigger({amount,currency})
},[])
uselazyquery documentation: https://redux-toolkit.js.org/rtk-query/api/created-api/hooks#uselazyquery
Related
What I have
I am getting the user's location (latitude/longitude) which I use to call a google geocode API, unless the user's coords change, the request is not running again, since the query it uses the user's coords as queryKey array dependecy.
The problem
the problem is that I'm running some operations in the onSuccess query method, this method is only run when any of the queryKey dependencies change, and I mentioned this not happen.
How to run the onSuccess method whether the queryKey dependencies change or not?
Reference code
export const useGoogleReverseGeocoding = (coords) => {
const url = 'someUrl';
const request = createClient(); // axios abstraction
return useQuery({
queryKey: ['google-geocode', coords],
queryFn: request,
enabled: !!coords,
onSuccess: (data) => {
const searchTerm = removeGlobalCodeText(data?.plus_code?.compound_code);
// set searchterm in a global store. This searchterm change with
// different user actions, so if the user re-share his location
// I need to run onSuccess transformation again.
setSearchTerm(searchTerm);
},
});
};
As I was explaining in my comment, onSuccess can't be fired without the query itself firing again. Since certain user actions should trigger the transformations on onSuccess, you have a couple of ways to go about this, one of them would be to move these transformations on a useEffect hook and add some user action related flag on the dependencies array. The other proposed solution would be to invalidate the query upon these user actions, so it will be refetched and the transformations on onSuccess will execute.
You can achieve this using useQueryClient hook which returns the current QueryClient instance. You can invalidate the query from anywhere as long as the component is wrapped by QueryClientProvider. For this example and for convenience, I will include this hook on useGoogleReverseGeocoding custom hook.
Example:
Custom hook:
export const useGoogleReverseGeocoding = (coords) => {
const queryClient = useQueryClient()
const url = 'someUrl';
const request = createClient(); // axios abstraction
const geocodingData = useQuery({
queryKey: ['google-geocode', coords],
queryFn: request,
enabled: !!coords,
onSuccess: (data) => {
const searchTerm = removeGlobalCodeText(data?.plus_code?.compound_code);
// set searchterm in a global store. This searchterm change with
// different user actions, so if the user re-share his location
// I need to run onSuccess transformation again.
setSearchTerm(searchTerm);
},
});
const invalidateQueryOnAction = () => queryClient.invalidateQueries(['google-geocode'])
return { geocodingData, invalidateQueryOnAction }
};
Some component:
const dummyCoords = {
lat: 33.748997,
lng: -84.387985
}
const SomeComponent = () => {
const { geocodingData, invalidateQueryOnAction } =
useGoogleReverseGeocoding(dummyCoords)
const handleSomeUserAction = () => {
// handle action...
// Invalidate query, so the query gets refetched and onSuccess callback executes again
invalidateQueryOnAction()
}
}
PS: If #TkDodo comes along with a different solution for this, I would suggest to go for it instead.
currently i am using SWR to data fetching, i trying to use Mutation feature of SWR to refetch the new data but this occurred a problem when i am calling mutate() by key was added new query params.
Here's my code not working:
import useSWR, { useSWRConfig } from 'swr'
function Profile () {
const { mutate } = useSWRConfig()
const { data } = useSWR('/api/post', fetcher)
return (
<div>
<h1>Title post {data.title}.</h1>
<button onClick={() => {
mutate('/api/post?author=1&pricing=1')
}}>
View more information of this post!
</button>
</div>
)
}
I read docs from SWR and i know the key of the mutate should be the same to key in useSWR() but in my case need more query params to get the corresponding data
How can i solve this problem?
Helps me please!
I wouldn't recommend using mutate in this scenario because the key (the URL) you want to use in the mutation is different from the original one. When mutate is called it'll update the cache for '/api/post' which will then contain data from '/api/post?author=1&pricing=1' in it.
As an alternative, I'd suggest you make the key an array in the useSWR call so that multiple arguments can be passed to the fetcher.
const [queryParams, setQueryParams] = useState('')
const { data } = useSWR(['/api/post', queryParams], fetcher)
Then, in your button onClick handler, you can update the queryParams state value to trigger a re-render and initiate a new request with the query parameters.
<button onClick={() => {
setQueryParams('?author=1&pricing=1')
}}>
View more information of this post!
</button>
You also need to modify the fetcher function slightly to expect multiple arguments, and also append the query params you pass to the URL.
const fetcher = (url, queryParams = '') => {
// Example fetch to demonstrate the logic
return fetch(`${url}${queryParams}`)
}
With this change you now have different keys (and cached data) for each URL you make a request for.
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.
I am using the Yelp Fusion API to get a list of restaurants from Yelp. However, I am always constantly sending a GET request and I am not sure what is going on or how to fix it. I have tried React.memo and useCallback. I think the problem lies within how I am making the call rather than my component rerendering.
Here is where I send a GET request
// Function for accessing Yelp Fusion API
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
// Saving our results, getting first 5 restaurants,
// and turning off our loading screen
setYelpResults({businesses: response.data.businesses.splice(0, 5)});
setEnableLoading(1);
}
catch (error) {
setEnableLoading(2);
}
};
This is where I use axios.
// Our Yelp Fusion code that sends a GET request
export default axios.create({
baseURL: `${'https://cors-anywhere.herokuapp.com/'}https://api.yelp.com/v3`,
headers: {
Authorization: `Bearer ${KEY}`
},
})
You are probably calling that function within your functional component and that function sets a state of that component, so it re-renders. Then the function is executed again, sets state, re-renders and so on...
What you need to do is to wrap that API call inside a:
useEffect(() => {}, [])
Since you probably want to call it one time. See useEffect doc here
You can do 2 things either use a button to get the list of restaurants because you are firing your function again and again.
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
Use a button instead maybe so once that button is clicked function is fired.
<button onClick={yelpFusionSearch} />Load More Restaurants </button>
Use your fuction inside useEffect method which will load 5 restaurants once the page renders
useEffect(() => {
const yelpFusionSearch = async () => {
try {
const response = await yelp.get('/businesses/search', {
params: {
term: food,
location: location
}
})
}, [])
I adopted Redux in my project for state control, and also Axios for fetching api in action.
But I wonder when should I fetch data using API call through Redux (in action), when should I directly make the api call in component.
Is it depending on, whether I need to store the response data in Redux (for sharing among different components)? May I know any best practice for it?
API call through Redux
export const fetchOptions = () => {
return async (dispatch, getState) => {
const request = await client.query({
query: gqlQueries.getQuery,
});
const options = await request;
dispatch({
type: types.FETCH_DATA_END,
options: options
});
}
}
Directly make API call in component:
const axios = require("axios");
useEffect(() => {
axios({
url: 'http://localhost/graphql',
method: 'post',
data: {
query: `
query PostsForAuthor {
author(id: 1) {
firstName
posts {
title
votes
}
}
}
`
}
}).then((result) => {
console.log(result.data)
});
}, []);
If multiple components are using the same data, redux shines there. API calls in components are preferred when you do not want any stale data to show, therefore you call api every time component mounts and your data is always in sync with your back end. There might be some other criteria but these two help me decide , where to keep the state.