Hi there!
While the Query component executes its query on mount, I don't believe the Mutation component counterpart has a way to do this. We have found ourselves in a situation where this would be very useful.
Imagine there is Foo component that does this:
const Foo = (accessToken) => (
<Query query={FOO} variables={{ accessToken }}>
{({ data }) => {
if (data && data.foo) return <div>{data.foo}</div>;
}}
</Query>
);
So as soon as it's called with <Foo accessToken="..." />, the GraphQL query gets run.
Now say there is another component that, instead of relying on a Query, relies on a Mutation. Intuitively, I would do something like this:
// This doesn't work:
<Mutation mutation={AUTH_MUTATION} variables={{ apiKey }}>
{({ data }) => <Foo accessToken={data.login.accessToken} />}
</Mutation>
This doesn't work because Mutation does not execute the query on mount, it only executes it when it's explicitly called. In a nutshell, this would ideally look exactly like the Query component, but for mutations.
My first try to make this work relied on the called property:
<Mutation mutation={AUTH_MUTATION} variables={{ apiKey }}>
{(login, { data, called }) => {
if (!called) login(); // 猬咃笍
return <Foo accessToken={data.login.accessToken} />;
}}
</Mutation>
This gave the following runtime warning:
Warning: Cannot update during an existing state transition (such as within 'render' or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to 'componentWillMount'.
Then, following this SO answer I got, went with something like:
class CallLogin extends React.Component {
componentDidMount() {
this.props.login();
}
render() {
return this.props.children;
}
}
<Mutation mutation={AUTH_MUTATION} variables={{ apiKey }}>
{(login, { data, error }) => (
<CallLogin login={login}>
<Foo accessToken={data.login.accessToken} />
</CallLogin>
)}
</Mutation>
Some random ideas, which are probably not great nor the best way to approach this, but here they are regardless :)
Mutation's children could omit the mutate function, which would call it on mount. Since I don't think there is a way to know such thing, Mutation could have an autorun prop (or better name) that would run it when mounted on top of any time mutate() gets called.
Query currently refuses to take a mutation in its query. Making it accept mutations (akin to Relay's QueryRenderer) could work, though I understand it would be a bit of a pain to get there seeing how the APIs of Query and Mutation differ. I'm thinking about optimisticResponse for example which doesn't exist for Query.
Thoughts?
Version
Query and Mutation components, this is intended for [email protected].Issue Labels
Agreed! We have a very similar use case - querying for data and then marking it as seen once the component is mounted. The wrapper route works to be sure, but it's just boilerplate to avoid the update while rendering warning...
Agreed too, i have the similar use case !
I have some mutations that need to be executed on mount and some queries that need to be executed on a user action. It seems to me that the Query and Mutation components are too opinionated.
Would it be possible to merge the two components and have triggering of the request be user configurable? Query would default to on mount and mutation would default to on callback.
For those around still struggling, I've kinda made a little hack. I nest my component within the Mutation function and work with the component life cycle of the child. Ends up looking like
<Mutation mutation={MY_MUTATION} variables={{ ... }} >
{(mutateIntegration, { data, loading, error }) => (
<MyComponent mutateIntegration />
)}
</Mutation>
Then in my <MyComponent /> class,
componentDidMount() {
this.props.mutateIntegration();
}
A configurable option would really come in handy for those of us who are using apollo-link-rest.
There are many times, in my experience, where REST APIs will use a POST for something like a search endpoint which you are technically querying, but because of the limitation that you cannot make a POST request through the Query component it requires all kinds of additional hackery when a simple prop to run it on component mount could ease that pain.
I understand that my situation may be an edge case, but it's something I've run into and would love to have a cleaner implementation than what I'm having to do. :)
Hi everyone,
Here is a generic MutationOnMount component I wrote for my own purpose, if anyone is interested:
import React from 'react';
import { Mutation } from 'react-apollo';
class DoMutation extends React.Component {
componentDidMount() {
const { mutate } = this.props;
mutate();
};
render() {
return null;
};
};
const MutationOnMount = ({ children, ...other }) => {
return (
<Mutation
{...other}
>
{(mutate, { data, loading, error }) => (
<React.Fragment>
<DoMutation mutate={mutate} />
{ children && children(mutate, { data, loading, error }) }
</React.Fragment>
)}
</Mutation>
)
};
export default MutationOnMount;
Same case here, I want to trigger a Mutation once I access to a specific URL
For those around still struggling, I've kinda made a little hack. I nest my component within the Mutation function and work with the component life cycle of the child. Ends up looking like
<Mutation mutation={MY_MUTATION} variables={{ ... }} > {(mutateIntegration, { data, loading, error }) => ( <MyComponent mutateIntegration /> )} </Mutation>Then in my
<MyComponent />class,componentDidMount() { this.props.mutateIntegration(); }
This is the only solution that work for my case, so I've to create a simple wrapper component to take advantage of componentDidMount
I used the graphql api exposed by react-apollo for this
const withSeenMutation = graphql(SET_COLLECTION_SEEN, {
options: ({ match }) => ({
variables: {
id: match.params.id,
},
}),
});
@withSeenMutation
class MyPage extends Component {
componentDidMount() {
this.props.mutate();
}
For those around still struggling, I've kinda made a little hack. I nest my component within the Mutation function and work with the component life cycle of the child. Ends up looking like
<Mutation mutation={MY_MUTATION} variables={{ ... }} > {(mutateIntegration, { data, loading, error }) => ( <MyComponent mutateIntegration /> )} </Mutation>Then in my
<MyComponent />class,componentDidMount() { this.props.mutateIntegration(); }
This doesn't work if you're working on a delete mutation as is my case. In other words I'm returning a null in the body since I'm currently within a Note component that needs to be deleted during the mutation callback response. I'll post a reply once I get my notes app working.
Here's what I ended up doing for the URINote component and it works very well. There's absolutely no need to directly invoke * graphql* and this particular scenario can be easily adapted for adding or updating (editing) notes or other components you may have. If you have any questions about the process tag me in a comment or DM me. Using styled-components, Flexbox, Apollo, GraphQL, React and ES6.
import PropTypes from "prop-types";
import styled from "styled-components";
import React from "react";
import ClickableText from "~c/text/ClickableText";
import Icon from "~c/Icon";
import Flexbox from "~c/containers/Flexbox";
import { Mutation } from "react-apollo";
import gql from "graphql-tag";
import translate from "~/utils/translate"
const StyledURINote = styled.div`
color: ${({ inheritTextColor }) => inheritTextColor && "inherit"};
`
// There should just be one trash icon per each note component
// and this should always be the case:
const StyledTrashIcon = styled(Icon)`
color: ${({ theme }) => theme.color.primary};
padding-right: 30px;
`
export const DELETE_URI_METRIC_NOTE = gql`
mutation deleteUriMetricNote($input: Long!) {
deleteUriMetricNote(id: $input) {
id
}
}
`
export default class URINote extends React.Component {
constructor(props) {
super(props)
this.state = {deleteNote: false};
}
static propTypes = {
date: PropTypes.string,
id: PropTypes.number.isRequired, // The PK to the URI_METRIC_NOTE table note record.
note: PropTypes.string.isRequired,
title: PropTypes.string.isRequired
}
// Delete this note
_onClickTrashIcon = mutationFunc => {
const variables = {
input: this.props.id
};
mutationFunc({variables});
this.setState({deleteNote: true});
alert(translate("label.note.delete"));
}
_renderNote = mutationFunc => {
return (
<StyledURINote>
<br />
<Flexbox direction="row" justifyContent="space-between">
<h5>
{this.props.title}
{this.props.date ? ` | ${new Date(this.props.date).toDateString()}` : ""}
</h5>
<div id="uriNoteTrashIcon">
<ClickableText onClick={() => {this._onClickTrashIcon(mutationFunc)}}>
<StyledTrashIcon type="trash" fixedSize />
</ClickableText>
</div>
</Flexbox>
<br />
<p>{this.props.note}</p>
<br />
<hr />
</StyledURINote>
)
}
render() {
let { deleteNote } = this.state;
if(deleteNote) {
return null;
} else {
return (
<Mutation mutation={DELETE_URI_METRIC_NOTE}
onCompleted={this._onCompletedMutation} onError={console.error}>
{this._renderNote}
</Mutation>
)
}
}
}
I'm triaging issues today and this one seems popular, with potential solutions. Does someone want to PR a solution and test for review?
Here is a generic solution using useEffect:
// @flow
import React, {
useEffect
} from 'react';
import {
Mutation
} from 'react-apollo';
import {
DEAUTHENTICATE_MUTATION
} from '../mutations';
import InlineMessage from '../components/InlineMessage';
const DoMutation = (props) => {
useEffect(() => {
props.mutate();
});
return <InlineMessage
type='neutral'
message='Signing out.'
/>
};
export default (viewProps) => {
return <Mutation
onCompleted={() => {
viewProps.history.push('/');
}}
mutation={DEAUTHENTICATE_MUTATION}>
{(deauthenticate, {loading, error, data}) => {
const mutate = () => {
deauthenticate()
.then(() => {
viewProps.history.push('/');
})
.catch(() => {
viewProps.history.push('/');
});
};
return <DoMutation
mutate={mutate}
/>
}}
</Mutation>;
};
````
const withSeenMutation = graphql(SET_COLLECTION_SEEN, {
options: ({ match }) => ({
variables: {
id: match.params.id,
},
}),
});
@withSeenMutation
class MyPage extends Component {
componentDidMount() {
this.props.mutate();
}
````
@tlenclos Looks like a viable one-off solution, but what about multiple mutations/queries?
To help provide a more clear separation between feature requests and bugs, and to help clean up the feature request backlog, React Apollo feature requests are now being managed under the https://github.com/apollographql/apollo-feature-requests repository.
This feature request will be closed here, but anyone interested in migrating this feature request to the new repository (to make sure it stays active), can click here to start the feature request migration process. This manual migration process is intended to help identify which of the outstanding feature requests are still considered to be of value to the community. Thanks!
Most helpful comment
Hi everyone,
Here is a generic
MutationOnMountcomponent I wrote for my own purpose, if anyone is interested: