如何为我的 React 表单组件中的选择菜单设置默认值?

Posted

技术标签:

【中文标题】如何为我的 React 表单组件中的选择菜单设置默认值?【英文标题】:How do I set a default value for a select menu in my React form component? 【发布时间】:2020-06-16 09:39:54 【问题描述】:

我正在使用 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;

【问题讨论】:

选择组件中是否有任何选项元素的 value 属性为 FormContainer.DEFAULT_COUNTRY? 否,但我在“静态”块中将“FormContainer.DEFAULT_COUNTRY”定义为“484”。 SELECT 菜单中有一个值为 484 的选项。 &lt;Country&gt; 到底是什么? @AlexWayne,这是我创建的表单组件。添加 src 作为对此问题的编辑。 可以给我们看一下FormContainer的完整代码吗? 【参考方案1】:

您的代码存在一个小问题,您没有在构造函数中传递 props,而在 Country 组件中没有传递 super。您必须将props 传递给constructorsuper,或者只需删除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】:

如果您将state 简化为具有字符串值的简单属性对象,并在 API 调用后更新这些值,那么您可以非常轻松地设置动态默认值(阅读下文以获得更深入的说明)。

工作示例


容器/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;

您的代码中有一些反模式选择可能会更难做您想做的事。简而言之:

不要来自this.state 对象的delete 属性,因为它会破坏React。 React 期望 stateprops 是不可变的(您只会使用 this.setState(); 浅复制/覆盖状态对象并从父高阶组件更新道具) 由于您只处理一个包含多个字段的表单,因此可以通过将状态属性保持为简单的字符串值来简化它。使用嵌套对象属性时,维护和更新变得更加困难——您基本上必须找到已更改的父属性,然后遍历子属性以找到更改的子属性,覆盖子值,然后一遍又一遍地重建嵌套的父属性。您可以通过简单地使用字符串来避免这种遍历,然后在将其发送到 API 之前构建结构化字段 JSON 对象。 当值在同一个执行/渲染中没有改变时,避免使用let。这是一个可能导致破坏 React/你的组件的常见错误。相反,请使用const,因为它将其声明为只读变量并尝试覆盖它会引发错误。

常见错误(如您所见,countriesoptionItems 在同一执行周期内声明后都不会更新/覆盖):

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 destructuring 我们可以从对象中提取属性)并返回 JSX。

【讨论】:

以上是关于如何为我的 React 表单组件中的选择菜单设置默认值?的主要内容,如果未能解决你的问题,请参考以下文章

在定义自定义选项组件时,如何为react-select应用默认样式?

如何为多个下拉菜单设置 jquery select2 的选定值?

如何为 React TypeScript 组件上的 props 设置默认值?

如何为 React 类(ES6)设置显示名称?

如何为 create-react-native-app 生成 apk 文件(使用 EXPO.IO 组件)

Angular2 Reactive forms - 使用下拉菜单设置表单字段的默认值