Using a value from a previous query in react query - javascript

I have a react query to get user data like this
const { data: queryInfo, status: queryInfoLoading } = useQuery('users', () =>
getUsers()),
);
I then have a sibling component that needs the same data from the get users query. Is there a way to get the results of the get users query without re-running the query?
Essentially, I would like to do something like this
const userResults = dataFromUserQuery
const { data: newInfo, status: newInfoLoading } = useQuery('newUserData', () =>
getNewUsers(userResults.name)),
)

As suggested in this related question (how can i access my queries from react-query?), writing a custom hook and reusing it wherever you need the data is the recommended approach.
Per default, react-query will trigger a background refetch when a new subscriber mounts to keep the data in the cache up-to-date. You can set a staleTime on the query to tell the library how long some data is considered fresh. In that time, the data will always come from the cache if it exists and no refreshes will be triggered.

Related

How to disable re-calling API after onClick and execute refetch the react-query hook?

for example, i've a component to fetch and call an API, but that query by default is enabled: false and i should fire that by onClick:
const query = useQuery('key', fetch, { enabled: false })
const exec = () => query.refetch()
return <button onClick={exec}>Load</button>
But i've a new API call after every clicks on the button, actually i want to cancel re-calling the API still the cached data is available and is not stale...
I there any way to implement something like refetch to retrieve cached data but without re-calling the API? our basically react-query has a re-call for any data reteive?
in fact, our data doesn't change frequently and is fix for 2-3 days...
other words, our clients frequently work with nested drop-downs with same API calls and i want to reduce same key queries... imagine that, something like category to select a brand for products
Thanks
You can set staleTime and cacheTime options of your query to Infinity to keep your cache always available
https://react-query.tanstack.com/reference/useQuery
imagine that, something like category to select a brand for products
This is a classic example for react-query where you want to put all dependencies of your query into your cache key (see the official docs). That way, the caches won't override each other, and you'll instantly get the values back from the cache if they are available. Not that you will also get a background refetch, and that's where staleTime comes in. If you don't want that, set a higher staleTime to only retrieve the value from the cache if it exists.
To illustrate, let's take your example of a select of categories for products:
function MyComponent() {
const [category, setCategory] = useState(null)
const { data } = useQuery(['key', category], () => fetchProducts(category), { enabled: !!brand, staleTime: 1000 * 60 * 3 })
<CategorySelect onSelect={setCategory} />
}
Multiple things going on here:
I have some local state, where I store the selected category
This selection drives the query. The query is disabled as long as I have no selection, but enables once the user makes a selection
every time the category changes, the query key changes, which will make react-query trigger a refetch automatically, for the new data
I've set the staleTime to 3 minutes. If I chose a category that I have already chosen, I will get data from the cache. Within 3 minutes, I will only get it from the cache. If my data is older, I will get it from the cache and the data will also be updated in the background.

react-query getting old data

I have a master page that is a list of items, and a details page where I fetch and can update an Item. I have the following hooks based upon the react-query library:
const useItems = (options) => useQuery(["item"], api.fetchItems(options)); // used by master page
const useItem = id => useQuery(["item", id], () => api.fetchItem(id)); // used by details page
const useUpdateItem = () => {
const queryClient = useQueryClient();
return useMutation(item => api.updateItem(item), {
onSuccess: ({id}) => {
queryClient.invalidateQueries(["item"]);
queryClient.invalidateQueries(["item", id]);
}
});
};
The UpdatePage component has a form component that takes a defaultValue and loads that into it's local "draft" state - so it's sort of "uncontrolled" in that respect, I don't hoist the draft state.
// UpdatePage
const query = useItem(id);
const mutation = useUpdateItem();
return (
{query.isSuccess &&
!query.isLoading &&
<ItemForm defaultValue={query.data} onSubmit={mutation.mutate} />
}
);
The problem is after I update, go to Master page, then back to Details page, the "defaultValue" gets the old item before the query completes. I do see it hitting the API in the network and the new value coming back but it's too late. How do I only show the ItemForm after the data is re-queried? Or is there a better pattern?
My updateItem API function returns the single updated item from the server.
I used setQueryData to solve this.
const useUpdateItem = () => {
const queryClient = useQueryClient();
// Note - api.updateItem is return the single updated item from the server
return useMutation(item => api.updateItem(item), {
onSuccess: data => {
const { id } = data;
// set the single item query
queryClient.setQueryData('item', id], data);
// set the item, in the all items query
queryClient.setQueryData(
['item'],
// loop through old. if this item replace, otherwise, don't
old => {
return old && old.map(d => (d.id === id ? data : d));
}
);
}
});
};
I will say, react-query is picky about the key even if it is fuzzy. Originally my id was from the url search params and a string, but the item coming back from the db an int, so it didn't match. So a little gotcha there.
Also, when I go back to the Master list page, I see the item change, which is kind of weird to me coming from redux. I would have thought it was changed as soon as I fired the synchronous setQueryData. Because I'm using react-router the "pages" are complete remounted so not sure why it would load the old query data then change it.
isLoading will only be true when the query is in a hard loading state where it has no data. Otherwise, it will give you the stale data while making a background refetch. This is on purpose for most cases (stale-while-revalidate). Your data stays in the cache for 5 minutes after your detail view unmounts because that’s the default cacheTime.
Easiest fix would just set that to 0 so that you don’t keep that data around.
You could also react to the isFetching flag, but this one will always be true when a request goes out, so also for window focus refetching for example.
Side note: invalidateQueries is fuzzy per default, so this would invalidate the list and detail view alike:
queryClient.invalidateQueries(["item"])
I had the same issue today. After scanning your code it could be the same issue.
const useItem = id => useQuery(["item", id], () => api.fetchItem(id)); // used by details page
The name of the query should be unique. But based on you details the ID changes depends on the item. By that you call the query "item" with different IDs. There for you will get the cached data back if you have done the first request.
The solution in my case was to write the query name like this:
[`item-${id}`...]

Is it possible to check if onSnapshot() has found changes before actually fetching?

When I am navigating to a page, I am passing some parameters such as "Discounts" and I am displaying that number in the render() but I am calling onSnapshot() right in the begin of navigating to that class. Now, I would like to display in the begin ONLY the value of the parameter and in case there are changes fetch it.
P.S. the reason behind this is that I am trying to reduce the number of fetches as much as possible.
check_amount_left() {
const getSelected = this.props.navigation.state.params;
var ref = db().collection('discounts').where("rest_id", "==", getSelected.rest_id)
ref.onSnapshot((querySnapshot => {
var amount = querySnapshot.docs.map(doc => doc.data().amount);
this.setState({
check_for_amount: amount.toString()
});
}));
}
Render method:
render(){
const getSelected = this.props.navigation.state.params;
return(
<View>
{getSelected.amount}
</View>
)
}
To know that something changed about data on the server, the server will need to tell the client about that change. In the case of Firestore the server always tells the client the entire new state of the document.
So the only way to reduce the amount of data that the server sends to the client, is to reduce the size of the document you're listening to.
But event then, it would not reduce the number of fetches, as the server will need to read the document to know that it has changed.

Fetch graphql query result on render and re-renders BUT LAZILY

I have a React Apollo app and what I am trying to do is that I have a component that renders some data using charts. For this data, I have some filters that I save in the local state of the component (Using hooks)
const [filters, setFilters] = useState(defaultFilters);
Now what I want is that whenever the component mounts, fetch the data using the default filters. But I also want to re-fetch data when the user updates the filters AND CLICKS ON SUBMIT and I'd fetch the results using new filters.
Since I also want to fetch the results on filter update, I am using useLazyQuery hook provided by apollo
const [getData, {data}] = useLazyQuery(GET_DATA_QUERY, { variables: {filters} });
useEffect(getData, []); // this useEffect runs only when the component mounts and never again
But, what happens is whenever my state, filters, updates the getData function is automatically run! ALWAYS! (BEHIND THE SCENE)
How do I handle such cases, where I want to fetch results on mounting and re-rendering.
I have tried using useQuery and refetch provided by it but I get the same problem there, whenever I update the state, the component rerenders and the useQuery hooks is run and makes the call. (That's how I believe it runs)
How do I fix my current code. Calling the getData function inside the useEffect function makes it run on every re-render.
I think I the problem defined in this stackoverflow-question is somewhat similar to mine.
Part of the problem is that you really have two different states that you're trying to utilize a single hook for. You have state that represents your inputs' values in the UI, and then you have state that represents the filters you want to actually apply to your charts. These are two separate bits of state.
The simplest solution is to just do something like this:
const [inputFilters, setInputFilters] = useState(defaultFilters)
const [appliedFilters, setAppliedFilters] = useState(inputFilters)
const { data } = useQuery(GET_DATA_QUERY, { variables: { filters: appliedFilters } })
const handleSubmit = () => setAppliedFilters(inputFilters)
const handleSomeInputChange = event => setInputFilters(...)
This way, you use inputFilters/setInputFilters only to manage your inputs' state. When the user clicks your submit button, the appliedFilters are set to whatever the inputFilters are at the time, and your query will update to reflect the new variables.

how to access redux store from custom javascript

I'm trying to implement an external API library in a redux application.
I'm fresh new in redux so I don't know exactly how it works.
In my javascript using the API library, I wan't to access info from a container (the user firstanme if he's logged).
After reading some doc, I tried to import the store in my js file, to get the state of the user, but I can't reach the info I need.
Here's the code I tried :
import configureStore from '../store/configureStore';
const store = configureStore();
const state = store.getState();
I get many info in state, but not the one I need. Any help ?
First of all it looks like configureStore creates new store every time you call it. But you need the very that store that your components will use and populate. So you need to somehow access the store you are passing your Provider.
Then since store state is "changing" you can't simply read it once. So your user data might be initially empty but available some time later.
In this case you could make it a Promise
const once = selector => available => new Promise(resolve => {
store.subscribe(() => {
const value = selector(value)
if(available(value)) resolve(value)
})
})
And usage
const user = once(state => state.user)(user => user && user.fullName)
user.then(user => console.log(`User name is ${user.fullName}`)
Or if your data might be changing more than once during application lifecycle you might want to wrap it with something that represent changing data (observable). RX examle

Categories