🚨 CODE DUPLICATION 🚨
So I am trying to have an inital state object like: {prop1: val1, prop2: val2} and get seperate values from two <selection> fields.
I am having problems with getting each event.target.value on submit.
The only solution I came up with is splitting the original state into different objects, duplicate the handler functions and merge them on submit. But as you can see this is getting ugly pretty quick, I am sure that this could be optimized but I am getting nowhere with the unique event handlers.
const [firstSelection, setFirstSelection] = useState({color: 'green'})
const [secondSelection, setSecondSelection] = useState({time: 'evening'})
const handleFirstChange = e => setFirstSelection({ color: e.target.value })
const handleSecondChange = e => setSecondSelection({ time: e.target.value })
const getSelection = e => {
e.preventDefault()
console.log({...firstSelection, ...secondSelection})
}
const Form = () => {
return (
<form>
<div className='selectWrapper'>
<select name='color' value={firstSelection.color} onChange={handleFirstChange}>
<option value='red'>Red</option>
<option value='blue'>Blue</option>
<option value='yellow'>Yellow</option>
<option value='green'>Green</option>
</select>
</div>
<div className='selectWrapper'>
<select name='time' value={secondSelection.time} onChange={handleSecondChange}>
<option value='morning'>Morning</option>
<option value='evening'>Evening</option>
<option value='night'>Night</option>
</select>
</div>
<input type='submit' value='Submit' onClick={getSelection}/>
</form>
)
}
Try this :
export default function Form() {
const [selection, setSelection] = useState({ color: "red", time: "morning" });
const handleChange = (e) => {
setSelection({ ...selection, [e.target.name]: e.target.value });
};
const getSelection = (e) => {
e.preventDefault();
console.log(selection);
};
return (
<form>
<div className="selectWrapper">
<select
name="color"
value={selection.color}
onChange={(e) => handleChange(e)}
>
<option value="red">Red</option>
<option value="blue">Blue</option>
<option value="yellow">Yellow</option>
<option value="green">Green</option>
</select>
</div>
<div className="selectWrapper">
<select
name="time"
value={selection.time}
onChange={(e) => handleChange(e)}
>
<option value="morning">Morning</option>
<option value="evening">Evening</option>
<option value="night">Night</option>
</select>
</div>
<input type="submit" value="Submit" onClick={getSelection} />
</form>
);
}
You can try to create just one fuction to update state that use the names of the <select>s tags as keys of the state object. And concentrate all the data into one state. I will call this single state as formState
const [formState, setFormState] = useState({color: 'green', time: 'evening'})
const handleInput = e => setFormState({
...formState,
[e.target.name]: e.target.value
})
const getSelection = e => {
e.preventDefault()
console.log(formState)
}
const Form = () => {
return (
<form>
<div className='selectWrapper'>
<select name='color' value={firstSelection.color} onChange={handleInput}>
<option value='red'>Red</option>
<option value='blue'>Blue</option>
<option value='yellow'>Yellow</option>
<option value='green'>Green</option>
</select>
</div>
<div className='selectWrapper'>
<select name='time' value={secondSelection.time} onChange={handleInput}>
<option value='morning'>Morning</option>
<option value='evening'>Evening</option>
<option value='night'>Night</option>
</select>
</div>
<input type='submit' value='Submit' onClick={getSelection}/>
</form>
)
}
You could create a reusable component for select and use object for selection value, retrieved by input name
const CustomSelect = ({ name, options, value, onChange }) => {
return (
<div className="selectWrapper">
<select name={name} value={value} onChange={onChange}>
{options.map((option) => (
<option value={option.value}>{option.label}</option>
))}
</select>
</div>
);
};
export default function Form() {
const selectData = [
{
name: "color",
options: [
{ label: "Red", value: "red" },
{ label: "Blue", value: "blue" },
{ label: "Yellow", value: "yellow" },
{ label: "Green", value: "green" }
]
},
{
name: "time",
options: [
{ label: "Morning", value: "morning" },
{ label: "Evening", value: "evening" },
{ label: "Night", value: "night" }
]
}
];
const [selection, setSelection] = useState({
color: "green",
time: "evening"
});
const getSelection = (e) => {
e.preventDefault();
console.log(selection);
};
return (
<div className="App">
<form>
{selectData.map((sd) => (
<CustomSelect
name={sd.name}
options={sd.options}
value={selection[sd.name]}
onChange={(event) =>
setSelection({
...selection,
[event.target.name]: event.target.value
})
}
/>
))}
<input type="submit" value="Submit" onClick={getSelection} />
</form>
</div>
);
}
Codesandbox demo
Related
This form works correctly as a regular form - submitting retains the dropdown values. When I use RHF's reset() to load data from the URL parameters, The input boxes show the default values but the select fields do not reflect the default values.
The functionality I'm going for here, is to load the values from the URL querystring params, which look like this:
http://localhost:3000/search?search=dev&location=&environment=On-Site&job_type=Internship&industry=&industry_category=&date_posted=&offset=0&count=25
...effectively making it "stateless", so we're able to pass this URL to anyone and it loads the form values, performs the search, etc.
import React, { useContext, useEffect, useState, useRef } from "react";
import { useForm, Controller } from "react-hook-form";
import InputMask from "react-input-mask";
import useLookupService from "../../service/lookup_service";
const Search = props => {
const { onSearch } = props;
const lookupSvc = useLookupService();
const [searchFormDefaults, setSearchFormDefaults] = useState(null);
const [locationOptions, setLocationOptions] = useState([]);
const [jobTypeOptions, setJobTypeOptions] = useState([]);
const [workEnvironmentOptions, setWorkEnvironmentOptions] = useState([]);
const [industryOptions, setIndustryOptions] = useState([]);
const [industryCategoryOptions, setIndustryCategoryOptions] = useState([]);
const {
register,
setValue,
handleSubmit,
control,
reset,
formState: { errors }
} = useForm({
defaultValues: searchFormDefaults,
context: JobSearchForm.type,
resolver: validator.validateResolver
});
useEffect(() => {
loadData();
}, []);
useEffect(() => {
if (!window.location.search) {
return;
}
const queryParams = new URLSearchParams(window.location.search);
let defaults = Object.fromEntries(queryParams);
setSearchFormDefaults(defaults);
reset(defaults);
}, [reset]);
const loadData = async () => {
let [
jobTypesData,
workEnvironmentsData,
industriesData,
industryCategoriesData
] = await Promise.all([
lookupSvc.getJobTypes(),
lookupSvc.getWorkEnvironments(),
lookupSvc.getIndustries(),
lookupSvc.getIndustryCategories()
]);
setJobTypeOptions([
<option key={-1} value="">Please select...</option>,
jobTypesData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
setWorkEnvironmentOptions([
<option key={-1} value="">Please select...</option>,
workEnvironmentsData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
setIndustryOptions([
<option key={-1} value="">Please select...</option>,
industriesData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
setIndustryCategoryOptions([
<option key={-1} value="">Please select...</option>,
industryCategoriesData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
};
return (
<div>
<div>
<div>
Filters
</div>
<div>
<a onClick={onClearClick}>
Clear Filters
</a>
</div>
</div>
<form onSubmit={handleSubmit(onSearch)} autoComplete="off">
<div>
<div>
<div>
<label>Search</label>
</div>
<input type="text" {...register("search")} />
{errors.search && <span className="validation">{errors.search}</span>}
</div>
<div>
<div>
<label>Location</label>
</div>
<input type="text" {...register("location")} onChange={onLocationChange} />
{errors.location && <span className="validation">{errors.location}</span>}
</div>
<div>
<div>
<label>Work Environment</label>
</div>
<select {...register("environment")}>
{workEnvironmentOptions}
</select>
</div>
<div>
<div>
<label>Date Posted</label>
</div>
<Controller
control={control}
name="date_posted"
render={({ field }) => (
<InputMask {...field} mask="99/99/9999" />
)}
/>
{errors.date_posted && <span className="validation">{errors.date_posted}</span>}
</div>
<div>
<div>
<label>Job Type</label>
</div>
<select {...register("job_type")}>
{jobTypeOptions}
</select>
</div>
<div>
<div>
<label>Job Industry</label>
</div>
<select {...register("industry")}>
{industryOptions}
</select>
</div>
<div>
<div>
<label>Industry Category</label>
</div>
<select {...register("industry_category")}>
{industryCategoryOptions}
</select>
</div>
<button type="submit">
Search
</button>
</div>
</form>
</div>
);
};
export default Search;
Note that the selected values and display values are set to the same - a string such as "On-Site".
While debugging, the first useEffect() is firing and loading the data before the second one, with reset as its param. I thought perhaps the lists were being reloaded and wiping out the selected value, but I don't see evidence of that.
When setting a breakpoint on the return line of the component, searchFormDefaults contains the expected values, yet they're always set to the default value on the screen (the initial "Please select..." when they're created.)
This is what I see for searchFormDefaults on the final render:
{
"search": "dev",
"location": "",
"environment": "On-Site",
"job_type": "Internship",
"industry": "",
"industry_category": "",
"date_posted": "",
"offset": "0",
"count": "25"
}
I'm fairly new to react-hook-form so I'm sure I've done something goofy, but I'm unable to spot it or find an explanation anywhere.
Figured it out. Was just an async issue that a simple flag solved.
const [isDataLoaded, setIsDataLoaded] = useState(false);
...then in my data loading function:
const loadData = async () => {
let [
jobTypesData,
workEnvironmentsData,
industriesData,
industryCategoriesData
] = await Promise.all([
lookupSvc.getJobTypes(),
lookupSvc.getWorkEnvironments(),
lookupSvc.getIndustries(),
lookupSvc.getIndustryCategories()
]);
setJobTypeOptions([
<option key={-1} value="">Please select...</option>,
jobTypesData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
setWorkEnvironmentOptions([
<option key={-1} value="">Please select...</option>,
workEnvironmentsData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
setIndustryOptions([
<option key={-1} value="">Please select...</option>,
industriesData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
setIndustryCategoryOptions([
<option key={-1} value="">Please select...</option>,
industryCategoriesData.map((x, i) => <option key={i} value={x.name}>{x.name}</option>)
]);
setIsDataLoaded(true);
};
...and the hook that needs it:
useEffect(() => {
if (!isDataLoaded) {
return;
}
if (!window.location.search) {
return;
}
const queryParams = new URLSearchParams(window.location.search);
let defaults = Object.fromEntries(queryParams);
setSearchFormDefaults(defaults);
reset(defaults);
}, [reset, isDataLoaded]);
I need to have this feature, when the cancel button is clicked, all the previous inputs shall be cleared. I mean, if the value in the textfield or scroll-bar should be reset to default. This is how I got now, the value still exists even when I click the cancel button (<button type="delete" onClick={deleteFilter} >X</button>) although the filter is disabled:
const [stateSelected, setStateSelected] = useState('')
const [jobType, setJobType] = useState('');
const [type, setType] = useState('');
var states = statepicker.getStates('us');
document.body.style = 'position:absolute;';
const stateChange = e => {
setStateSelected(e.target.value)
}
const jobChange = e => {
setJobType(e.target.value)
}
const typeChange = e => {
setType(e.target.value)
}
function filterOn(){
if(filter.position != "" || filter.state != "" || filter.type != ""){
return true;
} else{
return false;
}
}
function deleteFilter(){
setFilter({
position: "",
state: "",
type: ""
})
}
const handleSignUp = useCallback(async event => {
event.preventDefault();
setFilter({
position: jobType,
state: stateSelected,
type: type
});
setFilterOpen(false);
setJob([]);
})
return (
<div>
<div className="settings__Section">
<div className="settings__SectionHeader">
Filter
</div>
<form onSubmit={handleSignUp}>
<input className="settings__inputBox" name="occupation" type="occupation" placeholder="Job Title" onChange={jobChange}/>
<select className="settings__inputSelect" onChange={stateChange} value={stateSelected}>
<option value={""} disabled={true}>Select State</option>
{
states.map(state => (
<option value={state} name="state">{state}</option>
))
}
</select>
<select className="settings__inputSelect" onChange={typeChange} value={type}>
<option value={""} disabled={true}>Select Job Type</option>
<option value={"Entry Level"} name="type">Entry Level</option>
<option value={"Internship"} name="type">Internship</option>
<option value={"Fellowship"} name="type">Fellowship</option>
<option value={"Apprenticeship"} name="type">Apprenticeship</option>
</select>
<button type="submit" className="settings__signBtn">Save Filter</button>
</form>
{filterOn() &&
<div>
<p>Position: {filter.position} Location: {filter.state} Job Type: {filter.type}</p>
<button type="delete" onClick={deleteFilter} >X</button>
</div>
}
</div>
</div>
);
<form onSubmit={handleSignUp}>
<input className="settings__inputBox" name="occupation" type="occupation" placeholder="Job Title" onChange={jobChange}/>
<select className="settings__inputSelect" onChange={stateChange} value={stateSelected}>
<option value={""} disabled={true}>Select State</option>
{
states.map(state => (
<option value={state} name="state">{state}</option>
))
}
</select>
<button type="submit" className="settings__signBtn">Save Filter</button>
<button type="reset" >X</button>
</form>
Try with reset button it does the thing here I suppose
Ok, although there is some lines missing in your code i think i know what the problem is.
The filter renders if either position, state, or type is different from empty string.
But when you delete the filter you are only resetting position and state:
function deleteFilter(){
setFilter({
position: "",
state: ""
})
}
So to fix it, just add type to this state function being called:
function deleteFilter () {
setFilter({
position: '',
state: '',
type: ''
})
}
I have a form with dynamic selectBoxes. Depending on the result of the first one, I call a function whose result will go into the second selectBox.
I found solutions:
Apparently onClick is not working on the option tag, so I put my function in the onChange of the select tag.
Or
I used useEffect to call the function when the variable changes.
These solutions above work, but my problem is that it works once, sometimes twice but not more. I have to reload the page and it doesn't work for all of that.
If anyone would like to dive into my code and tell me why it doesn't work every time.
Thank you.
the variables componentOriginName, componentTargetName and the functions getComponentOriginNames, getComponentTargetNames come from redux.
const [relationType, setRelationType] = useState({
type: props.type,
});
const [newRelation, setNewRelation] = useState({
componentOriginType: "",
componentOriginName: "",
componentTargetType: "",
componentTargetName: "",
data: "",
});
the Form :
<div className='container--info--tuple'>
<div className='group--input'>
<select
name='componentOriginType'
onChange={handleChange}
value={newRelation.componentOriginType}
required>
<option value=''></option>
{props.components &&
props.components.map((component, i) => {
return (
<option key={i} value={component.name}>
{component.name}
</option>
);
})}
</select>
<span className='floating--label--select required'>
Composant origine
</span>
</div>
<div className='group--input'>
<select
name='componentOriginName'
onChange={handleChange}
value={newRelation.componentOriginName}
required>
<option value=''></option>
{props.componentOriginName &&
props.componentOriginName.map((component, i) => {
return (
<option key={i} value={component.name}>
{component.name}
</option>
);
})}
</select>
<span className='floating--label--select required'>
Nom du composant d'origine
</span>
</div>
</div>
<div className='container--info--tuple'>
<div className='group--input'>
<select
name='type'
onChange={handleChangeType}
value={relationType.type}
required>
<option value=''></option>
{props.relations.map((relation, i) => {
return (
<option key={i} value={relation.name}>
{relation.name}
</option>
);
})}
</select>
<span className='floating--label--select required'>Relation</span>
</div>
</div>
<div className='container--info--tuple'>
<div className='group--input'>
<select
name='componentTargetType'
onChange={handleChange}
value={newRelation.componentTargetType}
required>
<option value=''></option>
{props.components &&
props.components.map((component, i) => {
return (
<option key={i} value={component.name}>
{component.name}
</option>
);
})}
</select>
<span className='floating--label--select required'>
Composant cible
</span>
</div>
<div className='group--input'>
<select
name='componentTargetName'
onChange={handleChange}
value={newRelation.componentTargetName}
required>
<option value=''></option>
{props.componentTargetName &&
props.componentTargetName.map((component, i) => {
return (
<option key={i} value={component.name}>
{component.name}
</option>
);
})}
</select>
<span className='floating--label--select required'>
Nom du composant cible
</span>
</div>
</div>
and the functions with my differents tests commented :
const handleChange = (event) => {
setNewRelation({
...newRelation,
[event.target.name]: event.target.value,
});
if (event.target.name === "componentOriginType") {
propsGetComponentOriginNames(event.target.value);
} else if (event.target.name === "componentTargetType") {
propsGetComponentTargetNames(event.target.value);
}
};
// =======================================================================
// const test = (type) => {
// props.getComponentOriginNames(type);
// };
// const handleChangeOrigin = (event) => {
// test(event.target.value);
// setNewRelation({
// ...newRelation,
// [event.target.name]: event.target.value,
// });
// };
// const handleChangeTarget = (event) => {
// props.getComponentTargetNames(event.target.value);
// setNewRelation({
// ...newRelation,
// [event.target.name]: event.target.value,
// });
// };
// =======================================================================
const propsGetComponentOriginNames = props.getComponentOriginNames;
const propsGetComponentTargetNames = props.getComponentTargetNames;
// useEffect(() => {
// propsGetComponentOriginNames(newRelation.componentOriginType);
// }, [
// propsGetComponentOriginNames,
// newRelation.componentOriginType,
// relationType.type,
// ]);
// useEffect(() => {
// propsGetComponentTargetNames(newRelation.componentTargetType);
// }, [
// newRelation.componentTargetType,
// propsGetComponentTargetNames,
// relationType.type,
// ]);
// =======================================================================
So I have a React component like:
export default function ExampleComponent() {
return (
<div>
<select required name="select1">
<option label=" "></option>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
<select name="select2">
<option label=" "></option>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
</div>
);
}
Continuing on for multiple more selects...
I want it so that when I select one option in any of the select tags, it gets removed as an option in all of the others. If I change the selected option, it then becomes available again in all of the others. Does anyone know how I would be able to do this in React?
There may be a more elegant way to do this, but my thought was to keep track of which select have chosen which option with a object called chosenObjects then filtering results based on that information.
import React, {useState} from "react";
export default function ExampleComponent() {
const selectNames = ["select1", "select2"];
const [options] = useState([
{
label: 'Option 1',
value: 'option1'
},
{
label: 'Option 2',
value: 'option2'
},
{
label: 'Option 3',
value: 'option3'
}
]);
const [chosenOptions, setChosenOptions] = useState({});
const isChosenByOther = (optionValue, selectName) => {
for (let key in chosenOptions) {
if (key !== selectName) {
if (chosenOptions[key] === optionValue) {
return true;
}
}
}
return false;
};
const handleChange = (ev) => {
setChosenOptions({...chosenOptions, [ev.target.name]: ev.target.value});
};
return (
<div>
{selectNames.map((name, index) => {
return (
<select name={name} key={index} onChange={handleChange} value={chosenOptions[name] || ''}
required={index === 0}>
<option value=''/>
{options.filter(({value}) => !isChosenByOther(value, name))
.map(({label, value}, oIndex) =>
<option value={value} key={oIndex}>{label}</option>)
}
</select>
)
})}
</div>
);
}
You can also conditionally disable your empty string valued option after something is chosen:
<div>
{selectNames.map((name, index) => {
return (
<select name={name} key={index} onChange={handleChange} value={chosenOptions[name] || ''}
required={index === 0}>
<option value='' disabled={chosenOptions[name]}>Choose Option</option>
{options.filter(({value}) => !isChosenByOther(value, name))
.map(({label, value}, oIndex) =>
<option value={value} key={oIndex}>{label}</option>)
}
</select>
)
})}
</div>
You have to use state to manage the value and share it between two selects.
import React, {useState} from 'react'
export default function ExampleComponent() {
const [value, setValue] = useState('option0')
handleChange = (e) => {
setValue(e.target.value)
}
return (
<div>
<select value={value} onChange={handleChange}>
<option value="option0"></option>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
<select value={value} onChange={handleChange}>
<option value="option0"></option>
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
</select>
</div>
);
}
Why don't you use state? You can use same datasource for options and also same event handlers for all selects.
export default function ExampleComponent() {
state = {
options: [
{
name: 'Option 1',
value: 'option1'
},
{
name: 'Option 2',
value: 'option2'
},
{
name: 'Option 3',
value: 'option3'
}
]
};
onChangeSelect = event => {
// do something
this.setState({
options: this.state.options.filter(
option => option.value !== event.target.value
)
});
};
return (
<div>
<select required name="select1" onChange={onChangeSelect}>
<option label=" "></option>
{this.state.options.map(option => (
<option value={option.value}>{option.name}</option>
))}
</select>
<select name="select2" onChange={onChangeSelect}>
<option label=" "></option>
{this.state.options.map(option => (
<option value={option.value}>{option.name}</option>
))}
</select>
</div>
);
}
You need to track the state of multiple Select elements, and then conditionally render the options for each one.
I'll use some bits from the other answers and show how to make it work. Edited to keep the options in the same order.
import React, {useState} from "react";
function ExampleComponent() {
const [myState, setMyState] = useState({});
const options = [
{
name: 'Option 1',
value: 'option1'
},
{
name: 'Option 2',
value: 'option2'
},
{
name: 'Option 3',
value: 'option3'
}
]
// list of select names
const selectNames = [
"select1",
"select2"
]
handleChange = (e) => {
// update the value for the given select
myState[e.target.name] = e.target.value
setMyState(myState)
}
const inUseValues = Object.values(myState);
console.log(inUseValues)
// get the options for given name
getOptions = (name) =>
options.filter(option => !inUseValues.includes(option.value) || myState[name]==option.value)
.map(option => (
<option key={option.value} value={option.value}>{option.name}</option>
))
// render all select controls
const selectRender = selectNames.map(name => (
<select key={name} required name="{name}" value={myState[name]} onChange={handleChange}>
<option label=" "></option>
{getOptions(name)}
</select>
))
return (
<div>
{selectRender}
</div>
)
}
I have JSON file like this
[
{
"id": 1,
"country": "Afghanistan",
"city": ["Eshkashem","Fayzabad","Jurm","Khandud"]
},
{
"id": 2,
"country": "Italy",
"city": ["Milano","Rome","Torino","Venezia"]
}
]
and I want to iterate through array placed in the city. Idea is to have two selects, where the first select is reserved for countries and the second is reserved for cities. Whenever the user selects a country, I want to populate the second select with a list of cities. Problem is that I receive only one array of all cities for that country. Here is my code:
export default class DiffCountries extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
contacts: [],
selectedCountry: [],
selectedCity: []
}
}
onChangeHandler = (event) => {
const test = CountriesData[event.target.value - 1];
this.setState({
selectedCountry: test,
selectedCity: this.state.selectedCountry.city
})
console.log(this.state.selectedCity);
}
render() {
const { contacts } = this.state;
return (
<div>
<select name="" id="" onChange={this.onChangeHandler}>
{CountriesData.map(item => {
const { id, country } = item;
return <option key={id} value={id}>{country}</option>
})}
</select>
<select name="" id="">
{this.state.selectedCountry !== undefined ?
<option value="">{this.state.selectedCountry.city}</option> :
null
}
</select>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
And here is the screenshot of my problem
Thank you in advance!
You need to use map() on the city array.
<select name = "" id = "" > {
this.state.selectedCountry !== undefined ?
this.state.selectedCountry.city.map((x,i) => <option value={x} key={i}>{x}</option>)
:null
}
</select>
You need to iterate through the array.
this.state.selectedCountry.city.map((city, index) => {
return <option value={city} key={index}>{city}</option>
})
Be aware, that using the index as a key is considered an anti pattern. You could use the name of the city as a key as well. E.g.:
this.state.selectedCountry.city.map(city => {
return <option value={city} key={city}>{city}</option>
})
edit to add link to mdn docs as suggested in comments: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Example:
const CountriesData = [
{
id: 1,
country: 'Afghanistan',
city: ['Eshkashem', 'Fayzabad', 'Jurm', 'Khandud'],
},
{
id: 2,
country: 'Italy',
city: ['Milano', 'Rome', 'Torino', 'Venezia'],
},
];
class DiffCountries extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedCountry: null,
};
}
onChangeHandler = event => {
const selectedCountry = CountriesData[event.target.value - 1];
this.setState({
selectedCountry,
});
};
render() {
const { selectedCountry } = this.state;
return (
<div>
<select
name="country"
defaultValue="country"
onChange={this.onChangeHandler}
>
<option disabled value="country">
Select country
</option>
{CountriesData.map(({ id, country }) => (
<option key={id} value={id}>
{country}
</option>
))}
</select>
{selectedCountry && (
<select name="city" defaultValue="city">
<option disabled value="city">
Select city
</option>
{selectedCountry.city.map(item => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
)}
</div>
);
}
}
ReactDOM.render(<DiffCountries />, document.getElementById('container'));