react apollo cache.write issue - reactjs

I use "#apollo/client": "^3.5.9"
and have an issue when try to update cache
Here is the problem:
I use useMutation hook and inside this hook try to update my cache
const [createUser, { data, loading, error }] = useMutation(addUser, {
update(cache, { data }) {
const newUser = data?.createUser?.user;
const existUsers: any = cache.readQuery({
query: allUsersQuery
});
cache.writeQuery({
query: allUsersQuery,
data: {
allUsers: [...existUsers?.allUsers.edges, newUser]
}
});
}
});
I receive an errors below
Error 1:
react_devtools_backend.js:4061 Missing field 'edges' while writing result {
"__typename": "UserSchema",
"id": "acb4e46f-b80a-42ee-b2d5-c838b63b2c63",
"name": "456"
}
Error 2:
react_devtools_backend.js:4061 Cache data may be lost when replacing the allUsers field of a Query object.
To address this problem (which is not a bug in Apollo Client), define a custom merge function for the Query.allUsers field, so InMemoryCache can safely merge these objects:
existing: {"__typename":"UserConnection","edges":[{"__typename":"UserEdge","node":{"__ref":"CustomUserNode:6a845e81-ae09-46c5-9b5c-5e250efbad71"}},{"__typename":"UserEdge","node":{"__ref":"CustomUserNode:2cff31b1-ed2b-4245-b7d9-268df82b8c4f"}},{"__typename":"UserEdge","node":{"__ref":"CustomUserNode:95869b70-4c7b-42f1-baad-d7c358caa4ff"}},{"__typename":"UserEdge","node":
I try to googled, try to wrap in try and catch blow, but it doesn't work.
I supposed i missing something but i do not what exactly.
Any comments highly appreciated.

Related

Is it possible to mock schema fields in GaphQL request in react-query like apolloclient does it?

I am working on migrating my app to react-query from apolloclient. With apolloclient, I am mocking the schema like it's shown here. Here's what that looks like:
const users = [
{
name: "user1name",
image: "https://user1image.jpg",
},
{
name: "user2name",
image: "https://user2image.jpg",
},
{
name: "user3name",
image: "https://user3image.jpg",
},
{
name: "user4name",
image: "https://user4image.jpg",
},
];
const cache = new InMemoryCache({
typePolicies: {
Post: {
fields: {
user: {
read() {
return user[Math.floor(Math.random() * people.length)];
},
},
},
},
},
});
Then, in the query for posts, I can just add #client after the user field and it will run the read function specified above and return the value for that field while the other fields will have the data from the backend.
I haven't been able to find any information on how to do something similar with react-query. Is it even possible?
react-query is not a graphql specific library, so any schema related things cannot be handled by it. react-query basically wants a resolved or rejected Promise returned from the queryFn - that's it.
So maybe this question is more related to the library that you actually use the make the graphql request, like graphql-request or maybe urql-core?
What you can always do is amend the data inside the queryFn after it was fetched, but before it was returned from useQuery. If you take a look at the graphql basic example from the docs, you could do:
useQuery("posts", async () => {
const {
posts: { data },
} = await request(
endpoint,
gql`
query {
posts {
data {
id
title
}
}
}
`
);
return {
...data,
someOtherField: myMockedOtherFieldData };
}
});

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.

Refetching a query using Apollo-Client throws an error

I am using Apollo-client to post a mutation to my graphql server. When the mutation is completed, I want to refetch that data. I tried using the refetchQueries argument in the useMutation hook, however I receive this error when I execute the code:
query option is required. You must specify your GraphQL document in
the query option.
This is the line of code that sends the mutation:
const [addUser, { data, loading, error }] =
useMutation(ADD_USER_QUERY, {
refetchQueries:[GET_USERS_QUERY]
});
This is my query (the hardcoded arguments were to see if the issues was due to passing variables):
export const ADD_USER_QUERY = gql`
mutation {
createUser(name: "Albert Einstein", email: "albert#yahoo.ca") {
id
name
}
}
`;
Thank you!
Alright so I figured out what the issue was. I had to pass an object with a "query" key in the refetchQueries array:
const [addUser, { data, loading, error }] =
useMutation(ADD_USER_QUERY, {
refetchQueries: [{ query: GET_USERS_QUERY }],
});
It's weird though because it isn't mentioned in the ApolloDocs. They simply use an array...
// Refetches two queries after mutation completes
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO, {
refetchQueries: [
GET_POST, // DocumentNode object parsed with gql
'GetComments' // Query name
],
});
Does anyone know why that is?
Thanks!

Apollo Mutation - UI not updated after useMutation update

Background: always able to make Apollo store cache updated, but not UI
Question:
what's the reason that makes the UI updated or not?
what's the right way to pass the data object in update?
"react": "~16.9.0"
"#apollo/react-hooks": "^3.1.3"
Both UI and cache updated codes in my project:
update: (store, { data: { newPhoto } }) => {
const { bookInfo } = store.readQuery({ query: GET_BOOK_BY_ID, variables: { bookId } });
bookInfo.photos = [...bookInfo.photos, newPhoto];
store.writeQuery({
query: GET_BOOK_BY_ID,
variables: { bookId },
data: {
bookInfo
}
});
}
In this line: bookInfo.photos = [...bookInfo.photos, newPhoto];, the bookInfo object is amended directly and just passed back to writeQuery's data
This doesn't look okay to me as I saw people saying it needs to be "immutable" or "passing new object", etc.
if you experience the same thing, please check following list:
go to check out https://github.com/apollographql/apollo-client/pull/4543. By applying the freezeResults & assumeImmutableResults into the ApolloClient will help detect the issue. For my case, the issue actually occurs inside the parent component, which I mutated the Apollo store objects, instead of the component that calling client.writeQuery, which is generally hard for others to notice too, in my opinion.
const client = new ApolloClient({
link: ...,
cache: new InMemoryCache({
freezeResults: true, // new
}),
assumeImmutableResults: true, // new
});
Ensure you mutate the data in an immutable fashion. (i.e. Apollo store object hasn't been changed until the end of update) https://github.com/immerjs/immer definitely helps to keep your change in immutable fashion. I used this to mutate my nested object and it's working so well.
Try to use client returned from the useMutation, then you get client.writeQuery to do the update. Although I'm not sure about this point, a lot of people spreading this message, probably help in some cases.
import { useMutation } from '#apollo/react-hooks';
import produce from "immer";
const [mutate, { client }] = useMutation(MUTATION_GQL);
const submit = () => {
mutate({
variables: { inputs },
update: (store, { data: { response }) => {
// get existing cache returned from the query
const cache = store.readQuery({ query: QUERY_GQL, variables: { id } });
// manipulate the cache in immutable fashion
const data = produce(cache, draftCache => {
draftCache.title = "new title";
draftCache.approval = response;
});
// write the cache back to that query
// REMEMBER the variables inside writeQuery too!
client.writeQuery({
query: QUERY_GQL,
variables: { id },
data,
});
}
})
}
Try to use useQuery to read the data from ApolloClient instead of readQuery so you will get the updated cache from Apollo's store

How to run a graphQL query with react-apollo?

My GraphQLServer query:
query {
sources {
name
id
}
}
returns array of objects like so:
{
"data": {
"sources": [
{
"name": "bootstrap.css",
"id": 1
},
{
"name": "normalize.css",
"id": 2
}
]
}
}
Trying to understand how to call it from React (redux). I tried:
function mapQueriesToProps( {ownProps, state}) {
return {
data: {
query: gql`
query {
sources {
id
name
}
}
`,
forceFetch: false,
returnPartialData: false,
},
};
};
But nothing happens. Just get loading: true. I'm using apollo-client and got it all wired up according to docs (I think). By that I mean I'm using connect(mapQueriesToProps) etc.
When does the query actually get run?
Does it automatically get run when component loads?
Is there a step I need to do to wait for data to return?
How does the component know to wait for the data?
Is it alright to return an array? I couldn't find any examples of returning an array in the docs.
I only want to run this once, not do any polling or anything. Is that the only way?
This is my first test of Apollo. Any guidance appreciated, because the docs are rather skimpy for beginners.
Have you tried naming the query? I.e. writing query abc { instead of just query {. If that fixes the problem, you should file an issue on react-apollo.
If it doesn't work, you can start narrowing down the error by checking if there are any errors in the console and checking in the network tab if the browser actually makes a request.
Strangely, this 1st method works for me...
Assuming:
import gql from 'graphql-tag';
import { graphql } from 'react-apollo';
You might just be able to (e.g):
const getSources = gql`
query {
sources {
id
name
}
}
`;
export default graphql(getSources) (connect(mapStateToProps, mapDispatchToProps)(YouContainer));
YouContainer props.data will have your query result.
And this 2nd method, also works (probably the best approach):
const getSources = graphql(gql`
_your_query_here_
`);
const connector = connect(mapStateToProps, mapDispatchToProps);
export default connector(getSources(YourContainer));
And as #helfer mentioned, you should name your query also.

Resources