【问题标题】:How do I set a default value for a select menu in my React form component?如何为我的 React 表单组件中的选择菜单设置默认值?
【发布时间】: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的完整代码吗?

标签: reactjs forms select components


【解决方案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。

    【讨论】:

      猜你喜欢
      • 2017-04-28
      • 2016-07-07
      • 2019-11-24
      • 2018-12-05
      • 1970-01-01
      • 2011-09-11
      • 2011-08-17
      • 1970-01-01
      • 2021-11-24
      相关资源
      最近更新 更多