Consume data and make Dynamic Form with React

Asked

Viewed 1,853 times

0

I need to consume the data of a webservice to create a form interface for dynamic filling, that is, it must adapt the form structure received by the webservice and not be fixed with the questions of it.

As this is the first time I’ve touched React, I spent the last few days immersed in documentation and video lessons, and thought I’d solve the problem as follows:

  • Consume the webservice data using the Axios library.
  • Pass incoming data to a variable.
  • Use the function map() to traverse this variable (which will be an Array), within my React Component.
  • Dynamically fill in the form interface.

Unfortunately, I don’t know what’s going wrong, if the problem is with my methodology or my code... I’ll show you the code here of the Form component, which I created to be this interface.

import React, { Component } from 'react'
import FormTitle from './form-title'
import FormTextInput from './form-text-input'
import FormDateInput from './form-date-input'
import FormStarInput from './form-star-input'
import axios from 'axios'

class Form extends Component {
  constructor() {
    super()
    this.formInfo = (axios.get("https://coletum.com/api/graphql?query={form_structure(formId:6950){label,componentId,type,helpBlock,order,components}}&token=7s5txcu909kwc48wookgw8g00occokk")
    .then(response => {
      this.formInfo = response.data.data.form_structure 
      console.log(this.formInfo)
      return this.formInfo
    }).catch(err => {
      console.log(err)
    })
    )
  }

  render () {
    return (
        <div className="container">
          <div className="row">
          <div className="col-12">
            <FormTitle>Cadastro de Pokémon</FormTitle>
            <hr/>
          </div>
          <div className="col-md-7">
            <form>
                {this.formInfo.map(i => (
                  <div key={i} className="form-group row">
                  <label className="col-sm-4 col-form-label text-label" htmlFor={i.componentId}>{i.label}</label>
                  <div className="col-sm-8">
                    <FormTextInput componentId={i.componentId} />
                    <small className="form-text">{i.helpBlock}</small>
                  </div>
              </div>
                ))}
                

            </form>
          </div>
        </div>
      </div>
    )
  }
}

export default Form
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

Considering it was the first time I dealt with React, I already consider it a small miracle that I got there.

My problem is that I can, by Xios, play the array for my attribute this.formInfo... however, when trying to use the method map() in this attribute, it gives an error. I would even prefer to use the for in, instead of the map(), but I don’t know how to go through this array with the for in within the React rendering structure.

Can someone help me?

Thank you.

  • What error you get when trying to use map?

  • @Justcase , he says Cannot read Property 'map' of Undefined. When I give the console.log(this.formInfo), Inside the . then do get from Axios, it correctly returns me an array of 5 objects. However, it seems that he is not passing this value to the this.formInfo attribute when he is out of Axios. I cannot understand why.

1 answer

0


You are doing the operation in the wrong place, so your render will not update after you finish the search. You need a state variable (can be the 'formInfo') and search within a life cycle hook react. Another thing is that you cannot change this variable directly, if the "render" does not continue to be called after the search is over, you need use this.setState for that reason.

Note the change in your constructor and the addition of the componentDidMount method

import React, { Component } from 'react'
import FormTitle from './form-title'
import FormTextInput from './form-text-input'
import FormDateInput from './form-date-input'
import FormStarInput from './form-star-input'
import axios from 'axios'

class Form extends Component {
  constructor(props) {
    super(props)
    this.state = {
      formInfo: []
    }
  }

  componentDidMount () {
    axios.get("https://coletum.com/api/graphql?query={form_structure(formId:6950){label,componentId,type,helpBlock,order,components}}&token=7s5txcu909kwc48wookgw8g00occokk")
      .then(response => {
        this.setState({
          formInfo: response.data.data.form_structure 
        })
        console.log(this.formInfo)
      }).catch(err => {
        console.log(err)
      })
  }

  render () {
    return (
        <div className="container">
          <div className="row">
          <div className="col-12">
            <FormTitle>Cadastro de Pokémon</FormTitle>
            <hr/>
          </div>
          <div className="col-md-7">
            <form>
                {this.formInfo.map(i => (
                  <div key={i} className="form-group row">
                  <label className="col-sm-4 col-form-label text-label" htmlFor={i.componentId}>{i.label}</label>
                  <div className="col-sm-8">
                    <FormTextInput componentId={i.componentId} />
                    <small className="form-text">{i.helpBlock}</small>
                  </div>
              </div>
                ))}
                

            </form>
          </div>
        </div>
      </div>
    )
  }
}

export default Form

  • Man, fantastic! It worked! Thank you very much! If it’s not too much trouble for you, I’d like to ask one last question. I can make a parole within my render? For example, if the type within my array is "0", the Component inserted in the render will be Text. If the type for "1", will be Data, and etc? You can do something like this only with If?

  • Wonder! Yes, in this case you can do it with a ternary, like: {this.formInfo.map((value, index) => ( <div key={index} classname="form-group Row"> <label classname="col-Sm-4 col-form-label text-label" htmlFor={i.componentId}>{i.label}</label> <div classname="col-Sm-8"> { (value.type === 0) ? <Formtextinput componentId={i.componentId} /> : <Formdateinput componentId={i.componentid} /> } <small classname="form-text">{i.helpBlock}</small> </div> </div> ))}

  • Aaah, it doesn’t go with the if, but it goes with the ternary! What a mass, it’s true! But nesting ternary is kind of bad... I’ll have to think of a way, because I have at least 4 different types of inputs. Thank you very much!

  • You can have a function and control it within it, say you have a "renderField" method within your class, you can pass the type as parameter and create your logic inside. {this.formInfo.map(i => ( <div key={i} classname="form-group Row"> <label classname="col-Sm-4 col-form-label text-label" htmlFor={i.componentId}> {i.label} </label> <div classname="col-Sm-8"> { this.renderField(i.type) } <small classname="form-text">{i.helpBlock}</small> </div> </div> ))}

  • It’s an interesting structure too! I ended up solving it using ternaries of a single result, as for example { if the type is '1' ? use type="text" : null }, and did the 4 in a row, just changing the type. Since there will only be one type field in each object within the array, there is no way to return more than one, so it goes through the 4 tests, returning a type!

Browser other questions tagged

You are not signed in. Login or sign up in order to post.