Если вы упростите свой state
до простого объекта свойств со строковыми значениями и обновите эти значения после вызова API, тогда вы сможете очень легко установить значение по умолчанию для Dynami c (подробнее об этом читайте ниже) объяснение).
Рабочий пример :
![Edit Initialize Select Option](https://codesandbox.io/static/img/play-codesandbox.svg)
контейнеры / 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.