I'm using React 16.13.0. I'm trying to set a default value for an select menu in my form component. I have this ...
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;
But the country value is not getting set. Any ideas?
Edit: Adding Country.jsx component code
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;
There is a small issue with your code, you are not passing props in constructor and the super in the Country
component. You have to pass the props
to the constructor
and super
or just remove the constructor
as you arenot doing anything in the constructor
.
Try this.
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;
If you simplify your state
to a simple object of properties with string values and update those values after the API call, then you can set a dynamic default value very easily (read below for a more in-depth explanation).
Working example:
containers/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;
There's a few anti-pattern choices in your code that may it more difficult to do what you want. To be brief:
delete
properties from the this.state
object as it will break React. React expects the state
and props
to be immutable (you'll only be shallow copying/overriding the state object using this.setState();
and update props from a parent higher order component)let
when the value doesn't change within the same execution/render. This a common mistake that could potentially lead to breaking React/your component. Instead, use const
as it declares it as a read-only variable and attempting to override it will throw an error. Common mistake (as you can see, neither countries
nor optionItems
is updated/overwritten after it's been declared within the same execution cycle):
let options = this.props.options;
let optionItems = options.map((country) => <option key={country.id} value={country.id}>{country.name}</option>);
return ( ... );
Instead (just like in a function, these variables are redefined each time the function is called, but neither will be overwritten within the same execution period):
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 ( ... );
Taking the first example above, we could accidentally override options
, when we don't want it to be overwritten:
let options = this.props.options;
options = "Hello world";
let optionItems = options.map((country) => <option key={country.id} value={country.id}>{country.name}</option>);
return ( ... );
Now, instead of rendering a select
element with options, we broken it by trying to map over a string. This may seem arbitrary, but it ensures a strict compliance to avoid potential bugs within the component and its children.
state
, then it can be a pure function. In the CodeSandbox example above, my Select
component is a simple function that receives a single object argument (using ES6 destructuring we can pull out the properties from the object) and returns JSX. If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With