Reactjs/Apollo/AppSync Mutation Optimistic Response Resolved ID - reactjs

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
});

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!

xstate pass event data into onDone from service

I have xstate react script whereby a user fills in a form, and presses submit. On submit the xstate received a send("VALIDATE", {formData}) and that is run through a service that validates the form. On success the script transitions to the target: "success" and i need that final "success" state to call an external function which actually does the saving of the script.
I can get the data into the validator function, BUT, after the onDone, the subsequent success state doesn't appear to see the data.
How can I wire the data from the validating event to the success event??
id: 'validator',
initial: 'populating',
context: {},
states: {
populating: {
on: {
VALIDATE: 'validating'
}
},
validating: {
invoke: {
src: (context, data) => doValidate(data),
onDone: {
target: 'success',
actions: assign({ data: "hello world"})
},
onError: 'failure'
}
},
success: {
invoke: {
// I do see the hello world here, but what I want is the 'data' from the doValidate(data)
src: (ctx)=>{console.log("invoked success, what can I see here: ", ctx)}
}
},
I'm triggering the validate via: send("VALIDATE", formData)
If I understood you correctly you want the event from the first service onDoneto be available to the second service?
The easiest way would be to put the data on the context and access it in the other state/service and delete it afterward.
Or you can model your machine to send custom event to itself when the first
service is done.
import { createMachine, assign, send, interpret } from "xstate";
const machine = createMachine({
preserveActionOrder: true, //make sure actions are executed in order
id: "validator",
initial: "populating",
context: {},
states: {
populating: {
on: {
VALIDATE: "validating"
}
},
validating: {
on: {
// Listen to the event that fires when the first service is done
FIRST_SERVICE_DONE: {
target: "success"
}
},
invoke: {
src: (context, data) => Promise.resolve({ prop: "first service" }),
onDone: {
// target: 'success',
actions: [
assign({ data: "hello world" }),
//instead of using target:'success' send a custom
//event that has access to the data from the service
send((context, event) => {
//event here has event.data.prop === 'first service'
console.log("send evt ", event);
return {
type: "FIRST_SERVICE_DONE",
prop: event.data.prop
};
})
]
},
onError: "failure"
}
},
success: {
invoke: {
src: (_ctx, evt) => {
console.log("evt ", evt.prop); //first service
}
}
},
failure: {}
}
});
const service = interpret(machine);
service.start();
service.send({ type: "VALIDATE" });
Codesandbox
In Xstate the context is the extended state, so it doesn't seem like a good practice to use the context as a "machine memory". The extended state is used so that you don't have a potentially infinite number of states.
In case you need to preserve information that is sent by the event going to the state that invokes a Promise, you can add that information to the response. For example:
export const myMachineServices = {
myRequest: (context, event) =>
new Promise(resolve => {
setTimeout(() => {
resolve({
payload: 'some data',
})
}, 1000)
}).then(res => ({
...res,
event.data
}))
.catch(err => ({
...err,
event.data
})),
}

Apollo Client cache does not update

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

why wouldnt Apollo client re-render UI after successful cache register?

Just like the screenshot below, I am trying to add a task to my db and render it out on the client side as it updates. It does successfully readQuery and normalize the cache with my db, and adds to cache as below screenshot. The problem is the screen doesn't render again even after my code executes.
It does register new task correctly to the cache
Below is my code for this task.
// Apollo Client
await createBoard({
variables: { title, projectId },
refetchQueries: \[
{
query: GetBoardsDocument,
variables: { projectId },
},
\],
update: (cache, { data }) => {
const newBoardRes = data?.createBoard.boards;
const newBoard = newBoardRes && newBoardRes\[newBoardRes.length - 2\];
const existingBoards = cache.readQuery({
query: GetBoardsDocument,
variables: { projectId },
});
// console.log("newBoard", newBoard);
if (!existingBoards) return;
console.log("existingBoards", existingBoards);
console.log("newBoardRes", newBoardRes);
// cache.evict({ fieldName: "boards:{}" });
if (!newBoardRes) return; cache.writeQuery({
query: GetBoardsDocument,
variables: { projectId },
data: {
getBoards: {
boards: \[...existingBoards.getBoards.boards, ...newBoardRes\],
// boards: \[...newBoardRes\],
},
},
});
// console.log("handleCreateBoard", writeRes);
if (refetch) refetch();
},
});
};
I am confused as to why its not updating UI. From what I understand, apollo client updates its cache hence prop data changes, hence should react component rerender.
Am I missing something here?
Please advise.

GraphQL: Adding subscription to schema

I am trying to setup subscriptions with GraphQL and Relay. I have the following mutation which adds a new team:
const createTeamMutation = mutationWithClientMutationId({
name: 'CreateTeam',
inputFields: {
ownerId: { type: new GraphQLNonNull(GraphQLInt) },
name: { type: new GraphQLNonNull(GraphQLString) },
},
outputFields: {
team: {
type: teamType,
resolve: (payload) => payload
}
},
mutateAndGetPayload: (team) => {
const { ownerId, name } = team;
// Using Sequalize ORM here..
return db.models.team.create(team)
.then.... etc
}
});
This works fine but I can't seem to figure out how to get my subscriptions working at least in GraphiQL. I have the following definition in my schema:
const GraphQLCreateTeamSubscription = subscriptionWithClientId({
name: 'CreateTeamSubscription',
outputFields: {
team: {
type: teamType,
resolve: (payload) => payload
}
},
subscribe: (input, context) => {
// What is meant to go here??
},
});
I am not sure how to build out the subscribe feature and can't seem to find enough documentation. When I run the following in GraphiQL,
subscription createFeedSubscription {
team {
id
ownerId
name
}
}
I get the following error:
Cannot query field team on type Subscription.
Thank you for all the help!

Resources