Update a select's options based on another dropdown, using React - javascript

I've got 2 selects in my form. Options for the 2nd select are generated based on 1st select. Everything works fine except that every time I choose an option for the 2nd select and then change the 1st one, I should get the default option for the 2nd, but it's not resetting.
Code below
const [animalClass, setAnimalClass] = useState();
const [animalType, setAnimalType] = useState();
const [isLoadingClasses, setLoadingClasses] = useState(true);
const [isLoadingTypes, setLoadingTypes] = useState(true);
const [availableAnimalClasses, setAvailableAnimalClasses] = useState();
const [availableAnimalTypes, setAvailableAnimalTypes] = useState();
useEffect(() => {
setLoadingClasses(true);
const availableOptions = async () => {
const availableClasses = await Axios.get();
console.log(availableClasses.data);
if (availableClasses.data.length > 0) {
setAvailableAnimalClasses(availableClasses.data.map(animal => ({name: animal.name})));
setLoadingClasses(false);
}
};
availableOptions();
}, []);
useEffect(() => {
setLoadingTypes(true);
setAnimalType("DEFAULT");
const availableOptions = async () => {
const availableTypes = await Axios.get();
console.log(availableTypes.data);
if(availableTypes.data.length > 0) {
setAvailableAnimalTypes(availableTypes.data.map(animal => ({name: animal.name})));
setLoadingTypes(false);
}
};
availableOptions();
}, [animalClass]);
return (
<div className="page">
<h2>New Auction</h2>
<form className="form" onSubmit={onSubmit}>
<label>
Animal Class
<select defaultValue={'DEFAULT'} onChange={(e) => setAnimalClass(e.target.value)} >
<option value="DEFAULT" disabled>Choose animal class</option>
{isLoadingClasses ? <option value="Loading" disabled>Loading.....</option> : availableAnimalClasses.map((option) => (
<option value={option.name}>{upperCase(option.name)}</option>
))}
</select>
</label>
<label>Animal Type
<select defaultValue={'DEFAULT'} onChange={(e) => setAnimalType(e.target.value)} >
<option value="DEFAULT" disabled>Choose animal type</option>
{isLoadingTypes? <option value="Loading" disabled>Loading.....</option> : availableAnimalTypes.map((option) => (
<option value={option.name}>{upperCase(option.name)}</option>
))}
</select>

Since you are using state to store your form field values, you could set your defaults when you create the hooks:
const [animalClass, setAnimalClass] = useState('DEFAULT');
const [animalType, setAnimalType] = useState('DEFAULT');
and then use value instead of defaultValue in your JSX:
<select value={animalClass} onChange={(e) => setAnimalClass(e.target.value)} >
...
<select value={animalType} onChange={(e) => setAnimalType(e.target.value)} >
These values should reset your select elements when the state changes.

You should call setAnimalType to reset it back to the default when selecting the animal class. Change:
<select defaultValue={'DEFAULT'} onChange={(e) => setAnimalClass(e.target.value)}>...</select>
...to:
<label>
Animal Class
<select
defaultValue={'DEFAULT'}
value={animalClass}
onChange={(e) => {
setAnimalClass(e.target.value);
setAnimalType('DEFAULT');
}}
>
...
</select>
</label>
<label>
Animal Type
<select
defaultValue={'DEFAULT'}
value={animalType}
onChange={(e) => setAnimalType(e.target.value)}
>
// ...
</select>
</label>
This is necessary because the onChange event will not be automatically fired for a select when the options change. In order for this to work, where state controls the state of your <select/>s, you'll also want to change the defaultValueprops tovalue` props.

Related

How to wait for options of a select element

I want to select the option in the select element after all the options are drawn using a map method. I don't want to use setTimeout to wait for a while. I want to know after all the options are rendered.
The select is in a pop up modal which is shown after a button is clicked.
<select
ref={classSelRef}
className="form-select mr-sm-2"
id="class"
required
onChange={e => setClassId(e.target.value)}>
{classes.map(c => {
return (
<option key={c.id} value={c.id}>
{c.class_name}
</option>
)
})}
</select>
And here is the option selection method.
const selectOption = (ele, value) => {
return [...ele.options].some((option, index) => {
if (option.value === value) {
ele.selectedIndex = index
return true
}
})
}
You can add logical operator && before map function like so
<select
ref={classSelRef}
className="form-select mr-sm-2"
id="class"
required
onChange={e => setClassId(e.target.value)}>
{classes && classes.map(c => {
return (
<option key={c.id} value={c.id}>
{c.class_name}
</option>
)
})}
This will wait classes to be true before map is executed

"TypeError: Cannot read properties of undefined (reading 'id')"

I'm building the website but I got error in the browser's console, what's the problem and how to fix it. And another promblem is I can not get value from first and second select but input is fine.
Here's the code.
const Refund = () => {
const [payment, setPayment] = useState({
rpayment: ""
});
const [reason, setReason] = useState({
rrefund: ""
});
const [date, setDate] = useState({
ddate: ""
});
const handleInputOnChange = ({ currentTarget: select, input}) => {
const tempPayment = {...payment};
tempPayment[select.id] = select.value;
setPayment(tempPayment);
console.log(tempPayment)
const tempReason = {...reason};
tempReason[select.id] = select.value;
setReason(tempReason);
console.log(tempReason)
const tempDate = {...date};
tempDate[input.id] = input.value;
setDate(tempDate);
console.log(tempDate)
};
return (
<div className="refund">
<fieldset className="refund__container">
<legend align="center">
</legend>
<form className="refund__form">
<div className="refund__form__container">
<div className="refund__form__item" >
<select name="rpayment" id="rpayment" value={payment["rpayment"]} onChange={handleInputOnChange}>
<option value="none" selected disabled hidden>ช่องทางการรับเงิน</option>
<option value={payment["rpayment"]}>Credit Card</option>
<option value={payment["rpayment"]}>Paypal</option>
<option value={payment["rpayment"]}>Wallet</option>
</select>
</div>
<div className="refund__form__item">
<input
type="date"
id="rdate"
name="rdate"
value={reason["ddate"]}
onChange={handleInputOnChange}
/>
</div>
<div className="refund__form__item">
<select name="reason" id="reason" value={reason["rrefund"]} onChange={handleInputOnChange}>
<option value="none" selected disabled hidden>เหตุผลที่ต้องการเงินคืน</option>
<option value={reason["rrefund"]}>I need to cancle</option>
<option value={reason["rrefund"]}>It's not comfortable</option>
<option value={reason["rrefund"]}>Too expensive</option>
</select>
</div>
</div>
</form>
<input type="submit" className="refund__btn" value="Submit" />
</fieldset>
</div>
)
}
export default Refund;
Thank you in advance!!
There's such a huge amount of problems that I don't even know which one to start from.
Well let's start with state. One state is enough.
const [formState, setFormState] = useState({
rpayment: "none", // refers to paymentMethods items
rrefund: "expensive", // refers to refundReasons value field
ddate: "" // here's input string
})
Second - selects and their change handlers.
// simple approach
const paymentMethods = ['none', 'Credit', 'Paypal', 'Wallet'];
// complex approach
const refundReasons = [
{
title: 'I need to cancle',
value: 'need'
},
{
title: 'Too expensive',
value: 'expensive'
}
// add some more
]
Select handler. Here we are currying function and returning a new one with enclosed stateFieldName referring to our composed state field.
But I'm not actually sure if we must use event.target or event.currentTarget - try both, one will work for sure.
const selectHandler = (stateFieldName) => (event) => {
setFormState({
...formState,
[stateFieldName]: event.currentTarget.value
})
}
Render selects (no styles, sorry)
// no need to apply hidden fields
<select
onChange={selectHandler('rpayment')} // yes, this is a function call, it returns a new function
>
{paymentMethods.map((item) => (
<option
key={item}
value={item}
selected={item === formState.rpayment}
>
{item}
</option>
))
}
</select>
// complex select
<select
onChange={selectHandler('rrefund')}
>
{refundReasons.map(({title, value}) => (
<option
key={value}
value={value}
selected={value === formState.rrefund}
>
{title}
</option>
))}
</select>
// input
<input
type="date"
value={formState.ddate}
onChange={selectHandler(ddate)} // probably this handler will work here too
/>
And finally submit button. If your form is supposed to send async request, your submit button doesn't need value prop and any handler. I general it must have type='submit' prop but the form itself must have a submit hander and send data stored in your state.
Something like
<form
onSubmit={() => {
axios.post('/some/api', {
data: formState
})
.then(/* you logic here */)
}}
>
// your inputs here
</form>
Sorry for approximations and possible mistakes, I hope you got the main idea.

Clear a specific select in react-bootstrap

I need to clear the second select if it already has a selected value, in case the first one selects B.
When cleaning it requires that the value be removed, disabled and that it has the Select... option by default.
import React, { useState } from "react";
import { Form, Col, Button } from "react-bootstrap";
const App = () => {
const [data, setData] = useState({
to: "",
from: "",
});
const handleChange = (e) => {
const { name, value } = e.target;
setData((prevState) => ({
...prevState,
[name]: value,
}));
};
return (
<Form>
<Form.Row>
<Form.Group as={Col} md="auto">
<Form.Label>to</Form.Label>
<Form.Control
required
name="to"
as="select"
placeholder="to"
onChange={handleChange}
>
<option hidden value="" selected>
Select...
</option>
<option value="A">A</option>
<option value="B">B</option>
</Form.Control>
</Form.Group>
<Form.Group as={Col} md="auto">
<Form.Label>from</Form.Label>
<Form.Control
required
name="from"
as="select"
placeholder="from"
onChange={handleChange}
>
<option hidden value="" selected>
Select...
</option>
<option value="A">A</option>
<option value="B">B</option>
</Form.Control>
</Form.Group>
<Button variant="primary">Ok</Button>
</Form.Row>
</Form>
);
};
export default App;
How can I modify the code above to do what I commented? Thanks!
what you have here is cascading issue your second select depends on the first select. In your handle change you can do this
const handleChange = (e) => {
const { name, value } = e.target;
if(name === 'to' && data.from){
setData((prevState) => ({
...prevState,
[name]: value,
from: ''
}));
} else {
setData((prevState) => ({
...prevState,
[name]: value,
}));
}
};
We are checking whether the value we are changing is the first select and also if the second select already has a value . If yes then we are clearing the value in the second select.
Also since you are using the state to preserve the form values , you need to add the value prop to your component to make it as Controlled Component .
<Form.Control
required
name="to"
as="select"
placeholder="to"
value={data.to} // add this prop
onChange={handleChange}
>
<option hidden value="">
Select...
</option>
<option value="A">A</option>
<option value="B">B</option>
</Form.Control>

React <select>'s <option> is not working at onClick

I need a function at every here is it.
i want to access option value.
const region = ['Africa','America','Asia','Europe','Oceania'];
<div className="options">
<select>
<option>Select a Region</option>
{region.map((nameOfRegion) => {
return <option onClick={() => requestRegion(nameOfRegion)}>
{nameOfRegion}
</option>
})}
</select>
this function is not logging options
const requestRegion = (region) => {
console.log(region)
}
Use onChange event on the <select>:
const requestRegion = (event) => {
console.log(event.target.value);
};
return (
<div className="options">
<select onChange={requestRegion}>
<option>Select a Region</option>
{
region.map((nameOfRegion) => (<option>{nameOfRegion}</option>))
}
</select>
</div>
Because neither the onSelect() nor onClick() events are supported by the option tag. So you can use onChange in select tag in this case:
const onChange =(e) => {
console.log(e.target.value);
}
<select name="select" onChange = {onChange}>
<option>Select a Region</option>
{region.map((nameOfRegion, i) => {
return (
<option key={i}>
{nameOfRegion}
</option>
);
})}
</select>
Note: you need add key in child tag when using map
You should use an onChange handler on the select element, and get the value from the change event:
const Demo = ({ regions }) => {
const requestRegion = e => {
console.log(e.target.value)
}
return (
<select onChange={requestRegion}>
<option>Select a Region</option>
{regions.map(region => (
<option key={region}>{region}</option>
))}
</select>
)
}
const regions = ['Africa','America','Asia','Europe','Oceania']
ReactDOM.render(
<Demo regions={regions} />,
root
)
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"></div>

How can I do to show three params when I click on the select using react.js?

I have a select such as :
<Select id='1' list={[...this.state.dictionnary]}/>
where this.state.dictionnary is like this :
state = {
dictionnary: [
{value: "a", name: "ab"},
]}
And the component select is like this :
class Select extends Component {
handleChange()
{
// I would like to show 1, a and ab
// 1 is the id of the select
// a and ab are value and name of
};
render() {
return (
<select onChange={this.handleChange} className="custom-select">{this.props.list.map(option => (
<option key={option.value} value={option.value}>{option.name}</option>))}
</select>
)
}
}
export default Select;
I would like to show some informations using the handleChange() function like the id, name and value.
Could you help me please ?
Thank you very much
You should change the option so the value has the entire object to get it later in the handleChange. Also, to access the same this with the props in the handleChange method you need to use an arrow function:
<select onChange={(e) => this.handleChange} className="custom-select">
{this.props.list.map((option) => (
<option key={option.value} value={option}>
{option.name}
</option>
))}
</select>
By default, the onChange handler gets the event param so you can get the target and the props:
handleChange(event) {
const { value, name } = event.target.value;
console.log(this.props.id, value, name);
}
Adding to #Alavaro answer, you need to split after getting the value to remote ,
handleChange = e => {
const [value, name] = e.target.value.split(',')
console.log({ id: this.props.id, value, name})
}
render() {
return (
<select onChange={this.handleChange} className="custom-select">
{this.props.list.map((option, index) => (
<option key={option.value} value={[option.value, option.name]} >
{ option.name }
</option>
))}
</select>
);
}
}

Categories

Resources