react-query: is there a callback that get trigger regardless of whether getQuery is cached or not? - reactjs

I want to do some side effects like setState and update context after the data is fetched. However, the onSuccess will not be executed when the data is in cache. Also useEffect doesn't work because if the data is cached, it doesn't change from undefined to the real data. Therefore it doesn't get trigger either. What's the best way of doing this? Thanks

My usecase is to extract some values from the data returned from useQuery and set a new state on those.
usually, they’re shouldn’t be a need to be a need to copy state from react-query into local state. This will just lead to duplication of the source of truth. It is best to keep them separated, so that you can also profit from background updates.
If you want to transform the data or subscribe to parts of the data, use the select option of useQuery:
const { data } = useQuery(key, fn, { select: data => data.map(...) })
Alternatively, you can compute some new data depending on the returned data with useMemo, e.g.:
const { data } = useQuery(...)
const articles = useMemo(() => data?.map(...), [data])
// work with articles from here on
You can also put that nicely in a custom hook.

Related

Mutate useSWR data (local state) without revalidating?

I have some data from a public API that I fetched using useSWR, now I want to operate on that. Previously, I would setState with the response from an API and then when operating on it I would just overwrite it with setState.
In my case, I want to sort the data with a button press. it looks like this
function Component() {
const { data } = useSWR(...)
function sortData() {
data.sort(...)
}
}
How can I modify data like this without having useSWR revalidate and fetch new data? This is strictly a client-side need, the API does not need to worry about this sorting.
Thanks!
It's hard to say because you're not saying what causes SWR to revalidate but here are the main solutions I know of :
move the hook to the parent component if you want to avoid the re-fetch caused by the sort but you still want to fetch normally otherwise ;
pass null / a function as cache key : https://swr.vercel.app/docs/conditional-fetching if you want to only fetch in certain controlled conditions ;
use useSWRImmutable : https://swr.vercel.app/docs/revalidation if you don't want to re-fetch at all.

Zustand fetch with API call useEffect best practice

When fetching state from an API with Zustand in a useEffect function what is the best practice for doing that? Right now I am using it very simply:
export interface ModeState{
modes: Mode[];
fetchModes: () => void;
}
export const useModeStore = create<ModeState>((set) => ({
modes: [],
fetchModes: async () => {
const modes: AcquisitionMode[] = await API.get(`/acquisition-modes`);
await set({ modes });
},
}));
In component render function:
const modeStore = useModeStore()
const modes = modeStore.modes
useEffect(() => {
modeStore.fetchModes()
}, [])
However the documentation seems to imply there are multiple ways this could be written to be more efficient in terms of performance, especially if my store grows more complex with more values and fetch functions. Is it best practice to make one store per API call? Use slices to get just the part of the store you need in each component? Should I be using the store differently in useEffect? I can't find a clear example online of how you should use the store in useEffect. The subscribe documentation does not seem to apply to the use case where you are using the store to fetch values with an async function.
I have used zustand in a similar fashion in the past. I would often have a sync method on the store which I call in a useEffect and pass to it any state that is available to the component and not the store. Another possibility could be to let a library optimized for fetching get the data and make it available to the store once fetched.
What the documentation refers to with regard to performance is that you can indeed select parts of your store with a provided selector. In these cases a rerender will only happen when
the previous and current selected value are different or
a custom provided equality function states that previous and current values are different.
If you want to get into more detail with regard to performance I can recommend this this article here (disclaimer, I wrote it)
Even so, those performance considerations do not influence so much how you would trigger a fetch from, say, a useEffect hook.

How to share data across multiple components with react-query useMutation

I'm looking for a solution to share data across a React-Query mutation without having to create my own internal state or context.
I created a custom hook that takes care of the API call.
myData.ts
const useDataMutation = () => useMutation('MY_DATA_MUTATION', postData);
Then, I use my custom hook in different components.
Component1 is in charge of mutating. The response data will be available in data once the mutate is successful.
Component1.tsx
const { mutate, data } = useDataMutation();
useEffect(() => mutate('some_data'), []);
In another nested component, I want to access the data which came back from the response. But I don't want to pass down the data to 3-4 layers of components. And I wanted to avoid using a Context to access this data.
What I want is something like this:
Component2.tsx
const { data } = useDataMutation();
console.log({ data }); // log data once available.
But in this example, the data from Component2.ts is always undefined.
Is there a simple way to achieve something like this?
Thank you.
at the moment, mutations don't share data across instances like queries do. There is an open issue about it, and contributions are welcome.
If you're using #apollo/client then you can read the previously fetched data directly from the cache.
import Query from './query.ts'
const { todo } = client.readQuery({
query: Query,
variables: {}
})
That will not fetch the data from your server again and instead fetch it from the apollo cache. So you can fetch it using the hook or in the parent component and then 5 levels down you can just pull it again from the cache using the same hook.
When you're using a mutation it will update data in the cache if the properties in the query is the same as before, so if you have a query for getUser and a mutation for updateUser I think the mutation should automatically update the getuser cache data if the data aligns with the mutation data. I'm not sure about this.

When using React Hooks, how could we set a state to some complicated data structure initially?

We may have
const [data, setData] = useState([]);
but what if we need to set the initial state to some complicated data structure, such as an array of 360 objects, each other representing the initial amount, the monthly contribution, the interest earned, and the final monthly balance, just like how we calculate the compound interest or mortgage. How could we set this complicated data structure up?
How is using an IIFE:
const [data, setData] = useState( (function() {
const obj;
// set up the object
return obj;
}()) );
?
I think one precaution is we don't want to run the loop every time the function component gets called.
The most effective way is to use a useEffect hook. I'll rapidly explain how it works:
useEffect(()=>{
//here you can do your computation
let myFirstField=...;
let mySecondField=...;
setData({
one: myFirstField;
two: mySecondField;
});
},[]) //putting these "empty array" after use effect ensure that use Effect is called only after "onMount" (at the beginning and not at every refresh)
I want you to notice a thing. Use state hooks aren't like normal state in components. You can't set only a field when you want to change it, but you need to reset the whole state.
Said this, use effect will initialize your state in the way you want. Inside this method you can also run axios request or whatever you want, just remember to handle what the UI is showing accordingly (for example if you perform an axios request, show a spinner for the user to understand that the page is loading)
You can use Lazy initial state to initialize your data.

React Query selectors

I am using react-query in my project and it works great
I wonder if react-query has a selector feature. I use react-query to fetch a list of posts, for example, and would like to filter only the completed post. I want to cache the filter result so the next time I filter the list again, it can return the cache. Basically, the feature I want is the same as selectors in recoil
react-query added first-class support for selectors with its release of version 3.
Here's example usage from the library:
it('should be able to select a part of the data with select', async () => {
const key = queryKey()
const states: UseQueryResult<string>[] = []
function Page() {
const state = useQuery(key, () => ({ name: 'test' }), {
select: data => data.name,
})
states.push(state)
return null
}
renderWithClient(queryClient, <Page />)
await sleep(10)
expect(states.length).toBe(2)
expect(states[0]).toMatchObject({ data: undefined })
expect(states[1]).toMatchObject({ data: 'test' })
})
It's important to have in mind that react-query does not provide a local state. All fetched data is a temporary representation (cache) of the server state. It implements the SWR (stale-while-revalidate) pattern.
Although you can achieve what you want by selecting the data from the cache provided by react-query(you need to do it yourself, there are no selectors) it might be tricky if you use pagination or if you data becomes stale immediately (default lib behaviour).
So assuming your data is a temporary representation of server state it would be better to include the filters in the query key so you would end up making different queries and having different independent cache entries to different filter combinations. This way:
You don't need selectors
You only fetch what you need
When you change the filters you have access to the cache data (if any) while react-query refetches in the background. This depends on how you configure react-query
I am not aware of a builtin native selector solution, but You can achieve this with a custom hook.
function useMostPopularPosts(){
const postsResult = useQuery(postsQueryParams)
const mostPopularResult = useQuery(popularPostsQueryParams)
if (!(postsResult.success && mostPopularResult.success)){
return [];
}
return postsResult.data.filter(
post=> mostPopularResult.data.includes(post.id)
)
}
function MostPopularPosts(){
const popularPosts = usePopularPosts()
return <>
{
popularPosts.map(post=>(
<PostHighlight post={post} />
))
}
</>
}
Yes, react-query has a selector feature.
(1) You can achieve this by passing your filters in the query key array (the filters can stand alone or be grouped in an object). Each query key will lead to different results, and they are cached default by 5 minutes. Next time when you filter the list again, it can return data from the cache without fetching API again.
(2) react-query also provided an option named select, but it just customs the returned data (filter or just return a part of the object, etc.) but not affects what gets stored in the query cache (full data).
You can still use this latter way, but the most suitable is the former mentioned.

Resources