web-dev-qa-db-fra.com

Comment exécuter une mutation sur le montage avec React Composant Mutation d'Apollo 2.1?

Nous passons actuellement de Relay à React Apollo 2.1 et quelque chose que je fais semble louche.

Contexte: Certains composants ne doivent être rendus que si l'utilisateur est authentifié (via une clé API), il existe donc un Authenticator composant guarding le reste de l'arbre.

Dans App.js, Il est utilisé comme ceci (évidemment tous les extraits ci-dessous sont des exemples minimes):

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

Si l'authentification réussit, MyComponent est rendu. Authentication envoie la mutation d'authentification au serveur lors du rendu/montage pour la première fois et appelle le render prop en conséquence. Authentication.js Ressemble à ceci:

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

Cette if (!called) login(); est ce qui me donne une pause. Si je ne spécifie pas if (!called), l'interface utilisateur devient épileptique et envoie des milliers de requêtes (ce qui est logique, appeler login() provoque la réexécution de render()), mais est-ce ainsi qu'il est censé être utilisé?

Il semble que le Query équivalent de composant diffère en ce que le simple rendu émet la requête. et je me demande s'il existe un moyen d'appliquer le même mécanisme à Mutation, ce qui nécessite d'appeler la fonction mutate dans le cadre de la propriété de rendu.

L'équivalent relais de l'extrait ci-dessus fait exactement ce que React Apollo Query fait sur 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>;
      }}
    />
  );
}
16
astorije

À tort ou à raison, Apollo fait certaines hypothèses sur la façon dont les requêtes et les mutations sont utilisées. Par convention, les requêtes ne récupèrent que les données tandis que les mutations, enfin, mutent les données. Apollo va plus loin dans ce paradigme et suppose que des mutations se produiront en réponse à une sorte d'action. Ainsi, comme vous l'avez observé, Query récupère la requête lors du montage, tandis que Mutation transmet une fonction pour réellement récupérer la mutation.

En ce sens, vous vous êtes déjà écarté de la façon dont ces composants sont "censés être utilisés".

Je ne pense pas qu'il y ait vraiment quelque chose de mal avec votre approche - en supposant que called ne soit jamais réinitialisé, votre composant devrait se comporter comme prévu. Cependant, vous pouvez également créer un composant wrapper simple pour tirer parti de componentDidMount:

class CallLogin extends React.Component {
  componentDidMount() {
    this.props.login()
  }

  render() {
    // React 16
    return this.props.children
    // Old School :)
    // return <div>{ this.props.children }</div>
  }
}

export default function Authenticator({ apiKey, render }) {
  return (
    <Mutation mutation={AUTH_MUTATION} variables={{ apiKey }}>
      {(login, { data, error }) => {
        const token = (data && data.login.token) || undefined;
        return (
          <CallLogin login={login}>
            {render({ error, token })}
          </CallLogin>
        )
      }}
    </Mutation>
  );
}
18
Daniel Rearden

Voici mon implémentation pour mon cas légèrement unique et cela fonctionne bien:

const VERIFY_USER = gql`
    mutation Verify_User($token: String!){
        verifyUser(token:$token){
            token
        }
    }
`

class Authenticator extends Component {

  state = { redirect: false }

  redirect = (authStatus) => {
    this.setState({redirect:true})
 }

  render() {
    if(this.redirect){
      return <Redirect to="/" />
    }

    const { token } = this.props.match.params
    console.log(token, "the token sir")

    return (
      <Mutation mutation={VERIFY_USER} variables={{ token }}>
        {(mutation) => {
          return (
            <VerifyUser authenticateUser={mutation} redirect={this.redirect}/>
          )
        }}
      </Mutation>
    )
  }
}


class VerifyUser extends Component {

  componentDidMount(){
    this.authUser();
  }

  authUser = async () => {
    let userStatus = await this.props.authenticateUser() // call the mutation
    this.props.redirect(userStatus)
  }

  render() {
    return null
  }
}

export default Authenticator
0
Lawrence Eagles