How to run a mutation on mount with React Apollo 2.1's Mutation component?

只愿长相守 提交于 2019-11-29 07:34:44

问题


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

回答1:


Right or wrong, Apollo makes some assumptions about how queries and mutations are used. By convention queries only fetch data while mutations, well, mutate data. Apollo takes that paradigm one step further and assumes that mutations will happen in response to some sort of action. So, like you observed, Query fetches the query on mount, while Mutation passes down a function to actually fetch the mutation.

In that sense, you've already deviated from how these components are "supposed to be used."

I don't think there's anything outright wrong with your approach -- assuming called never gets reset, your component should behave as intended. As an alternative, however, you could create a simple wrapper component to take advantage of 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>
  );
}



回答2:


Here is my implementation for my slightly unique case and it works well:

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


来源:https://stackoverflow.com/questions/49456738/how-to-run-a-mutation-on-mount-with-react-apollo-2-1s-mutation-component

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!