Apollo Client cache does not update - reactjs

I am using Apollo Server / Client and the cache does not seem to work on update mutations. (Create, Delete). The server gets updated but nothing happens on the front end. I have to reload the page to show the new item / show change of an item.
I followed the Apollo docs and modeled it after their sandbox implementation.
Let me know if you need more of my code, thank you.
Here is my code:
<form
onSubmit={(e) => {
e.preventDefault();
createUser(
{
variables: {
name: input.value,
email: input.value,
password: input.value
}
},
{
update(cache, { data: { createUser } }) {
cache.modify({
fields: {
allUsers(existingUsers = []) {
const newUser = cache.writeFragment({
data: { createUser },
fragment: gql`
fragment NewUser on User {
name
email
}
`
});
return existingUsers.concat(newUser);
}
}
});
}
}
);
}}
>

You need to provide an id property in the writeFragment method. Here's the example on the docs:
client.writeFragment({
id: '5',
fragment: gql`
fragment MyTodo on Todo {
completed
}
`,
data: {
completed: true,
},
});
Also, writeFragment returns void, so you need to use readFragment to get the data you want, or just use the data available in the mutation's result

Related

Apollo mutation that depends on a previous mutation in a for loop

I'm using react native and apollo client for an app that creates a chat given an array of selected users. My code looks like this:
const [addUser, {
data: userAdded, loading: addingUsers, error: errorAddingUsers,
}] = useMutation(ADDUSERTOCHAT)
const [makeChat, {
data: chat, loading: chatLoading, error: chatError,
}] = useMutation(NEWCHAT, {
variables: { ownerId: viewerId },
onCompleted: () => {
for (let i = 0; i < selectedFriends.length; i++) {
addUser({
variables: {
chatId: chat.newChat.id,
id: selectedFriends[i].id,
},
onCompleted: () => {
if (i === selectedFriends.length - 1) {
navigation.navigate('Chat', { chatId: chat.newChat.id })
}
},
})
}
},
})
Right now, this does not work. I am not sure how to run the addUser mutation only after the chat is created, and I'm also not sure if the for loop will work to run a mutation for every selected friend. I also need to navigate to the screen chat once everything in the process is done and I'm not sure if the condition I have will work for that. In sum, I'm a bit lost with how to sequence these mutations and can't get it to work. Any help is appreciated thanks!

Cache data may be lost when replacing the my field of a Query object

this my code
const NewVerificationCode = () => {
const { loading, error, data = {}, refetch } = useQuery(CONFIRMATION_CODE, {
skip: true,
onError: (err) => {},
});
console.log(loading, error, data);
if (loading || error) {
return <ErrorLoadingHandler {...{ loading, error }} />;
}
return (
<form
onSubmit={(e) => {
refetch();
e.preventDefault();
}}
>
<div>
<button type="submit" className="signUpbutton">
{"Send the message again"}
</button>
</div>
</form>
);
};
const CONFIRMATION_CODE = gql`
query {
my {
sendNewTokenForConfirmation
}
}
`;
when i make a request i get a warning
Cache data may be lost when replacing the my field of a Query object.
To address this problem (which is not a bug in Apollo Client), either ensure all >objects of type My have IDs, or define a custom merge function for the Query.my >field, so InMemoryCache can safely merge these objects
existing:
{"__typename":"My","getUser{"__typename":"User","email":"shakizriker0022#gmail.com"}}
incoming: {"__typename":"My","sendNewTokenForConfirmation":"SUCCESS"}
For more information about these options, please refer to the documentation:
I followed the links.
I read the documentation and realized that the problem is in the apollo client cache (typePolicies).
But how should I solve this problem I just can't figure out.
What should i write in typePolicies to get rid of the warning ?.
You may need to return an id for Apollo to uniquely identify that object in the cache.
I think this issue is similar to yours:
link
const CONFIRMATION_CODE = gql`
query {
my {
id
sendNewTokenForConfirmation
}
}
`;
Every object should return id, _id or alternatively a custom id field (https://www.apollographql.com/docs/react/caching/cache-configuration) for automatic merges to function.
const cache = new InMemoryCache({
typePolicies: {
Product: {
keyFields: ["custom-id-field"],
},
},
});
(!)People often forget that cache operations require the same variables used for the initial query (!)
Mutation Sample (adding a purchase):
update(cache, { data: NewPurchase } }) => {
let productCache = cache.readQuery({
query: productQuery,
variables: {
id: productId
}
})
cache.writeQuery({
query: productQuery,
variables: { id: productId },
data: {
Product: {
...productCache.Product,
purchases: productCache.Product.purchases.concat(NewPurchase)
}
}
});
};
}
(Important: Each Purchase also requires it's own individual id that needs to be returned)

Handling Graphql Mutation update, cache read and writeQuery, if the query is dynamic?

Doing nightlife app on freecodecamp https://learn.freecodecamp.org/coding-interview-prep/take-home-projects/build-a-nightlife-coordination-app/
I am trying to implement 'Go' button, similarly 'Like' button on Youtube or Instagram. Users click the button the number(counting how many users go) goes up meaning users will go there and click again, it revokes, the number decreases, users will not go there.
It seems like working well except the issue, I have to refresh the page and then, the number has increased or decreased and throws the error like below so:
Invariant Violation: Can't find field getBars({}) on object {
"getBars({\"location\":\"vancouver\"})": [
{
"type": "id",
"generated": false,
"id": "Bar:uNgTjA9ADe_6LWby20Af8g",
"typename": "Bar"
},
{
"type": "id",
"generated": false,
"id": "Bar:CwL5jwXhImT_7K5IB7mOvA",
"typename": "Bar"
},
{
"type": "id",
"generated": false,
"id": "Bar:mdt1tLbkZcOS2CsEbVF9Xg",
"typename": "Bar"
},
.
.
.
I am assuming handling update function will fix this issue but unlike the example from Apollo documentation:
// GET_TODOS is not dynamic query
// nothing to pass as variables to fetch TODO list
<Mutation
mutation={ADD_TODO}
update={(cache, { data: { addTodo } }) => {
const { todos } = cache.readQuery({ query: GET_TODOS });
cache.writeQuery({
query: GET_TODOS,
data: { todos: todos.concat([addTodo]) },
});
}}
>
My query is dynamic:
// I have to pass location variable, otherwise it won't fetch anything.
const GET_BARS_QUERY = gql`
query getBars($location: String!) {
getBars(location: $location) {
id
name
url
rating
price
image_url
goings {
username
}
goingCount
}
}
`;
I believe I might need to handle to provide location using readQuery and writeQury but not too sure what I should do.
Here's my code:
const GoButton = ({ user, bar }) => {
const { token } = user;
const { id, goings, goingCount } = bar;
const [userGoes] = useMutation(GO_MUTATION, {
variables: { yelp_id: id },
update(proxy, result) {
const data = proxy.readQuery({
query: GET_BARS_QUERY
});
data.getBars = [result.userGoes, ...data.getBars];
proxy.writeQuery({ query: GET_BARS_QUERY, data });
}
});
return (
<Button onClick={userGoes}>
Go {goingCount}
</Button>
);
};
const GO_MUTATION = gql`
mutation go($yelp_id: String!) {
go(yelp_id: $yelp_id) {
id
goings {
id
username
}
goingCount
}
}
`;
export default GoButton;
Full code here https://github.com/footlessbird/Nightlife-Coordination-App
when you read/write the getBars query, you need to pass the location as a variable
const [userGoes] = useMutation(GO_MUTATION, {
variables: { yelp_id: id },
update(proxy, result) {
const data = proxy.readQuery({
query: GET_BARS_QUERY,
variables: {
location: 'New York'
}
});
data.getBars = [result.userGoes, ...data.getBars];
proxy.writeQuery({ query: GET_BARS_QUERY, data,
variables: {
location: 'New York'
}
});
}
});

Reactjs/Apollo/AppSync Mutation Optimistic Response Resolved ID

So first off I will start by saying I added an optimistic response to my mutation so it would it stop producing duplicates as referenced here and from this previous S.O. question.
So that is all working but I have a set of dependant mutations that run after the first using async await.
submitForm = async () => {
// Only submit if form is complete
if (!this.state.saveDisabled) {
try {
// Optimistic Response is necessary because of AWS AppSync
// https://stackoverflow.com/a/48349020/2111538
const createGuestData = await this.props.createGuest({
name: this.state.name,
})
let guestId = createGuestData.data.addGuest.id
for (let person of this.state.people) {
await this.props.createPerson({
variables: {
name: person.name,
guestId,
},
optimisticResponse: {
addPerson: {
id: -1, // A temporary id. The server decides the real id.
name: person.name,
guestId,
__typename: 'Person',
},
},
})
}
this.setState({
redirect: true,
})
} catch (e) {
console.log(e)
alert('There was an error creating this guest')
}
} else {
Alert('Please fill out guest form completely.')
}
}
Now this works and it is using the same pattern for the mutation as per the sample project
export default compose(
graphql(CreateGuestMutation, {
name: 'createGuest',
options: {
refetchQueries: [{ query: AllGuest }],
},
props: props => ({
createGuest: guest => {
console.log(guest)
return props.createGuest({
variables: guest,
optimisticResponse: () => ({
addGuest: {
...guest,
id: uuid(),
persons: [],
__typename: 'Guest',
},
}),
})
},
}),
}),
graphql(CreatePersonMutation, {
name: 'createPerson',
}),
)(CreateGuest)
The only problem is that I can't force the state to get updated to the ID that actually gets inserted when using Async Await, so all the person entries get the place holder UUID. Note, I have also tried using id: -1 as is done with the createPerson mutation but that didn't change anything, it just used negative one for all the entires.
Is there a better way of doing this? I am doing something wrong. This all worked without the optimisticResponse but it always created two entries per mutation.
Can you try this again? There were enhancements to the AppSync SDK for Javascript which no longer require you to use Optimistic Response. You can use it optionally if you still want an optimistic UI.
Additionally you can also now disable offline if that's not a requirement for your app by using disableOffline like so:
const client = new AWSAppSyncClient({
url: AppSync.graphqlEndpoint,
region: AppSync.region,
auth: {
type: AUTH_TYPE.API_KEY,
apiKey: AppSync.apiKey,
},
disableOffline: true
});

Relay mutation on plain array is not working

I am having a hard time figuring out how to do mutations on plain array via relay.
I am trying to add a new tag to a post.
It does not get updated on client-side after being successfully added on the server-side.
I have to manually reload to see the new tag.
I have tried both REQUIRED_CHILDREN and this.props.relay.forceFetch(), but to no avail.
Also, tried FIELDS_CHANGE for post.
GraphQL Schema:
Post {
id: ID!
text: String!
tags: [Tag!]!
}
Tag {
id: ID!
name: String!
}
AddTagToPostMutation:
static fragments = {
post: () => Relay.QL`
fragment on Post {
id
tags
}
`,
}
getMutation() {
return Relay.QL`mutation { addTagToPost }`;
}
getVariables() {
return {
name: this.props.tag.name,
};
}
getFatQuery() {
return Relay.QL`
fragment on AddTagToPostMutationPayload {
tag {
id
name
}
post {
id
tags
}
}
`;
}
getConfigs() {
return [{
type: 'REQUIRED_CHILDREN',
children: [Relay.QL`
fragment on AddTagToPostMutationPayload {
tag {
id
name
}
post {
id
tags
}
}
`],
}];
}
getOptimisticResponse() {
return {
tag: {
name: this.props.tag.name,
},
post: {
id: this.props.post.id,
},
};
}
As freiksenet already pointed out, FIELDS_CHANGE should be used in getConfigs() function. I took your schema, implemented the GraphQL types, server-side and client-side mutation to add tag to a post. The client-side gets updated successfully. I'm going to just elaborate the solution in my answer.
First, check your server-side mutation. My implementation uses graphql and graphql-relay libraries and looks like below. Notice that the output of the server-side mutation is a post to which a tag has been added. This post is the one whose ID was provided as input.
const AddTagToPostMutation = mutationWithClientMutationId({
name: 'AddTagToPost',
inputFields: {
postId: { type: new GraphQLNonNull(GraphQLID) },
name: { type: new GraphQLNonNull(GraphQLString) },
},
outputFields: {
post: {
type: PostType,
resolve: ({id}) => getPost(id),
},
},
mutateAndGetPayload: ({postId, name}) => {
const id = fromGlobalId(postId).id;
addTagToPost(id, name);
return {id};
},
});
Using graphiql, you can test your mutation:
mutation {
addTagToPost(input:{
postId: "UG9zdDpwb3N0Mg=="
name:"a new tag name"
clientMutationId:"123244"}) {
post {
id
text
tags {
id
name
}
}
}
}
I added a field posts for all posts to the root query. Using graphiql, I first checked the post IDs and used one above.
Using react-relay, the client-side mutation code looks like below. It is passed a prop post whose ID is used as input variable in getVariables() function. In the getConfigs() function, we specify that post field has to be updated. The association between the payload field post and the passed prop post is established using FIELDS_CHANGE mutation type.
export default class AddTagToPostMutation extends Relay.Mutation {
getMutation() {
return Relay.QL`mutation{addTagToPost}`;
}
getVariables() {
return {
postId: this.props.post.id,
name: this.props.name,
};
}
getFatQuery() {
return Relay.QL`
fragment on AddTagToPostPayload {
post {
id,
tags {
id,
name,
}
}
}
`;
}
getConfigs() {
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {
post: this.props.post.id,
},
}];
}
static fragments = {
post: () => Relay.QL`
fragment on Post {
id,
}
`,
};
}
The client-side mutation is invoked like this:
Relay.Store.commitUpdate(new AddTagToPostMutation({
post: postToModify,
name: tagName,
}));
I think you should just use FIELDS_CHANGE in such situations.
getConfigs() {
return [{
type: 'FIELDS_CHANGE',
fieldIDs: {post: this.props.post.id},
}];
}
getOptimisticResponse() {
return {
post: {
id: this.props.post.id,
tags: [...this.props.post.tags, this.props.tag],
},
};
}

Resources