How to partially update Apollo client cache after fetching GraphQL query? - reactjs

I have massive query, which looks like this: https://pastebin.com/pMp4iNDc.
I have made also second query, which task would be to fetch only needed fields and update them in apollo cache, the query looks like that:
export const SCHEMA_TELEPORT_GET_FREE_TIME_LEFT = gql(`query GetFreeTimeLeft {
teleport {
enteredZoneData {
timeToFreeTry
positionPrice {
currency
amount
}
}
}
}
`);
So the main idea is that when the second query data is fetched it should update the cache which is coming from the first query.
My plan currently is to pass callback function to timer component, and execute the callback after time become zero.
Example:
const TeleportGrid = () => {
const eventData = useApolloClient().cache.readQuery({query: SCHEMA_TELEPORT_GET_DATA}).teleport;
const [fetchNewTime] = useLazyQuery(SCHEMA_TELEPORT_GET_FREE_TIME_LEFT);
return (
<TimerElement timerId={"counterNextFreeTry"} translation={__('event_teleport_position_time_until_free_try')} time={eventData.enteredZoneData.timeToFreeTry} callback={() => {
return fetchNewTime();
}}
/>
);
}
What I have tried till now ?
Use onComplete option from useLazyQuery and manually do writeQuery to update the cache
Use promise chain after fetchNewTime() and manually do writeQuery to update the cache
I haven't tried the useMutation because in this case we are not really mutating anything we are getting new data and updating cache.
Thanks for any advices in advance !
Have an awesome day <3

I haven't worked with Apollo's cache, but according to the documentation, normalized data should update itself in the cache. In your case, this does not happen.
Try setting fetchPolicy: network-only;
If that doesn't work, try tweaking your cache;
You can also directly use cache.modify in onCompleted() with fetchPolicy: no-cache;
const TeleportGrid = () => {
const {cache} = useApolloClient();
const eventData = cache.readQuery({query: SCHEMA_TELEPORT_GET_DATA}).teleport;
const [fetchNewTime] = useLazyQuery(SCHEMA_TELEPORT_GET_FREE_TIME_LEFT, {
onCompleted: (data) => {
cache.modify(...modify logic)
}
});
return ...;
}
PS. Let me remind you that queries have option pollInterval. Good luck!

Related

How can I access the React useQuery data result?

Currently, when using react query - I can access the response from "data" like...
const { data, error, isLoading } = useQuery(
'MyQuery',
() => {
setNewdata(data)
return getMyQuery()
}
)
then I can .map through the page response like ...
return ({data.data.response.data.map((item) => (...))
Q: Is there way I can shorten the long data.data.response.data list to something shorter like
newdata = data.data.response.data
and then...
{newdata.map((post) => (...)
I do not know where to access -"data" before it is output to the page. Can you help with this?
Thanks for your help in advance
I tried useState to update using setNewdata(data) but it did not show up correctly...
Update: I added async like this below - is it correct?
const { data, error, isLoading } = useQuery(
'MyQuery',
async () => {
setNewdata(data)
return await getMyQuery()
}
)
If I console.log(data) after useQuery it is populated with the response.
First, setNewData() has absolutely no place in this code. data is undefined where you're trying to use it. You also do not need to set the data result into local component state; the useQuery hook takes care of state management already.
You can make your query results more opaque by simply transforming the result before returning it.
You can do that either within getMyQuery() or by using a selector in useQuery()
const { data, error, isLoading } = useQuery(["MyQuery"], getMyQuery, {
select: ({ data }) => data.response.data,
});
return data.map((post) => (
{ /* ... */ }
));
Unnecessary object nesting in API response data is a pet peeve of mine. I highly recommend simplifying the response format if possible.
whats stopping you from doing..
const { data, error, isLoading } = useQuery(
'MyQuery',
async () => {
setNewdata(data) // not sure how you set data here before it exists?
return await getMyQuery() // im assuming this is where the data comes from?
}
)
const newdata = data.data.response.data
return ({newdata.map((item) => (...))

useLazyQuery in Promise.all only storing last response in cache

I'm trying to use the same query with different variables using useLazyQuery. I built a hook for this reason and it fetches everything alright. However, it never really uses cached data. I looked into the cache and it's only storing the response from the last request in the array. This is a simplified version of my hook:
const useProductSearchQuery = (categories) => {
const [getProducts] = useLazyQuery(QUERY);
const [data, setData] = useState();
useEffect(() => {
async function getProducts() {
const responses = await Promise.all(
categories.children.map((cat) =>
getProducts({ variables: { category: cat.id } })
)
);
setData(responses);
}
getProducts();
}, [productCategories, getProducts]);
return { data };
};
I'm not sure if this use case fits useLazyQuery so maybe that's why it doesn't work. I just needed an imperative way of running my queries and this seemed easier than using a consumer to pass the client around.
The other alternative would be to just iterate categories and then have a useQuery hook for each but I'd prefer having my data ready in one go.
Nevermind I didn't notice the hooks docs mention the useApolloClient hook (https://www.apollographql.com/docs/react/api/react/hooks/#useapolloclient)... Works like a charm!

Why is queryClient.setQueryData taking so long in this example with react-query?

I'm updating cache with react-query using useQuery and setQueryData, the problem is setQueryData can take up to 2mins to update the data, possibly due to a loop of some sort. I'm mapping each page on the 'Styles' data, and updating the Styles, Groups and Ranges on the page that matches the pageIndex with data from the response. I have no idea why the very long update, an I using react-query wrong here? The data being updated is not huge at all by the way.
Thanks in advance.
export const useStyle = (styleId, pageIndex) => {
const queryClient = useQueryClient();
const { refetch } = useQuery('Style', () => fetchStyle(styleId), {
staleTime: Infinity,
refetchOnWindowFocus: false,
initialData: {},
onSuccess: (res) => {
queryClient.setQueryData('Styles', (oldData) => ({
pages: map(oldData.pages, (page, index) => ({
...page,
...(index === pageIndex
? {
Styles: {
...page.Styles,
...res.Styles,
},
Groups: {
...page.Groups,
...res.Groups,
},
Ranges: {
...page.Ranges,
...res.Ranges,
},
}
: {}),
})),
pagesParams: oldData.pageParams,
}));
},
});
return { refetchStyle: refetch };
};
I have solved this problem by using useMutation instead of useQuery and setQueryData, this is the better approach if a loop of onSuccess'es is caused by setQueryData.
I think you're creating an infinite loop here:
useQuery('Styles') subscribes to the key Styles, so this component will always update / re-render whenever something in the cache under that key changes
in onSuccess of this query, you update that very same key (Styles)
this update informs all observers, because they should be aware of that change
calling setQueryData also triggers onSuccess, because setQueryData is to be treated the same as if the data came from the backend via the queryFn.
this will trigger your onSuccess again and so on...
The better question would be: What problem are you trying to solve? By the looks of it, you want to perform some data transformation after each fetch. For this, you have multiple options, all of which I have outlined extensively here: https://tkdodo.eu/blog/react-query-data-transformations
But usually, merging data like that should not be necessary. The cache should best be a 1:1 mapping of the response you get from the server.

React-query: how to update the cache?

I have a very basic app that fetches a user and allows to change his name. I fetch the user with React query so I can benefit from the cache feature. It works.
However, when I want to update the user, I use a classic post request with axios. Once the user is updated in the database, I need to update the cache directly in the updateUser() function. I've seen tutorials on the web that use queryCache.setCache, but it doesn't work here. How to fix this? Or maybe there is a better way to handle such queries?
Also, I notice a huge number of rendering... (see the "user render" console.log in the app file).
For the convenience, I've made a small script on a codesandbox with the pokeapi:
https://codesandbox.io/s/api-service-syxl8?file=/src/App.js:295-306
Any help would be greatly appreciated!
So, I'll show you what I do:
const updateUser = async (userUpdates: User) => {
const data = await UserService.updateUser(userUpdates); // return axios data
return data;
}
// if you want optimistic updating:
const { mutate: mutateUser } = useMutation(updateUser, {
onMutate: async (userUpdates) => {
// Cancel any outgoing refetches (so they don't overwrite our optimistic update)
await queryClient.cancelQueries(['user', userUpdates.id]);
// Snapshot the previous value
const previousUser = queryClient.getQueryData(['user', userUpdates.id]);
// Optimistically update to the new value
queryClient.setQueryData(['user', userUpdates.id], userUpdates);
// Return a context with the previous user and updated user
return { previousUser, userUpdates }; // context
},
// If the mutation fails, use the context we returned above
onError: (err, userUpdates, context) => {
queryClient.setQueryData(['user', context.userUpdates.id], context.previousUser);
},
// Always refetch after error or success:
onSettled: (userUpdates) => {
queryClient.invalidateQueries(['user', userUpdates.id]);
}
});
// then to update the user
const handleUpdateUser = (userUpdates: User) => mutateUser(userUpdates);
This all comes from the docs:
Optimistic Updates

UseApolloClient query won't return fetchMore

I am working on project with Apollo on client side. I am using react-apollo-hooks on my client side. And I have a problem with useApolloClient.
When i fire query with my client I got in useApolloClient I don't get back all data I need. FetchMore is missing. If I use regular query (useQuery) I get that. But problem is I need to fire that query on click and i need to use one provided with apollo client.
I have this function for fetching data on click:
const bulkSearch = async data => {
setContent(<Spinner />);
showModal();
try {
const response = await client.query({
query: BULK_SEARCH_PRODUCTS,
variables: { data }
});
if (!response.loading) {
setContent(
<ProductsListDisplay
products={response.data.bulkSearch.products}
fetchMore={response.fetchMore}
count={{ total: 10 }}
/>
);
return 200;
}
} catch (err) {
return 400;
}
};
And response doesn't contain fetchMore.
On the other way classic query returns fetchMore.
const newdata = useQuery(BULK_SEARCH_PRODUCTS, {
variables: { data: { ids: ["536003", "513010"] } }
});
Some help ? Thank you!
According to the apollo-client docs, ApolloClient.query returns a Promise that resolves to an ApolloQueryResult, which is a simpler object that has only data, errors, loading, networkStatus, and stale as properties.
On the other hand, the render prop argument of react-apollo's Query component gets fed a much richer object, with fetchMore being one of its additional properties. If you want to do something similar using the raw ApolloClient object, you would have to use ApolloClient.watchQuery, which returns an ObservableQuery that you can subscribe to consume results. The benefit of this is that you have access to more methods, such as ObservableQuery.fetchMore.
Note this approach is fundamentally different than using ApolloClient.query, since that function requests one query and returns the result as a Promise, while ApolloClient.watchQuery consistently monitors your cache and pushes updated results to your subscribe method when the cache store changes, so it's a more complicated lifecycle. In general, if you're already using react-apollo or one of the #apollo/react-X packages, you probably want to stay away from ApolloClient.watchQuery, since the functionality from those libraries builds directly on top of it and is designed to be easier to consume.
Hope this helps!
You have to create your own FetchMore method for this. This has to be handled by you that's the safer you to go.
In my case I needed
fetchMore
Adding Infinite loading and should event deal with loading state as well.
Problem with default loading state is that it will be always false as return of promise.
When you use await client.query.
In our query we have cursor based pagination.
read this
Create Function that will trigger on scroll ( end of page )
Check on value of after and update it with state management
Loading as well as data also needs to be in state.
Code:
const fetchMoreFilteredData = async (after) => {
try {
setFilteredLoading(true); // set this state in order to show loading indicator
const { data, loading } = await client.query({
query: QUERY,
variables: {
after: after,
...all variables,
},
fetchPolicy: "network-only",
notifyOnNetworkStatusChange: true,
});
const {
query: {
pageInfo: { hasNextPage, endCursor },
},
} = data;
setFilteredData({
// update your data ...filteredData,
});
setHasNextPage(hasNextPage); // check if there is next page
setEndCursor(endCursor); // set end cursor for next page this will guide the query to fetch next page
setFilteredLoading(loading); // set loading state to false
} catch (error) {
error.graphQLErrors.map((error) => {
console.log("error", error.message);
});
setFilteredLoading(false);
} };
const handleLoadMore = () => {
hasNextPage && fetchMoreFilteredData(_endCursor);
};

Resources