如何为我的 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 的选项。<Country>
到底是什么?
@AlexWayne,这是我创建的表单组件。添加 src 作为对此问题的编辑。
可以给我们看一下FormContainer的完整代码吗?
【参考方案1】:
您的代码存在一个小问题,您没有在构造函数中传递 props,而在 Country
组件中没有传递 super。您必须将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】:如果您将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 期望 state
和 props
是不可变的(您只会使用 this.setState();
浅复制/覆盖状态对象并从父高阶组件更新道具)
由于您只处理一个包含多个字段的表单,因此可以通过将状态属性保持为简单的字符串值来简化它。使用嵌套对象属性时,维护和更新变得更加困难——您基本上必须找到已更改的父属性,然后遍历子属性以找到更改的子属性,覆盖子值,然后一遍又一遍地重建嵌套的父属性。您可以通过简单地使用字符串来避免这种遍历,然后在将其发送到 API 之前构建结构化字段 JSON 对象。
当值在同一个执行/渲染中没有改变时,避免使用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 destructuring 我们可以从对象中提取属性)并返回 JSX。
【讨论】:
以上是关于如何为我的 React 表单组件中的选择菜单设置默认值?的主要内容,如果未能解决你的问题,请参考以下文章
在定义自定义选项组件时,如何为react-select应用默认样式?
如何为多个下拉菜单设置 jquery select2 的选定值?
如何为 React TypeScript 组件上的 props 设置默认值?