-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ideas for improving mutations, mutation results, and optimistic UI #398
Comments
I don't mean to add too many thoughts into this discussion, but I wonder if reactivity is also related? I've been adding updates to the Apollo store from a CouchDB Some of the discussion on reactivity that I've seen: #180: updating the apollo store after creation |
@dahjelle seems like what you are describing is not relevant to this issue. That being said, it is something we are interested in supporting too. Maybe open a separate issue for a discussion? Thank you. |
A new idea how to do it with a reducer-style function: submitComment: (repoFullName, repoId, commentContent, currentUser) => ({
mutation: gql`
mutation ($repoFullName: String!, $commentContent: String!) {
submitComment(repoFullName: $repoFullName, commentContent: $commentContent) {
postedBy {
login
html_url
}
createdAt
content
}
}
`,
variables: {
repoFullName,
commentContent,
},
optimisticResponse: {
submitComment: {
postedBy: currentUser,
content: commentContent,
},
},
updateQueries: {
Comment: (prevRes, newComment, vars) => {
const prevEntry = prevRes.entry;
return {
Entry: {
...prevEntry,
comments: [newComment, ...prevEntry.comments],
}
};
}
},
}), as opposed to: mapQueriesToProps: ({ ownProps }) => ({
data: {
query: gql`
query Comment($repoName: String!) {
# Eventually move this into a no fetch query right on the entry
# since we literally just need this info to determine whether to
# show upvote/downvote buttons
currentUser {
login
html_url
}
entry(repoFullName: $repoName) {
id
postedBy {
login
html_url
}
createdAt
comments {
postedBy {
login
html_url
}
createdAt
content
}
repository {
full_name
html_url
description
open_issues_count
stargazers_count
}
}
}
`,
variables: {
repoName: `${ownProps.params.org}/${ownProps.params.repoName}`,
},
},
}),
mapMutationsToProps: () => ({
submitComment: (repoFullName, repoId, commentContent, currentUser) => ({
mutation: gql`
mutation submitComment($repoFullName: String!, $commentContent: String!) {
submitComment(repoFullName: $repoFullName, commentContent: $commentContent) {
postedBy {
login
html_url
}
createdAt
content
}
}
`,
variables: {
repoFullName,
commentContent,
},
optimisticResponse: {
__typename: 'Mutation',
submitComment: {
__typename: 'Comment',
postedBy: currentUser,
createdAt: +new Date,
content: commentContent,
},
},
resultBehaviors: [
{
type: 'ARRAY_INSERT',
resultPath: ['submitComment'],
storePath: [`Entry ${repoId}`, 'comments'],
where: 'PREPEND',
},
],
}), |
For 'You need to make sure that the fields in your mutation result match up with the fields you need to render in the UI, which basically means it should match the fields you ask for in the queries on this page of the UI. This can be mitigated using fragments, but that makes optimistic UI harder, see below' -- am I right in thinking that this also means that if you have any components outside of the current component that relies on data affected by the mutation, the result from the mutation will need to make sure to fetch the properties for those other components? e.g.: ComponentA fetches:
ComponentB fetches:
Now if ComponentB has a mutation that edits the title, will the result of the mutation need to return |
@saikat Yes, that's correct. But I'm not sure how to get around this. I guess one option is to somehow keep track of which fields are accessed on that todo item so that they can all be refetched. I feel like for complex cases (when lots of parts of the UI are being updated) it might be pretty reasonable to refetch queries, but for simple cases the current approaches will work? |
I wonder if having an option to just fetch every field on the mutation I do think that the complex case may be the more common one to handle as a On Tue, Jul 26, 2016 at 3:40 AM Sashko Stubailo notifications@github.com
|
I've read in your new Medium article on mutations about the new Is this really the best way to do this? I know that the underlying problem is somewhat difficult, but this still feels somewhat hack-ish to me. Doing optimistic updates is one thing, but they will be corrected very soon by a server response. As far as I understand the result of I can think of the following problems:
I've read in the documentation about Designing mutation results that it's good practice to fetch the parent of the mutated item. Couldn't this also apply to this scenario? The developer could tell the apollo client which lists the item could be part of, and apollo would re-fetch at least the ids of the items in the lists (or parts, if paginated). I might be totally wrong though, I'm not experienced with GraphQL and Apollo yet 🙂. In any case, it's definitely good that there is a feature for dealing with updating lists based on mutations. |
@saikat @amannn Note - if you fetch the parent of the mutated item or array, and you have IDs set up for your objects, it "just works" without using
There's another aspect of It feels like we added a feature that is almost too simple to use - it looks like it doesn't really do much, when in fact it does a lot of great things underneath that you just don't have to think about when using it. Perhaps just documenting this better and explaining how to use it in several cases would be enough? In short,
I don't think
IMO (1) and (2) are great for many situations, but (3) is useful for the case where you know exactly what needs to be updated for a particular mutation, and don't want to refetch too much data.
In complex pagination scenarios I think refetching the query will be the right approach. |
Thanks for the detailed response!
You mean the regular normalization technique with Btw. my comment is mostly about mutations that create or delete entities – I think updates are covered really well with normalization. Regarding mutation handling strategy #2 you mentioned: From your experience, do you know a good way how to do this in a way that the component that triggers the mutation doesn't have to know about which lists should be re-fetched because of that mutation? In a somewhat bigger redux app I'm currently working on, I solve this in the action creators. It's easy to invoke a re-fetch of particular lists when a entity got created that could potentially be in that list. It's not perfect, but at least there's one central place that invokes the list refetches, instead of all components that can create such entities knowing about which lists could potentially update. Does it make sense to do something like this in Apollo? Maybe call all mutations from a central place, where hooks for re-fetches can be registered? Or watch the actions that Apollo dispatches and if a particular mutation is successful, call some re-fetches? Whatever approach makes sense, I think this can be solved in user land – just wondering if you've experimented with something like this. EDIT: Maybe realtime queries for lists will eventually be the most elegant way to solve this specific problem. EDIT 2: Just found issue #448. That's actually exactly what I'd need for my use case. The only thing I'd consider is to set up |
yeah, I used this issue as a place to discuss ideas |
Right now, we have a working implementation of mutation results and optimistic UI, which can be seen in this PR to GitHunt: https://github.com/apollostack/GitHunt/pull/64/files
First, let's clear up the difference between mutation result handling and optimistic UI for that:
resultBehaviors
on the mutation. These tell the client where to put the result into the store, once a result is received. You need this if you want to avoid refetching queries after the mutation result, even if you don't want optimistic UI.Issues with mutations
Issues with mutation behaviors
storePath
to locate data. This isn't well documented and might require a lot of knowledge of internals.dataId
of the object you want. It's hard to access thedataIdFromObject
function inreact-apollo
.Issues with optimistic UI
__typename
in the optimistic result. This is unfortunate, because this is something you usually don't have to worry about.Ideas
To be continued....
Properties we want to preserve
Properties nice to preserve
The text was updated successfully, but these errors were encountered: