React Query selectors - reactjs

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.

Related

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 pass an argument in a React Query?

I need to pass a couple of arguments in a react query one of which needs to decided by the user action
Here is how the query looks so far:
const { refetch: likeDislikeProfile } = useQuery(
['like_dislike_profile'],
() => like_dislike_profile_q(data.userid, <BOOLEAN_ARG>), // 👈
{ enabled: false }
)
Whenever the clicks on a like/dislike button, the argument will be true/false respectively.
This is further used as a query param in the request : action?like=false
How do I achieve this?
My approach
create a local state that changes on button click
create a side effect (useEffect) method which is triggered when this state changes
which will further trigger this react query
This approach seems bad, can't think of anything else atm
Looks like your HTTP request is changing data in the backend, that's the use case for mutations.
From the official docs
A query is a declarative dependency on an asynchronous source of data that is tied to a unique key. A query can be used with any Promise based method (including GET and POST methods) to fetch data from a server. If your method modifies data on the server, we recommend using Mutations instead.
For your use case it should be something like this
const updateLike = useMutation((id, bool) => like_dislike_profile_q(id, bool))
// invoke the mutation at any point like this
updateLike.mutate('my-id', true)
Read more about mutations on Tkdodo's blog post on mutations

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.

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

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.

How to do Batch Mutations with Apollo Client

I try to use ApolloClient 2.1 with the new Mutation Component.
Simple use cases are working but now I have something more complex.
What I want to achieve is to query data and put them in a list, then sort this list (here via react-sortable-hoc) and once sorted, I want to update the new position for all elements in the list.
So the basis is something like this, which is working for simple Querying:
const query = gql`
{
items( order:{by:"position", direction:"desc"}) {
id
name
position
}
}`
const ItemView extends Component {
onSortEnd = ({ oldIndex, newIndex }) => {
console.log("Sort ended: ", oldIndex, newIndex);
}
render() {
<Query query={query}>
{({ loading, data, error }) => {
if (loading) return <p>Loading...</p>;
if (error) return <p>Error</p>;
return (
<ItemList items={data.items} onSortEnd={this.onSortEnd} />
)
}}
</Query>
}
}
Now I really struggle at a lot of parts in order to do the mutation.
I think I need to wrap the Mutation Component. But how can I provide a GraphQL Query in there, because I want to do batch mutation with a similar query being fired multiple times, such as
mutation {
updateItem1: updateItem(id: 457092155, input: {position: 1}) {
item {
id
}
ok
errors
},
updateItem2: updateItem(id: 54489270, input: {position: 2}) {
item {
id
}
ok
errors
},
... // much more mutations, one for each item in the list
}
So my main question is, how do I pass a GraphQL mutation with dynamic amount of mutations to the Mutation component? Or should I do this completely differently?
Thanks a lot for any hints
You will have to compose multiple Mutations together in order to achieve this. You can use react-adopt for this. They have even addressed this here https://github.com/pedronauck/react-adopt#leading-with-multiple-params.
You can also take a look at the discussion going on here https://github.com/apollographql/react-apollo/issues/1867 and jasonpaulos has an example demonstrating this with hooks
Hi everyone! I believe that the new Apollo hooks, useQuery,
useMutation, and useSubscription, adequately address this use case. To
demonstrate this, I have converted #Cridda's example that uses
react-adopt and modified it to use #apollo/react-hooks here:
https://codesandbox.io/s/apollo-and-react-hooks-4vril
This example is by no means perfect, but it serves as a demonstration
of how hooks can massively simplify some use cases.
Hope this helps!
As Hemant mentioned already, the #compose annotation in Apollo 2.1 is the "correct" / conventional way to solve this problem. If that doesn't work for you for whatever reason, there is possibly another cruder/hacky way to accomplish this:
If your Item model has a parent model, you can mutate multiple nodes with one mutation by passing the children in as the array values to the connect / create / update actions.
The unfortunate limitation here is that there is no way to individually identify child nodes to be updated. What I mean is that you can filter child Items to be mutated based on a criteria (like postition = 2) but that will only allow you to mutate the filtered items to the same state; you won't be able to update them differently from one another this way.
If we allow ourselves one more crude step, you can delete the Item nodes that you wish to update before calling the update mutation - this will allow you to call the mutation with all of the updated items under the create: key in the mutation, which will allow you to specify each item to be created. In this way, the number of items you can create is only limited by the size of your request payload.
There are many cases where deleting and creating nodes is unacceptable (as opposed to updating them)...if you use this method then be sure there are no negative side effects to your use case(s) from deleting item data in this way.

Resources