How to get the value of selected option from React hooks? - reactjs

I have this React Component like below:
const ProductCell = (props) => {
const [option, setOption] = useState();
return(
<div>
<NativeSelect
value={option}
onChange={e => setOption(e.target.value)}
>
{props.product.variations.nodes.map( // here I extracted all the item
(item, i) => (
<option value={item} key={i}>{item.name}</option> // set option value to item object
))
}
</NativeSelect>
<Typography variant="h5" className={classes.title}>
{option.price} // I want to update the value of price according to the selected option.
</Typography> // according to the selected option above
</div>
)
}
I have a NativeSelect component which is from React Material-Ui, so basically it is a Select html tag. In the code above, what I do is, extract all the element inside props.product.variations.nodes and put all the extracted item and put each of the element into a <options/> tag.
The Json object for item will look like this:
"variations": {
"nodes": [
{
"id": "someId",
"name": "abc1234",
"variationId": 24,
"price": "$100.00"
},
{
.. another ID, name,variation and price
}
]
}
As you can see, I targeting the part of id, name , variationId and price as an object. Therefore each <option/> tag will present with item.name as the presentation to user. So far in this part having no problem, let say having 5 variations, and can present all of them.
What I want to do is:
I want to update the value of price under the <Typography /> component. Example, user selected 3rd options in the Select, I want to update the price value of the 3rd item in <Typography /> .
What I tried:
I create a react hooks const [option, setOption] = useState(); , then when handleChange, I setOption() with event.target.value in NativeSelect component . Therefore the value of <option /> tag is set as item object.
Lastly, I get the price value from the hooks in the Typography section.
But what I get is:
The price value is undefined in console log. So I can't get the value of option.price.
and this error:
TypeError: Cannot read property 'price' of undefined
Question:
How can I get the option.price value(which I expect it is same with item.price) outside the NativeSelect component in my above example?
I tried my best to explain based on what I understand by this time being. So any help will be well appreciated.
Update:
Here is what I got when console log the item object in variation.node.map() section and data object inside onHandleChanged section, but also produce the same result:

You have to set a default selected option on your ProductCell component. Also your onChange handler will receive a string instead of an object when you access the value on event.target.value.
From the docs
function(event: object) => void event: The event source of the callback. You can pull out the new value by accessing event.target.value (string).
event.target.value will be a string even though you pass the value as object on NativeSelect component.
What you might want to do? Don't set the current selected item as an object, instead use the id and have a function that look-ups the item using the current selected id.
Check the code below.
const ProductCell = (props) => {
const { variations } = props.product;
const { nodes } = variations;
// we're setting the first node's id as selected value
const [selectedId, setSelectedId] = useState(nodes[0].id);
const getSelectedPrice = () => {
// finds the node from the current `selectedId`
// and returns `price`
const obj = nodes.find((node) => node.id === selectedId);
return obj.price;
};
function onChange(event) {
// event.target.value will be the id of the current
// selected node
setSelectedId(parseInt(event.target.value));
}
return (
<div>
<NativeSelect value={selectedId} onChange={onChange}>
{nodes.map((item, i) => (
<option value={item.id} key={i}>
{item.name}
</option>
))}
</NativeSelect>
<Typography variant="h5">{getSelectedPrice()}</Typography>
</div>
);
};
Also notice that were passing the id as a value prop on each of our options.
<option value={item.id} key={i}>
And how we now display the price, we're calling our getSelectedPrice().
Update
I thought a better solution. I realized that you can set your selected state as an object and on your onChange handler given the id from event.target.value find the item on nodes and set that as your new selected state.
const ProductCell = (props) => {
const { variations } = props.product;
const { nodes } = variations;
const [selected, setSelected] = useState(nodes[0]);
function onChange(event) {
const value = parseInt(event.target.value);
setSelected(nodes.find((node) => node.id === value));
}
return (
<div>
<NativeSelect value={selected.id} onChange={onChange}>
{nodes.map((item, i) => (
<option value={item.id} key={i}>
{item.name}
</option>
))}
</NativeSelect>
<Typography variant="h5">{selected.price}</Typography>
</div>
);
};

Related

How to conditionally disable check boxes

I have a drop down with check boxes inside it (used Bootstrap). When two are checked, I want to make it so the users cannot check anymore until the others are unchecked. I attempted this using the disable attribute but keep getting the error below.
What I tried:
export default function Test() {
const [countries, setCountries] = useState([]);
const [disableRadio, setDisableRadio] = useState(false);
function createCountryList() {
const countries = ["a", "b", "c", "d"];
return countries.map((country, key) => (
<Form.Check
disabled={disableRadio ? (e) => e.target.checked : false}
key={key}
label={country}
onChange={(e) =>
e.target.checked ? handleChecked(e) : handleUncheck(e)
}
/>
));
}
function handleChecked(e) {
const Arr = countries;
Arr.push(e.target.value);
setCountries(Arr);
if (countries.length > 1) {
setDisableRadio(true);
} else {
setDisableRadio(false);
}
}
function handleUncheck(e) {
const Arr = countries;
const index = Arr.indexOf(e.target.value);
Arr.splice(index, 1);
setCountries(Arr);
}
return (
<>
<Dropdown>
<Dropdown.Menu as={CustomMenu}>
{createCountryList()}
</Dropdown.Menu>
</Dropdown>
</>
);
}
Getting this error: react-dom.development.js:67 Warning: Invalid value for prop 'disabled' on <input> tag. Either remove it from the element, or pass a string or number value to keep it in the DOM.
What I want:
Thanks!
There are several issues with your code, and I have a feeling that you're overcomplicating the handling of checkbox events.
You have countries as a state, and also as a fixed array of available countries. I'd suggest you store them separately: one is pure data (fixed array of available countries), and the other is the component state.
You can use Set to handle adding/removing of countries, without too much of a fuss. There is no need to have separate event handlers for checked/unchecked states. We can handle adding/removing selected countries to the array using this logic below:
const [countries, setCountries] = useState(new Set());
function onChange(e) {
if (e.target.checked) {
setCountries(c => new Set([...c, e.target.value]));
} else {
setCountries(c => new Set([...c].filter(x => x !== e.target.value)))
}
}
There is no need to store disableRadio in a separate state. Instead, take advantage of useMemo, or simply use the logic of countries.size > 1 in the template itself. Combine that with countries.has(country) to check whether a given checkbox's value is in the set of values. In this sense, a checkbox is disabled when (1) more than 1 countries have been selected AND (2) the count is NOT in the list of selected countries
disabled={countries.size > 1 && !countries.has(country)}
Your checkbox options are missing the value attribute: remember to add that.
With all these changes proposed, your code can be simplified into something like this:
export default function Test() {
// NOTE: Store available countries as a constant
const availableCountries = ["a", "b", "c", "d"];
// NOTE: countries here refer to SELECTED countries
// We use new Set() so we automatically dedupe values
const [countries, setCountries] = useState(new Set());
function createCountryList() {
return availableCountries.map((country, key) => (
<Form.Check
disabled={countries.size > 1 && !countries.has(country)}
key={key}
value={country}
label={country}
onChange={onChange}
/>
));
}
function onChange(e) {
if (e.target.checked) {
setCountries(c => new Set([...c, e.target.value]));
} else {
setCountries(c => new Set([...c].filter(x => x !== e.target.value)))
}
}
return (
<>
<Dropdown>
<Dropdown.Menu as={CustomMenu}>
{createCountryList()}
</Dropdown.Menu>
</Dropdown>
</>
);
}
See example of CodeSandbox:

React updating state after User control value changes

I am getting a book list from database and is stored in a state variable
Book list also has book price field
const [books, setBooks]=useState([])
setBooks(data)
I am creating an html in a loop based on this data
return ( <div>
{books.map((x,i) => ( <tr>
<td>x.bookName</td>
<td><MyCustomTextInput value={x.price}></MyCustomTextInput></td>
<tr></div>);
Following is the implementation of MyCustomTextInput
function MyCustomTextInput(props)
{ return (<div>><MyCustomTextInput></MyCustomTextInput> </div>)
} exports default MyCustomTextInput
My question is: when I change a price in the custom text input appeared in the table, it does not change in the underlying state level variable's array element
Could you please suggest?
to change a price in books state update books state like that
const handleChange = (e, id) => {
setBooks(books => books.map(book => {
if (book.id === id) book.price = e.target.value;
return book;
}))
};
//you must pass onChange prop to Input
<MyCustomTextInput onChange={e => handleChange(e, x.id)} value={x.price}>
//pass onChange prop to Input like this
function MyCustomTextInput(props){
return <TextInput onChange={props.onChange}/>
}

fire an onChange event while setting the initial value

I am trying to build a select field which will "select" an Entry automatically when there is only one entry in the list. My Problem is, that there will no onChange event triggered, when setting the value in the list. Is there any possibility to send these event programatically?
This is my code so far
export const SelectField = function(props) {
const classes = useStyles();
const {t} = useTranslation();
const [selectedValue, setSelectedValue] = React.useState(undefined);
if (selectedValue === undefined && props.menuEntries.length === 1) {
setSelectedValue(props.menuEntries[0]);
//need to fire an event in this case
} else if (props.addSelectEntry) {
props.menuEntries.push({"name":t("select"), "value":""});
}
return (
<Select
value={selectedValue}
onChange={props.onChange()}
name={props.name}
displayEmpty
className={classes.selectEmpty}
>
{props.menuEntries.map(entry => (
<MenuItem key={entry.name} value={entry.value}>
{entry.name}
</MenuItem>
))}
</Select>);
};
There is an important difference in passing a function as a prop and calling a function.
onChange={props.onChange()}
Will call that function every render.
onChange={props.onChange}
Will pass that function to be called by the component.
For one is the mentioned onChange() to onChange, but that is not all.
It seems the issue is connected to code outside of the provided component. One indicator of an issue is the following:
....
} else if (props.addSelectEntry) {
props.menuEntries.push({"name":t("select"), "value":""}); // This line could be faulty
}
....
I don't know what kind of value menuEntries is, but it should be a state somewhere higher in your component tree. You should also pass in the setter. Probably called something like setMenuEntries. Then call that setter in instead of mutate the prop.
setMenuEntries([...menuEntries, {"name":t("select"), "value":""}])
Only when setting a state a rerender is triggered.
In general, updating props of any function is considered a bad-practise when following the principle of immutability. Eslint can help you with signifying patterns like that.
From the looks of your code it seems to be alright, there is however one thing that might cause your problem.
return (
<Select
value={selectedValue}
onChange={props.onChange} <---
name={props.name}
displayEmpty
className={classes.selectEmpty}
>
{props.menuEntries.map(entry => (
<MenuItem key={entry.name} value={entry.value}>
{entry.name}
</MenuItem>
))}
</Select>);
I changed how you set the onchange function. You did it while also calling the actual function. This will make the function fire when the component renders and not on the change of your controlled value for this select.
I have solved my problem by calling he handler by my own, if i set the initial value:
export const SelectField = function(props) {
const classes = useStyles();
const { t } = useTranslation();
const [selectedValue, setSelectedValue] = React.useState(props.value);
// on startup load test cases
React.useEffect(() => {
if (selectedValue === props.value && props.menuEntries.length === 1) {
setSelectedValue(props.menuEntries[0].value);
props.onChange(props.menuEntries[0].value);
}
}, [selectedValue]);
const updateValue = function(e) {
props.onChange(e.target.value);
setSelectedValue(e.target.value);
};
return (
<Select
value={selectedValue}
onChange={updateValue}
name={props.name}
displayEmpty
className={classes.selectEmpty}
>
{
(props.menuEntries.length !== 1 && props.addSelectEntry) && (
<MenuItem key={t("select")} value={t("select")}>
{t("select")}
</MenuItem>
)
}
{props.menuEntries.map(entry => (
<MenuItem key={entry.name} value={entry.value}>
{entry.name}
</MenuItem>
))}
</Select>);
};

State is one change behind in select menu

I have a dropdown menu that is used to select a State. It is used to display different data per state. I am console.log(), Whenever I go to select a state and the initial one is '' and when I change again, it seems to be one step behind. For example: My dropdown menu starts empty, I then select New York. It prints '', I then select Maine, It prints New York. I think I understand why it is happening but I cannot figure out how to fix it.
ProjectStatus.js
const [selectedState, setSelectedState] = useState('');
const handleChange = event => {
setSelectedState(event.target.value);
console.log(selectedState);
};
return (
<div className='project-status-nav'>
<h3>Project Status Page</h3>
<hr className='separator' />
<FormControl variant='filled' className={classes.formControl}>
<InputLabel htmlFor='selectState'>Select State</InputLabel>
<Select
id='selectState'
className='selectStyle'
value={selectedState}
onChange={handleChange}>
<MenuItem value={''}> </MenuItem>
<MenuItem value={'New York'}>New York</MenuItem>
<MenuItem value={'New Jersey'}>New Jersey</MenuItem>
<MenuItem value={'Colorado'}>Colorado</MenuItem>
<MenuItem value={'Florida'}>Florida</MenuItem>
<MenuItem value={'Maine'}>Maine</MenuItem>
</Select>
</FormControl>
</div>
);
That happens because state isn't immediatly updated. When the function is called, selectedState is declared and doesn't change untill the next call (update).
You can if you want observe selectedState changes with useEffect, or create an custom hook to do it like this:
// works like the React class setState, but the callback is called passing the new State
function useStateCallback(initialState) {
const [[state, cb], setState] = useState([initialState, null]);
useEffect(
() => {
cb && cb(state);
},
[state]
);
const setStateCallback = (value, callback) => setState([value, callback]);
return [state, setStateCallback];
}
function App() {
const [val, setVal] = useStateCallback(0);
const increment = () =>
setVal(val + 1, newVal => alert("Incremented: " + newVal));
}
setSelectedState is not synchronous, so when you log the value of selectedState just after calling setSelectedState, you get the current value, not the new one. That's why you're always one step behind
I faced the same problem and solved it like this.
const [breakwater, setBreakwater] = useState(props.breakwater);
useEffect( () => {
props.handleBreakwater(breakwater); //I updated the parent component's state in useEffect
},[breakwater]);
const handleprimaryBwType = (e) =>{
setBreakwater({...breakwater, primaryBwType : e.target.value});
//props.handleBreakwater(breakwater); when i updated here a selection was updating from behind
}

How can i define a default value for react-select v1

I had a react-select rendering a list of emails, and i need to keep the selected emails as a default option when the email is selected and saved, but the defaultValues are not working. How can i do that?
Here is my select component:
const [selectedOption, setSelectedOption] = useState("")
const makeEmailOption = item => ({
value: item.id,
label: item.ccEmail,
id: item.id,
chipLabel: item.ccEmail,
rest: item,
selected: item.selected
})
const makeEmailOptions = items => items.map(makeEmailOption)
const handleChange = (value) => {
setSelectedOption(value)
props.emails(value)
}
return (
<div>
<Select
multi={true}
name={props.name}
options={makeEmailOptions(props.ccemailfilter)}
onChange={handleChange}
value={selectedOption}
/>
</div>
)
I receive everything as props and work with that to make the options. How can i do that to make the default value if a field selected is true?
You almost have it, but in this case, you are setting the value to the selectedOption instead of setting the defaultValue. Also, you are changing the default value each time there is a change, which shouldn't be needed.
const defaultVal = {value: selectedOption, label: selectedOption};
return (
<div>
<Select
multi={true}
name={props.name}
options={makeEmailOptions(props.ccemailfilter)}
defaultValue={defaultVal}
/>
</div>
)
I came with the following solution, since my component use a function to set some variables to the select, i use a useEffect to call that with a filter right after the page render.
useEffect(() => {
handleChange(makeEmailOption(props.ccemailfilter.filter(x => x.selected)))
}, [])
const handleChange = (value) => {
setSelectedOption(value)
props.emails(value)
}
So, the handleChange are called on the onChange of the select and once after the page loads, to create a value to the select to use.

Resources