-
Notifications
You must be signed in to change notification settings - Fork 787
Mutation component #1520
Mutation component #1520
Conversation
@excitement-engineer first thing I see is being able to pass variables to runMutation. If you can take variables from both the function and props it would make for a much more powerful API because then you can partially apply the function from props and pass the rest of the variables into the function itself. |
src/Mutation.ts
Outdated
error, | ||
}; | ||
|
||
return children(this.runMutation, result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we add props instead of result here? That way people can still use polling functions from the component as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some WIP type feedback - I think we should go ahead and define our own MutationResult
like we did with Query
, as I'm not sure when apollographql/apollo-client#2795 will be handled.
src/Mutation.tsx
Outdated
client: PropTypes.object.isRequired, | ||
}; | ||
|
||
constructor(props: any, context: any) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
props: MutationProps<TData, TVariables>
src/Mutation.tsx
Outdated
// update?: MutationUpdaterFn; | ||
// notifyOnNetworkStatusChange?: boolean; | ||
|
||
export interface MutationProps { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MutationProps<TData = any, TVariables = OperationVariables>
src/Mutation.tsx
Outdated
|
||
export interface MutationProps { | ||
mutation: DocumentNode; | ||
variables?: OperationVariables; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
: TVariables
src/Mutation.tsx
Outdated
mutation: DocumentNode; | ||
variables?: OperationVariables; | ||
optimisticResponse?: Object; | ||
children: (mutateFn: () => void, result?: any) => React.ReactNode; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This result is the from apollo-client
mutate<T>(options: MutationOptions<T>): Promise<FetchResult<T>>
Note there is a typing issue on it so it cannot yet be fully typed results: apollographql/apollo-client#2795
Alternately, we could define our own MutationResult
like we did in Query
and solve the type problem ourselves.
src/Mutation.tsx
Outdated
}); | ||
|
||
try { | ||
const response = await this.client.mutate({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will ultimately need parameterized types but apollographql/apollo-client#2795 needs fixed first to use them.
@rosskevin @excitement-engineer what do you guys think about the following?
I see this API being a lot more flexible, because 1. you get the render prop pattern, and 2. if you need to compose React Components together, it's really straight forward. |
No, this will be far more confusing. I've already made my opinion known - Along these lines, I have discussed with @excitement-engineer and I'm not even sure there is a great use case for a |
@rosskevin if I create a demo of what I discussed in a real use case would you consider otherwise? Or should I just create my own node module for the api I am thinking about? The pattern I’ve discussed would compose incredibly well with apollo-link-state when providing local state used in turn as variables for a remote query/mutation |
@lifeiscontent I am not the ultimate authority on the decision, but my thoughts are centered around ability for many to understand. What is clear is that many would not comprehend passing parameters as children, therefore it would be a docs/education problem and would create more issues. I suggest wrapping it as you desire. I have a wrapper on |
Might be worth to take a look at #1550 (comment) Currently what is the state of this? I would like to help |
@kandros I have been on holidays for the past few weeks. But I will be picking this PR up again in a couple of days when I get back:) |
@excitement-engineer when can I test the latest release? e.g. |
You can use Query by installing react-apollo@beta Mutation is WIP |
@excitement-engineer totally personal feedback: I think |
@kandros I think there is a use case for performing some custom operation as soon as the mutation is done or there is an error (e.g. updating the state or calling a callback in the props). This cannot be done using the render prop AFAIK. Therefore the |
Since onComplete just pass data and onError pass the error, can’t we estimate the completion state by the loading prop and error state by state prop in the renderer? Am I missing some specific usecases? |
I re read your answer, I get it now 👍🏻 |
src/Mutation.tsx
Outdated
return; | ||
} | ||
|
||
this.verifyDocumentIsMutation(nextProps.mutation); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to do this only if the mutation is different from the previous one? It involves calling parser
which I think does a couple things that may add unnecessary overhead if the mutation doesn't change, which should be most of the time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! good tip
src/Mutation.tsx
Outdated
const mutationId = this.mostRecentMutationId; | ||
|
||
try { | ||
const response = await this.mutate(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we return the result when this.mutate
was resolved?
src/Mutation.tsx
Outdated
|
||
try { | ||
const response = await this.mutate(); | ||
this.onCompletedMutation(response, mutationId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should run this outside of the try/catch. Otherwise, if there's an error inside this method (which is likely since it calls user code), it would trigger the mutation error path, which would be incorrect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Thanks, updated it.
src/Mutation.tsx
Outdated
mutation: DocumentNode; | ||
variables?: TVariables; | ||
optimisticResponse?: Object; | ||
refetchQueries?: string[] | PureQueryOptions[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jbaxleyiii if we want to get rid of the query recycler, should we also get rid of the string array in this option? This would look up the currently watched queries by name, which seems to be the same thing that the deprecated updateQueries
does.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@leoasis yes for sure!
src/Mutation.tsx
Outdated
variables?: TVariables; | ||
optimisticResponse?: Object; | ||
refetchQueries?: string[] | PureQueryOptions[]; | ||
update?: MutationUpdaterFn; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm wondering if we should improve the type of the updater function so that it is parametric on TData
, so that the second argument provides the result in the shape you'd expect, instead of an object of any keys of any value.
We could do this here in react apollo, the same way we did it to improve some of the Query
types, and then we can send some PRs to correctly fix this in apollo client.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to improve on the type and made it parametric on TData, the second argument is now fully typed. The issue with this is that it is not so easy to to fix this issue. I had to override the type definition for ExecutionResult
which is contained in graphql-js. So we will have to update the type in that repo to create a long-term sustainable solution I think,
src/Mutation.tsx
Outdated
update, | ||
} = this.props; | ||
|
||
const response = await this.client.mutate({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not return the await'd mutate function instead of creating a variable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point! I updated the PR:) Thanks
I agree with @lifeiscontent - I think it's important for the
<Mutation mutation={mutation}>
{({ mutate }) => (
<Formik onSubmit={(formValues) => mutate({ variables: formValues })}
{/* etc */}
</Formik>
}}
</Mutation>
|
@excitement-engineer I think the only question remaining here is the usage of variables. Is it better done as a prop or passed to the function or both (i.e. assign). This is def a case where I think the prop remapping of the HOC gets it right and would be nice to replicate the ideas of that here in some manner? |
@excitement-engineer I'm really excited about your work on the Mutation Component! Will this be included in the non-beta 2.1 release? 🎉 |
@JonathanZWhite I'm hoping |
Hey @JonathanZWhite, nice to hear that you like the component! As far as I'm aware this will not be included in the 2.1 release. We will however do our best to get it in your hands as quickly as possible:) |
@excitement-engineer Awesome! Thanks for letting me know 🤘 |
…ad of through the props.
I updated the PR! It is now possible to pass variables as options in the mutation function. <Mutation mutation={mutation}>
{(createTodo, result) => {
setTimeout(() => {
createTodo({ variables });
});
return <div />;
}}
</Mutation> Thanks @thomasdashney for providing a use case with formik where passing variables in the props doesn't work |
@excitement-engineer I've merged this into a branch on the upstream so I can finish it out! :yay: |
@jbaxleyiii @excitement-engineer Do you see mutation components replacing the HOC mutation pattern completely? Or are there still going to be use-cases for both? In the latter case, what are some good guidelines to figure out when a component should be used vs a HOC? |
* Mutation component (#1520) * Initial implementation for Mutation component * Updated the error message * Improved the component. Added some initial tests * Added more props to the mutation component. * Improved typescript typings * fixed listing issues. * Added onComplete and onError props * Implemented logic for handling changes in props. * Changelog * prop-types validation * The render prop only shows the result of the most recent mutation in flight * Only verify document if the mutation has updated * Moved the onCompletedMutation outside the try-catch * Improved the type for the update prop. * Allow for passing variables as options in the mutation function instead of through the props. * removed all types for mutation component * return result from mutate call * remove default data type * bundle size increase for mutation component
This is an implementation of the
<Mutation />
component.Note, this is still a WIP. I will be improving it over the next few days and adding tests.