We are currently moving from Relay to React Apollo 2.1 and something I'm doing seems fishy.
Context: Some components must only be rendered if the user is authenticated (via an API key), so there is an Authenticator
component guarding the rest of the tree.
In App.js
, it gets used like this (obviously all snippets below are minimal examples):
import React from 'react';
import Authenticator from './Authenticator';
import MyComponent from './MyComponent';
export default function App({ apiKey }) {
return (
<Authenticator apiKey={apiKey}
render={({ error, token }) => {
if (error) return <div>{error.message}</div>;
if (token) return <MyComponent token={token} />;
return <div>Authenticating...</div>;
}}
/>
);
}
If authentication succeeds, MyComponent
gets rendered.
Authentication
sends the authentication mutation to the server when rendered/mounted for the first time and calls the render prop accordingly.
Authentication.js
looks as such:
import gql from 'graphql-tag';
import React from 'react';
import { Mutation } from 'react-apollo';
const AUTH_MUTATION = gql`mutation Login($apiKey: String!) {
login(apiKey: $apiKey) {
token
}
}`;
export default function Authenticator({ apiKey, render }) {
return (
<Mutation mutation={AUTH_MUTATION} variables={{ apiKey }}>
{(login, { data, error, called }) => {
if (!called) login(); // ⚠️ This seems sketchy ⚠️
const token = (data && data.login.token) || undefined;
return render({ error, token });
}}
</Mutation>
);
}
That if (!called) login();
is what is giving me pause. If I don't specify if (!called)
, the UI becomes epileptic and sends thousands of requests (which makes sense, calling login()
causes render()
to re-run), but is that how it's supposed to be used?
It seems like the Query
component equivalent differs in that simply rendering it emits the request. and I am wondering if there is a way to apply the same mechanism to Mutation
, which requires calling the mutate function as part of the render prop.
The Relay equivalent of the snippet above does exactly what React Apollo's Query
does on Mutation
:
// Authentication.js
import React from 'react';
import { graphql, QueryRenderer } from 'react-relay';
import { Environment } from 'relay-runtime';
// Hiding out all the `Environment`-related boilerplate
const environment = return new Environment(/* ... */);
const AUTH_MUTATION = graphql`mutation Login($apiKey: String!) {
login(apiKey: $apiKey) {
token
}
}`;
export default function Authenticator({ apiKey, render }) {
return (
<QueryRenderer query={AUTH_MUTATION} variables={{ apiKey }}
render={render}
/>
);
}
// App.js
import React from 'react';
import Authenticator from './Authenticator';
import MyComponent from './MyComponent';
export default function App({ apiKey }) {
return (
<Authenticator apiKey={apiKey}
render={({ error, props }) => {
if (error) return <div>{error.message}</div>;
if (props) return <MyComponent token={props.loginAPI.token)} />;
return <div>Authenticating...</div>;
}}
/>
);
}