Как установить значение по умолчанию для меню выбора в моем компоненте формы React? - PullRequest
4 голосов
/ 04 марта 2020

Я использую React 16.13.0. Я пытаюсь установить значение по умолчанию для меню выбора в моем компоненте формы. У меня есть это ...

import React, {Component} from 'react';

/* Import Components */
import Input from '../components/Input';
import Country from '../components/Country';
import Province from '../components/Province';
import Button from '../components/Button'

class FormContainer extends Component {
  statics: {
    DEFAULT_COUNTRY: 484;
  }

  constructor(props) {
    super(props);

    this.state = {
      countries: [],
      provinces: [],
      newCoop: {
        name: '',
        type: {
          name: ''
        },
        address: {
          formatted: '',
          locality: {
            name: '',
            postal_code: '',
            state: ''
          },
          country: FormContainer.DEFAULT_COUNTRY,
        },
        enabled: true,
        email: '',
        phone: '',
        web_site: ''
      },

    }
    this.handleFormSubmit = this.handleFormSubmit.bind(this);
    this.handleClearForm = this.handleClearForm.bind(this);
    this.handleInput = this.handleInput.bind(this);
  }

  /* This life cycle hook gets executed when the component mounts */

  handleFormSubmit(e) {
    e.preventDefault();
    const NC = this.state.newCoop;
    delete NC.address.country;

    fetch('/coops/',{
        method: "POST",
        body: JSON.stringify(this.state.newCoop),
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json'
        },
      }).then(response => {
        response.json().then(data =>{
          console.log("Successful" + data);
        })
    })
  }
  handleClearForm() {
    // Logic for resetting the form
  }
  handleInput(e) {
    let self=this
    let value = e.target.value;
    console.log("value:" + value);
    let name = e.target.name;
    //update State
    this.setValue(self.state.newCoop,name,value)
  }

  setValue = (obj,is, value) => {
       if (typeof is == 'string')
         return this.setValue(obj,is.split('.'), value);
       else if (is.length === 1 && value!==undefined)
         return this.setState({obj: obj[is[0]] = value});
       else if (is.length === 0)
         return obj;
       else
         return this.setValue(obj[is[0]],is.slice(1), value);
  }

  render() {
    return (
        <form className="container-fluid" onSubmit={this.handleFormSubmit}>

            <Input inputType={'text'}
                   title= {'Name'}
                   name= {'name'}
                   value={this.state.newCoop.name}
                   placeholder = {'Enter cooperative name'}
                   handleChange = {this.handleInput}

                   /> {/* Name of the cooperative */}
            <Input inputType={'text'}
                   title= {'Type'}
                   name= {'type.name'}
                   value={this.state.newCoop.type.name}
                   placeholder = {'Enter cooperative type'}
                   handleChange = {this.handleInput}

                   /> {/* Type of the cooperative */}

            <Input inputType={'text'}
                   title= {'Street'}
                   name= {'address.formatted'}
                   value={this.state.newCoop.address.formatted}
                   placeholder = {'Enter address street'}
                   handleChange = {this.handleInput}

                   /> {/* Address street of the cooperative */}

            <Input inputType={'text'}
                   title= {'City'}
                   name= {'address.locality.name'}
                   value={this.state.newCoop.address.locality.name}
                   placeholder = {'Enter address city'}
                   handleChange = {this.handleInput}

                   /> {/* Address city of the cooperative */}

          <Country title={'Country'}
                  name={'address.country'}
                  options = {this.state.countries}
                  value = {this.state.newCoop.address.country}
                  placeholder = {'Select Country'}
                  handleChange = {this.handleInput}
                  /> {/* Country Selection */}
          <Province title={'State'}
                  name={'address.locality.state'}
                  options = {this.state.provinces}
                  value = {this.state.newCoop.address.locality.state}
                  placeholder = {'Select State'}
                  handleChange = {this.handleInput}
                  /> {/* State Selection */}

          <Input inputType={'text'}
                   title= {'Postal Code'}
                   name= {'address.locality.postal_code'}
                   value={this.state.newCoop.address.locality.postal_code}
                   placeholder = {'Enter postal code'}
                   handleChange = {this.handleInput}

                   /> {/* Address postal code of the cooperative */}

          <Input inputType={'text'}
                   title= {'Email'}
                   name= {'email'}
                   value={this.state.newCoop.email}
                   placeholder = {'Enter email'}
                   handleChange = {this.handleInput}

                   /> {/* Email of the cooperative */}

          <Input inputType={'text'}
                   title= {'Phone'}
                   name= {'phone'}
                   value={this.state.newCoop.phone}
                   placeholder = {'Enter phone number'}
                   handleChange = {this.handleInput}

                   /> {/* Phone number of the cooperative */}
          <Input inputType={'text'}
                   title= {'Web Site'}
                   name= {'web_site'}
                   value={this.state.newCoop.web_site}
                   placeholder = {'Enter web site'}
                   handleChange = {this.handleInput}

                   /> {/* Web site of the cooperative */}


          <Button
              action = {this.handleFormSubmit}
              type = {'primary'}
              title = {'Submit'}
            style={buttonStyle}
          /> { /*Submit */ }

          <Button
            action = {this.handleClearForm}
            type = {'secondary'}
            title = {'Clear'}
            style={buttonStyle}
          /> {/* Clear the form */}

        </form>
    );
  }

  componentDidMount() {
    let initialCountries = [];
    let initialProvinces = [];
    // Get initial countries
    fetch('/countries/')
        .then(response => {
            return response.json();
        }).then(data => {
        initialCountries = data.map((country) => {
            return country
        });
        console.log("output ...");
        console.log(initialCountries);
        this.setState({
            countries: initialCountries,
        });
    });
    // Get initial provinces (states)
    fetch('/states/484/')
        .then(response => {
            return response.json();
        }).then(data => {
        console.log(data);
        initialProvinces = data.map((province) => {
            return province
        });
        this.setState({
            provinces: initialProvinces,
        });
    });
  }
}

const buttonStyle = {
  margin : '10px 10px 10px 10px'
}

export default FormContainer;

Но значение страны не устанавливается. Есть идеи?

Редактировать: Добавление кода компонента Country.jsx

import React from 'react';

class Country extends React.Component {
    constructor() {
        super();
    }

    render () {
        let countries = this.props.options;
        let optionItems = countries.map((country) =>
                <option key={country.id} value={country.id}>{country.name}</option>
            );

        return (
          <div className="form-group">
                        <label for={this.props.name}> {this.props.title} </label>
            <select
                      id = {this.props.name}
                      name={this.props.name}
                      value={this.props.value}
                      onChange={this.props.handleChange}
                      className="form-control">
                      <option value="" disabled>{this.props.placeholder}</option>
                      {optionItems}
            </select>
          </div>
        )
    }
}

export default Country;

Ответы [ 2 ]

5 голосов
/ 08 марта 2020

Есть небольшая проблема с вашим кодом, вы не передаете реквизиты в конструктор, а супер в компоненте Country. Вы должны передать props constructor и super или просто удалить constructor, поскольку вы ничего не делаете в constructor.

Попробуйте это.

class Country extends React.Component {
    constructor(props) {
        super(props);
    }

    render () {
        let countries = this.props.options;
        let optionItems = countries.map((country) =>
                <option key={country.id} value={country.id}>{country.name}</option>
            );

        return (
          <div className="form-group">
                        <label for={this.props.name}> {this.props.title} </label>
            <select
                      id = {this.props.name}
                      name={this.props.name}
                      value={this.props.value}
                      onChange={this.props.handleChange}
                      className="form-control">
                      <option value="" disabled>{this.props.placeholder}</option>
                      {optionItems}
            </select>
          </div>
        )
    }
}

export default Country;
2 голосов
/ 11 марта 2020

Если вы упростите свой state до простого объекта свойств со строковыми значениями и обновите эти значения после вызова API, тогда вы сможете очень легко установить значение по умолчанию для Dynami c (подробнее об этом читайте ниже) объяснение).

Рабочий пример :

Edit Initialize Select Option


контейнеры / FormContainer

import React, { Component } from "react";
import Input from "../../components/Input";
import Select from "../../components/Select";
import Button from "../../components/Button";
import { fakeAPI } from "../../api";
import { fields1, fields2, fields3 } from "./fields";

const buttonStyle = {
  margin: "10px 10px 10px 10px"
};

/* moving state outside of class for reuseability */
const initialState = {
  countries: [],
  providences: [],
  name: "",
  typeName: "",
  formattedAddress: "",
  localityName: "",
  postalCode: "",
  providence: "",
  country: 484,
  enabled: true,
  email: "",
  phone: "",
  website: ""
};

class FormContainer extends Component {
  constructor(props) {
    super(props);

    /* spreading initial state above with isLoading and err properties */
    this.state = { ...initialState, isLoading: true, err: "" };
    this.handleFormSubmit = this.handleFormSubmit.bind(this);
    this.handleClearForm = this.handleClearForm.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }

  /* This life cycle hook gets executed when the component mounts */
  componentDidMount() {
    this.fetchCountryData();
  }

  /* Get initial countries/providences */
  async fetchCountryData() {
    try {
      /* 
        since the two (countries and providences) are intertwined, 
        I'd recommend creating one API call and setting state once 
      */
      const res = await fakeAPI.getCountryData();
      const data = await res.json();

      // throw new Error("No data available!");

      this.setState({
        countries: data.countries,
        providences: data.providences,
        country: data.countries[0].name,
        providence: data.providences[0].name,
        isLoading: false,
        err: ""
      });

      /*
         const res = await fetch("/countries/");
         const data = await res.json();

         this.setState(...);
      */
    } catch (err) {
      /* catch any errors returned from API call */
      this.setState({ err: err.toString() });
    }
  }

  /* Handles form submissions */
  async handleFormSubmit(e) {
    e.preventDefault();

    /* build the JSON object here to send to the API */
    const newCoop = {
      name: this.state.name,
      type: {
        name: this.state.typeName
      },
      address: {
        formatted: this.state.formattedAddress,
        locality: {
          name: this.state.localityName,
          postal_code: this.state.postalCode,
          state: this.state.providence
        },
        country: this.state.country
      },
      enabled: true,
      email: this.state.email,
      phone: this.state.phone,
      web_site: this.state.website
    };

    /* 
        try {
          const res = await fetch("/coops/", {
            method: "POST",
            body: JSON.stringify(newCoop),
            headers: {
              Accept: "application/json",
              "Content-Type": "application/json"
            }
          });
          const data = await res.json();

          console.log(data);
        } catch (err) {
          console.error(err.toString());
          this.setState({ err });
        }
    */

    alert("Sent to API: " + JSON.stringify(newCoop, null, 4));
  }

  /* Clears the form while maintaining API data */
  handleClearForm() {
    this.setState(({ countries, providences }) => ({
      ...initialState,
      countries,
      country: countries[0].name, // sets a default selected value
      providence: providences[0].name, // sets a default selected value
      providences
    }));
  }

  /* Updates form state via "event.target.name" and "event.target.value" */
  handleChange({ target: { name, value } }) {
    this.setState({ [name]: value });
  }

  /* 
    Renders the view according to state: 
    - If there's an error: show the error,
    - Else if it's loading: show a loading indicator
    - Else show the form with API data
  */
  render() {
    return this.state.err ? (
      <p>{this.state.err}</p>
    ) : this.state.isLoading ? (
      <p>Loading...</p>
    ) : (
      <form
        className="container-fluid"
        style={{ padding: 20 }}
        onSubmit={this.handleFormSubmit}
      >
        {fields1.map(({ name, placeholder, title, type }) => (
          <Input
            key={name}
            type={type}
            title={title}
            name={name}
            value={this.state[name]}
            placeholder={placeholder}
            onChange={this.handleChange}
          />
        ))}
        {fields2.map(({ name, placeholder, title, options }) => (
          <Select
            key={name}
            title={title}
            name={name}
            options={this.state[options]}
            value={this.state[name]}
            placeholder={placeholder}
            onChange={this.handleChange}
          />
        ))}
        {fields3.map(({ name, placeholder, title, type }) => (
          <Input
            key={name}
            type={type}
            title={title}
            name={name}
            value={this.state[name]}
            placeholder={placeholder}
            onChange={this.handleChange}
          />
        ))}
        <Button
          buttonType="primary"
          type="submit"
          title="Submit"
          style={buttonStyle}
        />
        <Button
          onClick={this.handleClearForm}
          buttonType="secondary"
          type="button"
          title="Clear"
          style={buttonStyle}
        />
      </form>
    );
  }
}

export default FormContainer;

В вашем коде есть несколько вариантов анти-паттернов, которые могут затруднить выполнение того, что вы хотите. Короче говоря:

  • Не delete свойства объекта this.state, поскольку он нарушит React. React ожидает, что state и props будут неизменными (вы будете только поверхностно копировать / переопределять объект состояния, используя this.setState(); и обновлять реквизиты из родительского компонента более высокого порядка)
  • Поскольку вы Работая только с одной формой с несколькими полями, упростите ее, сохранив свойства состояния в виде простых строковых значений. При работе со свойствами вложенного объекта его сложнее поддерживать и обновлять - вам, по сути, придется найти родительское свойство, которое было изменено, затем выполнить итерацию по дочерним свойствам, чтобы найти измененное дочернее свойство, переопределить дочернее значение и затем перестроить вложенное родительское свойство снова и снова и снова. Вы можете избежать этого обхода, просто используя строки, а затем создавая объект структурированных полей JSON перед отправкой его в API.
  • Избегайте использования let, когда значение не изменяется в рамках одного и того же выполнения / рендеринга. Это распространенная ошибка, которая потенциально может привести к поломке React / вашего компонента. Вместо этого используйте const, поскольку он объявляет ее как переменную только для чтения, и попытка переопределить ее приведет к ошибке.

Распространенная ошибка (как видите, ни countries, ни optionItems не обновляются / перезаписываются после того, как были объявлены в том же цикле выполнения):

let options = this.props.options;
let optionItems = options.map((country) => <option key={country.id} value={country.id}>{country.name}</option>);

return ( ... );

Вместо (как и в функции, эти переменные переопределяются при каждом вызове функции, но ни одна из них не будет перезаписана в течение одного и того же периода выполнения):

const { options } = this.props; // same as: const options = this.props.options;
const optionItems = options.map((country) => <option key={country.id} value={country.id}>{country.name}</option>);

return ( ... );

Взяв первый пример выше, мы могли бы случайно переопределить options, когда мы не хотим, чтобы он был перезаписан:

let options = this.props.options;
options = "Hello world";
let optionItems = options.map((country) => <option key={country.id} value={country.id}>{country.name}</option>);

return ( ... );

Теперь, вместо рендеринга элемента select с опциями, мы сломали его, пытаясь отобразить строку. Это может показаться произвольным, но оно обеспечивает строгое соблюдение, чтобы избежать потенциальных ошибок в компоненте и его дочерних элементах.

  • Если компонент не использует state, то он может быть чистой функцией. В приведенном выше примере CodeSandbox мой компонент Select представляет собой простую функцию, которая получает один аргумент объекта (используя ES6 destruuring , мы можем извлечь свойства из объекта) и возвращает JSX.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...