简体   繁体   中英

How to pre-fill a form with data from a GraphQL query using Apollo 2?

I am having difficulty getting the data from an Apollo 2 GraphQL query into regular form fields.

How are others approaching this? Previously tried Relay and was successful in setting the form fields state with the component constructor.

In Apollo however, even after searching for hours and reading tons of documentation I am still in the dark...

During initialization of the component the state always returns null as the props are set at a later stage. I tried to set it after loading, but that gets me into an endless loop...

I am completely lost :/

This is what I have now come up with... it works. But is this the recommended way to approach this requirement?

import React from 'react';

import { Route, Redirect, Switch } from "react-router-dom";

import { graphql } from 'react-apollo';
import gql from 'graphql-tag';

// SEMANTIC-UI REACT
import { Container, Form, Button, Loader, Message } from "semantic-ui-react";


const MY_QUERY = gql`
  query {
    erp(id:"RXJwTm9kZToy") { 
      id
      name
      code
    }
  }`;


class Test extends React.Component {

  constructor(props) {
    super(props)
    this.state = {
    }
  }

  componentWillReceiveProps(newProps) {
    if (newProps !== this.props) {
      this.setState({
        erp_name: (newProps.data.erp || "").name,
        erp_code: (newProps.data.erp || "").code
      });
    }
  }

  render() {
    if (this.props.data.loading) {
      return <Loader active inline='centered' />;
    } else if (this.props.data.error) {
      return <Message error content={this.props.data.error.message} />;
    } else {
      return (
        <Form>
          <ul>
            <li key={this.props.data.erp.id}>{this.props.data.erp.name}</li>
          </ul>
          <Form.Input name="erp_name" label="ERP Name" placeholder="Name" value={this.state.erp_name} />
          <Form.Input name="erp_code" label="ERP Code" placeholder="Code" value={this.state.erp_code} />
          <Button type='submit'>Submit</Button>
        </Form>
      );
    }
  }
}

export default graphql(MY_QUERY)(Test);

This is an small example with React and ReduxForm. Maybe this will help you.

In this example the query will be executed at the initialization of the component and will return the data needed to fill out the form.

I hope it helps you!

import PropTypes from 'prop-types'
import React, { Component } from 'react'
import { compose } from 'recompose'
import { graphql, withApollo, gql } from 'react-apollo'
import { Form } from 'redux-form'

class MyExampleComponent extends Component {
  render() {
    const { userData } = this.props

    return (
      <Form
        initialValues={userData}
      />
    )
  }
}

MyExampleComponent.propTypes = {
  userData: PropTypes.object
}

const query = gql`
  query getUser($userId: ID) {
    user(id: $userId) {
      age
      firstName
      lastName
    }
  }
`

export default compose(
  withApollo,

  graphql(query, {
    props: ({ data }) => ({
      isLoading: data.loading,
      userData: data.user
    }),
    options: ({ params }) => ({
      variables: { id: params.id }
    })
  })
)(MyExampleComponent)

The approach I'm currently using is to render the form always even if the data is not yet available. This way there is no check needed for the loading prop as it presumes all data is available. In case you do check for the loading prop and only render the form when the data has been loaded then the client browser waits with showing the form until data is available and this might feel annoying especially when it takes longer for the data to be available. Better let the browser use this spare time to build the form in the DOM already.

But how to know the initial state from the graphql query and prefill?

The state is used to keep the form input data while the initial data from the graphql source is NEVER loaded into the state! And every time the (truth) data is needed it will be recalculated. This recalculation should happen at one place only so I introduced a helper function getTruthData(). And wherever you need the truth data you let it handle by this helper function. Eg with the rendering and also with submitting the updated data back to graphql mutate. And only in this getTruthData() function it will be needed to return the data based on availability. Don't mind about performance as this recalculating the truth data is really fast and caching would only introduce unpredictable results due to the nature of React re-rendering.

I've (tried to) update your code. What I did:

  • remove componentWillReceiveProps as it will not be needed
  • remove the check for loading in the render
  • Introduce the getTruthData()
  • change from uncontrolled to controlled form ( I noticed later although not related to the question )

The code:

class Test extends React.Component {
  constructor(props) {
    super(props);
    this.getProfileTruth = this.getProfileTruth.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  getTruthData() {
    return {
      erp_id: this.props.data.erp.id || '',
      erp_name: this.state.erp_name || this.props.data.erp.name || '',
      erp_code: this.state.erp_code || this.props.data.erp.code || '',
    };

    // OR you might want to use the underscore helper for larger forms with more fields to keep like this:

    return _.defaults(
      {},
      this.state,
      this.props.data.erp,
      { erp_name: '', erp_code: '' },
    );
  }

  handleChange(data) {
    this.setState(data);
  }

  handleSubmit(e) {
    e.preventDefault();
    const { erp_id, erp_name, erp_code } = this.getProfileTruth();
    // run a mutate on graphql
  }


  render() {
    const { erp_id, erp_name, erp_code } = this.getProfileTruth();

    return (
      <Form>
        <ul>
          <li key={erp_id}>{erp.name}</li>
        </ul>
        <Form.Input
          name="erp_name"
          label="ERP Name"
          placeholder="Name"
          value={erp_name}
          onChange={e => this.handleChange({ erp_name: e.target.value })} />
        <Form.Input
          name="erp_code"
          label="ERP Code"
          placeholder="Code"
          value={erp_code}
          onChange={e => this.handleChange({ erp_code: e.target.value })} />
        <Button type='submit' onClick={this.handleSubmit}>Submit</Button>
      </Form>
    );
  }
}

The side effect might be that if the data from graphql will take really long that the form can be entered already. With the controlled form you could check in the handleChange to only allow changes when the data is loaded properly. You could even de-activate the style of the button.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM