Apollo Client React and local state best practices - reactjs

I am working on my first GraphQL React Client app and I am not sure about some best practices.
I have a query, that gets me places from an API to be shown on a map.
In the map.js component I fetch the data with this query:
const GET_PLACES_ON_MAP = gql`
query Region($input: MapInput!) {
map(input: $input) {
places {
id
name
distance
long
lat
}
infos {
totalPlacesInArea
responseTime
}
}
}
`;
const { loading, error, data } = useQuery(GET_PLACES_ON_MAP, someQueryParams);
The response includes infos about the total number of places and the response time of API response.
This infos object should be used in other components too (e.g. in some kind of dashboard) and it changes everytime a new query is made in the map component.
My question no 1:
What's the best practice to use infos in another component that do not query the API itselves?
My first try was to save infos in a local state graphql object like with the following mutation and resolver code (this sets the infos in local state but I get endless rendering of the map component):
const SET_API_INFO = gql`
mutation {
setApiInfo(params: $params) #client
}
`;
const GET_API_INFO = gql`
{
apiInfo #client {
totalPlacesInArea
responseTime
}
}
`;
const resolvers = {
Mutation: {
setApiInfo: (_root, variables, { cache, getCacheKey }) => {
const { apiInfo } = cache.readQuery({ query: GET_API_INFO });
const { params } = variables;
_.map(params, (value, key) => {
apiInfo[key] = value;
});
cache.writeData({ data: { apiInfo } });
},
},
};
My question no 2:
I was looking for some tutorial or an open source project that uses Apollo Client & React to learn more about the architecture of a really complex app. Is there anywhere a good example that goes further then query, mutation, subscription?

You don't need to store your data in the client cache because Apollo supports fetchPolicy. When you use the same query with the same variables, query fields in any component, Apollo will look up its cache query first before making a new network request. If the query has existed, Apollo will return the last query result.
By default, Apollo Client's fetch policy is cache-first, which means it checks the cache to see if the result is there before making a network request. Since we want this list to always reflect the newest data from our graph API, we set the fetchPolicy for this query to network-only.
I don't know any Apollo complex app, but you can read more about Apollo's best practices in Apollo blog.

Related

What is the Syntax for Refetching a Query after a Local State Change in React

I am relatively new to Apollo and GraphQL, and I need to make a requery after several mutations, since the recoil states don't want to update in time and a million errors get thrown off after some of the mutations. I simply put do not know how to do this and have been unable to find any relevant documentation to my scenario.
The following code is inside of theApp.js file.
// Mutations
const { loading: loadingO, error: errorO, data: dataO, refetch: refetchO } = useQuery(GET_OWNER)
const { loading: loadingM, error: errorM, data: dataM, refetch: refetchM } = useQuery(GET_MANAGER)
const handleRefresh = () => {
setRefresh(!refresh)
if (role && id){
if (role == "MANAGER"){
// reftechM
}
if (role == "OWNER"){
// refetchO
}
}
}
useEffect( () => {
console.log("???")
}, [refetchM, refetchO])
...where handleRefresh is essentially called after every mutation, or of course, every refresh. However, just calling refetch does not work, and I have been very unable to find the proper syntax for my issue. Does anyone know the solution?
By default, the useQuery hook checks the Apollo Client cache to see if all the data you requested is already available locally. If all data is available locally, useQuery returns that data and doesn't query your GraphQL server. This cache-first policy is Apollo Client's default fetch policy. If you say that you will call handleRefresh() after mutation the below code will work fine.
here read fetch policy
const { loading: loadingO, error: errorO, data: dataO, refetch: refetchO } = useQuery(GET_OWNER, {
fetchPolicy: "network-only",
})
const { loading: loadingM, error: errorM, data: dataM, refetch: refetchM } = useQuery(GET_MANAGER, {
fetchPolicy: "network-only",
})
const handleRefresh = () => {
setRefresh(!refresh)
if (role && id){
if (role == "MANAGER"){
refetchM()
}
if (role == "OWNER"){
refetchO()
}
}
}
Try this directly from apollo docs:
Refetching queries after a mutation
In certain cases, writing an update function to update the cache after
a mutation can be complex, or even impossible if the mutation doesn't
return modified fields.
In these cases, you can provide a refetchQueries option to the
useMutation hook to automatically rerun certain queries after the
mutation completes.
For details, see Refetching queries.
Note that although refetchQueries can be faster to implement than an
update function, it also requires additional network requests that are
usually undesirable. For more information, see this blog post."
Source: https://www.apollographql.com/docs/react/caching/advanced-topics/

Why react useQuery() doesnt fetch the newest data after a mutation?

I have a code like this
const [inputComment, setInputComment] = useState('');
const [
commentPost,
{ data: data4, loading: loading4, errorCreate4 },
] = useMutation(COMMENT_POST);
const { error: error2, loading: loading2, data: data2 } = useQuery(
GET_POST_BY_ID,
{
variables: {
postid: item.id,
},
},
);
const doComment = () => {
commentPost({
variables: {
postId: item.id,
userEmail: email,
comment: inputComment,
},
})
.then(({ data }) => {
setInputComment('');
console.log('success');
})
.catch((e) => {
console.log('not success');
});
};
This is supposed to get the data, and when I do comment then it runs the mutation and re-render everything.
My problem is, it re-render alright BUT the data that the useQuery fetch is not the newest data a.k.a the data before I add a new comment.
Does anyone know how to fix this problem??
Please help :(
Your mutation modifies data on the server side.
Once your mutation is done, you should refetch your data in order to get the modified version in your local cache on the client side.
By guessing how your mutation and query actually work, here is how it would look like:
const [
commentPost,
{ data: data4, loading: loading4, errorCreate4 },
] = useMutation(COMMENT_POST, {
refetchQueries: [
{ query: GET_POST_BY_ID, variables: { postid: item.id } }
]
});
Otherwise, intead of refetching from the server, you could update the local cache directly.
More info can be found here in the official documentation.
I assume commentPost is an insert operation, not an update of a single record. In this case, Apollo useMutation will not update the cache for you. You need to modify the cache yourself. The official Apollo documentation has covered this use case with an example. You may want to revise the usage of writeFragment as well.
Below are directly from apollo docs on cache update for list fields.
In most cases, a mutation response should include any object(s) the
mutation modified. This enables Apollo Client to normalize those
objects and cache them according to their __typename and id fields (by
default).
...
When a mutation's response is insufficient to update all modified
fields in your cache (such as certain list fields), you can define an
update function to apply manual changes to your cached data after a
mutation.
const [addTodo] = useMutation(ADD_TODO, {
update(cache, { data: { addTodo } }) {
cache.modify({
fields: {
todos(existingTodos = []) {
const newTodoRef = cache.writeFragment({
data: addTodo,
fragment: gql`
fragment NewTodo on Todo {
id
type
}
`
});
return [...existingTodos, newTodoRef];
}
}
});
}
});
EDIT
I noticed another answer suggests using refetch, which is not a bad option for starters. However, updating the cache is the recommended approach over refetch. You can refer to the Apollo blog article When To Use Refetch Queries in Apollo Client.
Below are some quotes you should note from this article.
If you’re just getting started with GraphQL, I think the mental model of passing in the queries that you’d like to re-run after a mutation is an easy one to wrap your head around.
...
The advantage here is that this approach is straightforward. The disadvantage is that we’re fetching the entire list of data again when we might not need to.
...
For a more efficient use of bandwidth and network round-trips, we can rely on cache normalization and update functions.

How to update query in apollo client?

Let's say I have a simple data model like this
User
- email
- password
- Profile
- profile_image
- address
- phone_number
When I visit the user's profile page, I use useQuery and query user from server
const ME = gql`
query {
me {
email
profile {
profileImage
address
phoneNumber
}
}
`;
const {loading, data, refetch} = useQuery(ME);
And when I want to update a profile. I will do this
const UPDATE_PROFILE = gql`
mutation($profileImage: String!, $address: String!, $phoneNumber: String!) {
updateProfile(profileImage: $profileImage, address: $address, phoneNumber: $phoneNumber) {
profileImage
address
phoneNumber
}
}
`;
const [updateProfile, {loading}] = useMutation(UPDATE_PROFILE, {
onCompleted(data) {
// Refetch to refresh whole user data
refetch();
}
}
I just want to display new updated user info in the page, So What I do is calling refetch() from useQuery(ME).
But I found that I can use refetchQueries() from this doc.
Which will be a better choice? What is the difference between them?
The difference between refetchQueries and refetch:
refetchQueries: you can refetch any queries after a mutation including your ME query or other queries like getMessageList, getListUser,...
refetchQueries is the simplest way of updating the cache. With refetchQueries you can specify one or more queries that you want to run after a mutation is completed in order to refetch the parts of the store that may have been affected by the mutation (refetchQueries doc).
refetch: you just can refetch query ME when your use refetch which is one of the results of useQuery(ME).
A function that allows you to refetch the query and optionally pass in new variables (refetch doc).
In your case, if you want to refetch your ME data, you can use refetch. On the other hand, if your want to update other queries you should use refetchQueries.
In my experience, I prefer using refetchQueries after a mutation to using refetch.

Apollo Refetch Query Component with new Variables

So I have this Apollo Query Component like this:
<Query
fetchPolicy='network-only' // also tried without and with 'no-cache'
query={GET_MENUS}
variables={{
foo // This has the default value of the state
}}
>
{({ loading, error, data, refetch }) => {
// Display Data here
// We have an Imput here that can change the State of Bar in the parent Component
<Button
onPress={() => {
/*refetch({
foo: { bar}
}); */
setBar(blubb); // I am using react hooks (useState)
}}
text='Refresh!'
/>
}
)}
</Query>
I tried to refetch by using the refetch method and also by just updating the state. Actually I checked the Apollo Server and in both methods the new variables get passed, but the new Data is not updated. The funny thing is, that if I just use another default value in the state, it works fine. I also tried different fetch-policies without any luck.
I thought it should be quite basic, but I didn't find any solution so far...
So how do I get data with my new variables?
EDIT:
GET_MENUS is a bit complicated, but this is the whole thing. I am passing the variables into different resolvers, because they are nested. The Foo Bar thingy is the "daily" variable
const GET_MENUS = gql`
query getMenus($lat: Float!, $lng: Float!, $daily: Daily) {
getMenus(lat: $lat, lng: $lng) {
distance
location {
_id
street
streetNumber
plz
city
coordinates
shopIDs {
name
togo
shopType
menus(daily: $daily) {
_id
name
price
hot
sweet
togo
allergies
components
}
}
}
}
}
`;
My solution to refetch using variables in Apollo 3.0:
import { gql, useApolloClient } from "#apollo/client";
const client = useApolloClient();
const SEARCH_PROJECTS = gql``
await client.query({
query: SEARCH_PROJECTS,
variables: { page: 1, limit: 1 },
notifyOnNetworkStatusChange: true,
fetchPolicy: "network-only"
})
See more about the fetch policy here and here.
My context was the following: I fetch a list of projects, then the user can remove or update the projects. The list of projects, the project, the update and delete are different components. The default refresh provided by Apollo doesn't allow me to send the variables for the project' pagination, so when I remove or update a project I refresh it manually, without the need to create a structure where I can use the refresh or fetch more option from the component "list of projects"

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