I'm trying to handle an event based on the 'checked' attribute of a radio button in React.js. I want the buttons to allow for selecting more than one value, so I removed the 'name' attribute which seemed to disallow multiple selections.
The base radio button component looks like
export function Radio_Button_Multiple_Selection (props) {
return (
<label>
<input type="radio"
checked={props.form_data[props.field_name].indexOf(props.btn_value) > -1}
onClick={props.radio_effect} />
{props.btn_value}
</label>
)
}
I have a form_data objec that has an array of values that correspond to the btn_value of the radio buttons, where if the value is found in the array it should come up as checked. (And this part is working fine right now, they do in fact show up checked as expected):
const form_data = {
...
occupations: ['student', 'merchant', 'paladin', 'troll'],
...
}
Now, I also have a react class with a method for manipulating the values in the occupations array,responding to whether the radio button being licked was already checked or not:
handleRadioButton (event) {
const target = event.target;
const value = target.value;
const isChecked = target.checked;
console.log(isChecked) // this undefined. why?!?
if (isChecked) {
// remove it from array
// etc.
} else {
// add it to array
// etc.
}
}
My main issue is that following:
When I console.log the "checked" logic that returns a boolean inside the RadioButton component's checked attribute, it comes up as expected (true of false if it is or isnt in the array). But it always comes up as checked = undefined in the class method for handling the buttons.
You cannot uncheck a radio button in HTML either. You have to control the checked attribute with your props or state:
Even more, you should rely only on your state, instead of a mix, e.target.checked & state.
class Radio extends Component {
state = {}
render() {
return <input
type="radio"
checked={this.state.checked}
onClick={e => this.setState({
checked: !this.state.checked
})}
/>
}
}
<input type="radio" />
I found the workaround:
use the 'value' attribute to store the same information that I am storing under the 'checked' attribute, with an array that has the button's value AND the checked logic boolean; e.g., value=[boolean, btn_value]
then access the 'value' attribute in the event handler. the value arrives as a full string, so I just split it at the comma and work from there.
it's hacky, but it worked.
Related
How can I store the selected value in another state and stop unchecking the value when I select another button?
const Notifications = () => {
data:[
{id:1, name:apple, notification:{id:1,msg:"hi"}, {id=2,msg:"hlo"}},
{id:2, name:banana, notification:{id:13,msg:"hi"}, {id=3,msg:"hlo"}}
{id:3 ,name:mango, notification:{id:14,msg:"hi"}, {id=34,msg:"hlo"}},
{id:4, name:grape, notification:{id:15,msg:"hi"}, {id=341,msg:"hlo"}},
{id:5, name:carrot, notification:{id:16,msg:"hi"}, {id=4,msg:"hlo"}},
]
const onCheckedValue = (e) => {
const { checked, value } = e.target;
if (checked) {
setCheckedData([...checkedData, value]);
} else {
setCheckedData(checkedData.filter((item) => item !== value));
}
};
return(
<>
{data.map(d => {
<Button onClick=(setActive(true))>
{d.name}
</Button>
})}
//render the notifications when I clicked on the button I want to store the checked value in a state with respect to the selected button and checked notification.
{active ?
data.notifications.map((notification) => (
<div className="checkbox">
<Checkbox
onChange={onCheckedValue}
value={notification}
/>
/div>
}
</>
)
}
You can use the React Use State Hook. You'll want to define a variable and a state setter for that variable like so:
const [selected, setSelected] = useState([]);
Then when you click or un-click a checkbox, you can call setSelected with the updated selected value. I instantiated selected as an empty array in my example, so you'd arr.push(new value).
Your second block of code is where there are issues.
You are attempting to access properties on data, but that is an array and you cannot access the properties of the objects inside of it like that.
You will need to loop over the array or access an index of the array directly in order to access notifications.
As a side note, you are attempting to use the ternary operator to conditionally render (?), but not supplying the second argument after the :. I gave it a value of null so that it doesn't render anything if not active.
That could look something like this:
// I don't know where active comes from, but I'll assume that's correct.
{active ? (
data.map((item) => (
<div className="checkbox">
<Checkbox
onChange={onCheckedValue}
checked={!!checkedData.find(name => name === item.name)}
value={item.name}
/>
</div>
))) : null
}
In this example, I map over data and create a Checkbox for each item in the array. I don't know if that's what you want as it's unclear. However, with your onCheckedValue function, it will make updates to the checkedData array.
You should pass the prop checked to control the checkbox's state. I set the checked value to the return of checkedData.find(), and used the double-bang (!!) operator to coerce it into a Boolean value (true or false).
The find operation will be looking to see if the data[i].name matches any value in the checkedData array, since you are storing the object's name property in the checkedData array. If it finds nothing, it will be !!undefined which will evaluate to false, and if it does find it, it will be !!"banana" which evaluates to true.
I'm just starting with React, combined with the Carbon Design System from IBM (https://www.carbondesignsystem.com/). My problem is that Checkbox and RadioButton components do not return the same {event} object to my onChange handler as that returned by straight React/JSX. Carbon DOES match React on TextInput and Select components.
The behavior is similar to the issue in React onChange event doesnt return object. However, that is referencing React Toolbox and Material Design, which I am not using.
The difference can be seen in Carbon's online sandbox tool. An example of a radio button is at http://react.carbondesignsystem.com/?selectedKind=RadioButton&selectedStory=Default&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Factions%2Factions-panel. Click the button and see the results returned in the Action Logger panel. Compare to results of changing (not just clicking) a text input in http://react.carbondesignsystem.com/?selectedKind=TextInput&selectedStory=Default&full=0&addons=1&stories=1&panelRight=0&addonPanel=storybook%2Factions%2Factions-panel. The problem element returns {event} as the third element down, while the good one returns only one element containing the {event} as desired. Apparently, Carbon is only passing along the first element.
Below is the change handler, the ReactJSX, and the resulting HTML.
handleChange(event) {
console.log(event)
const {name, value, type, checked} = event.target
this.setState({[name]: value})
}
<RadioButton
id="gender-male"
name="gender"
labelText="Male (Carbon)"
labelPosition="right"
value="male"
checked={this.state.gender === "male"}
onChange={this.handleChange}
/>
<div class="bx--radio-button-wrapper">
<input
id="gender-male"
name="gender"
type="radio"
class="bx--radio-button"
value="male"
>
<label for="gender-male"
class="bx--radio-button__label"
aria-label="Male (Carbon)">
<span class="bx--radio-button__appearance"></span>
<span class="">Male (Carbon)</span>
</label>
</div>
The expected behavior is for Local Scope to contain an event object with properties for name, value, type, checked, etc. But for Carbon's RadioButton and Checkbox, Local Scope contains only a flat text of the element's value, no object at all.
Global Scope does seem to contain the full object. But I hesitate to reference outside of Local, and it seems wrong to have to.
The following code is straight from the RadioButton component of the carbon-components-react library.
handleChange = evt => {
this.props.onChange(this.props.value, this.props.name, evt);
};
source
The code shows that the event is passed as the third argument, following the contents of the value and name props of the RadioButton component.
So you should be able to handle changes like this:
handleChange(value, name, event) {
console.log(event)
const {type, checked} = event.target // Should not be needed anymore.
this.setState({[name]: value})
}
can anyone help, i'm trying this in React but i'm really stuck.
I have 3 checkboxes on a child component that i have rendered dynamically using an array of options in state (in the parent component):
What i need to be able to do is, as per the below image: click on each checkbox and get further options.
https://discourse-user-assets.s3.dualstack.us-east-1.amazonaws.com/original/3X/d/3/d385407399b0fa130741e653c9650ff9953282df.png
The checked options and the sub options (not just dropdowns, the third checkbox has input fields) all need to be available in state so i can collect them up and post them to a database
What i have so far is the below:
Array of options in state:
sowType: [
"ProductSow",
"Teradata Customer SOW",
"Custom Professional Services SOW"
]
Final rendered checkboxes:
https://discourse-user-assets.s3.dualstack.us-east-1.amazonaws.com/original/3X/4/5/45702836b84abe764917f4bdf5172258f4d3e39c.png
My problem is, i don't know what to do next. I have dynamically rendered the initial 3 check boxes but i don't know how to add the conditional rendering to get the sub boxes to appear on clicking the check boxes) and add the info selected from them to state.
i.e. can i only add the conditional rendering on checkboxes that have NOT been dynamically rendered from map or is there a way to do it, in which case how is it done?
My code so far is as below, it may not be the best setup for what i am trying to do:
Can anyone help??
This is the reference to the component in the parent with the props passed down:
<SowType
title={"SOW Type"}
setName={"SOW Type"}
subtitle={"What type of SOW do you want to generate?"}
type={"checkbox"}
controlFunc={this.handleSOWTypeCheckbox}
options={this.state.sowType}
selectedOptions={this.state.sowTypeSelectedOption}
/>
This is the child component dynamically rendering the array of items from state:
class SOWType extends React.Component {
render() {
// console.log(this.props);
return (
<div className="form-group">
<label htmlFor={this.props.name} className="form-label">
{this.props.title}
<h6>{this.props.subtitle}</h6>
</label>
<div className="checkbox-group">
{this.props.options.map(option => {
return (
<label key={option}>
<input
className="form-checkbox"
name={this.props.setName}
onChange={this.props.controlFunc}
value={option}
checked={this.props.selectedOptions.indexOf(option) > -1}
type={this.props.type}
/>
{option}
</label>
);
})}
</div>
</div>
);
}
}
This is the method i am currently using in the parent when a checkbox is clicked in the child component (this basically takes the selected option from the array in state and puts the checked options into another array in state called 'sowTypeSelectedOption: [ ]',
handleSOWTypeCheckbox(e) {
const newSelection = e.target.value;
let newSelectionArray;
if (this.state.sowTypeSelectedOption.indexOf(newSelection) > -1) {
newSelectionArray = this.state.sowTypeSelectedOption.filter(
item => item !== newSelection
);
} else {
newSelectionArray = [...this.state.sowTypeSelectedOption, newSelection];
}
this.setState(
{
sowTypeSelectedOption: newSelectionArray
} /*() =>
console.log("sow Type Selection: ", this.state.sowTypeSelectedOption) */
);
}
If I understand your question correct, you want the additional sub menu to show once the checkbox is selected, then you can use conditional rendering. Just show/hide the menu on select or deselect of checkbox like so.
{ this.props.selectedOptions.indexOf(option) > -1 ? (
<h5>submenu options component renders here</h5>
) : " "
}
I have a scenario where there will be two fields of each item. One field is a checkbox and another is dropdown but the point is to get a pair of data from this and I am mapping this based on the item(they have category too). And the dropdown depends on checkbox(when unchecked the dropdown is disabled and value is reset to none or zero)
I tried
<Field name={ `${item.label}` } component={MyCheckBoxComponent}>
<Field name={ `${item.value}` } component={MyDropDownComponent}>
what happens is each of them have unique name and I cant do anything when I need to update the values depending on the checkbox selections.
I have tried putting the two inputs in one Field but I can't get it to work. Help would be much appreciated
You need to use Redux Fields (https://redux-form.com/6.0.4/docs/api/fields.md/) not Redux Field.
You can create a separate component which wraps your check-box component and your drop-down component.
This is how you use it
<Fields
names={[
checkboxFieldName,
dropDownFieldName
]}
component={MyMultiFieldComponent}
anotherCustomProp="Some other information"
/>
And the props that you get in your MyMultiFieldComponent
{
checkboxFieldName: { input: { ... }, meta: { ... } },
dropDownFieldName: { input: { ... }, meta: { ... } },
anotherCustomProp: 'Some other information'
}
The input property has a onChange property (which is a method), you can call it to update the respective field value.
For example in onChange method of check-box
onChangeCheckbox (event) {
const checked = event.target.checked;
if (checked) {
// write your code when checkbox is selected
} else {
const { checkboxFieldName, dropDownFieldName } = props;
checkboxFieldName.input.onChange('Set it to something');
dropDownFieldName.input.onChange('set to zero or none');
}
}
This way you can update multiple field values at the same time.
Hope this helps.
Probably, this is what are you looking for - formValues decorator.
Just wrap your dropdown with this decorator and pass the name into it of your checkbox so you will have access inside of the MyDropDownComponent.
For example:
import { formValues } from 'redux-form';
<form ...>
<Field name="myCheckBox" component={MyCheckBoxComponent}>
<Field name="myDropdown" component={formValues('myCheckBox')(MyDropDownComponent)} />
</form>
So then myCheckBox value will be passed as a prop.
Performance note:
This decorator causes the component to render() every time one of the selected values changes.
Use this sparingly.
See more here - https://redux-form.com/7.3.0/docs/api/formvalues.md/
I am having a very annoying issue with React and checkboxes. The application I am working with requires a list of checkboxes that represent settings that are persisted in the back-end. There is an option to restore the settings to their original state.
At first, I created a component that has an object like a map of settings. Each setting has a key and a boolean value. Hence:
{
bubbles: true,
gregory: false
}
Is to be represented as:
<input type="checkbox" value="bubbles" checked="checked" />
<input type="checkbox" value="gregory" />
Now, it seems React is ignorant about how a checkbox is supposed to work. I don't want to set the checkboxes' values, I want the "checked" property.
Yet, if I try something like this:
<input
type="checkbox"
value={setting}
checked={this.settings[setting]}
onChange={this.onChangeAction.bind(this)}
/>
I get this warning:
Warning: AwesomeComponent is changing an uncontrolled input of type checkbox to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: [some useless docs page I read several times to no avail]
So I decided to create another component to wrap each individual checkbox and I got this:
<input
type="checkbox"
checked={this.state.checked}
onChange={this.onChangeAction.bind(this)}
/>
Now the checked is a property present directly in my state.
This yields the same warning, so I tried using defaultChecked:
<input
type="checkbox"
defaultChecked={this.state.checked}
onChange={this.onChangeAction.bind(this)}
/>
Which makes the warning disappear, but now it is unable to reset the checked value to the default one. So I tried playing with the method componentWillReceiveProps, this way I am quite sure my state is correct, this.state.checked is correct and the component renders again.
And it does. But the checkbox remains as it was originally.
For now I left that ugly warning and I am using checked.
How do I fix this thing so the warning goes away?
I was thinking that perhaps there is a way to force-re-render the component, so it captures the new defaultChecked value and uses it. But I don't know how to do that. Perhaps suppress the warning only for this component? Is that possible? Perhaps there is something else that can be done?
The problem arises if you set the checked property of your checkbox to null or undefined.
Those are "falsy" values in JS. However, React treats a value of null as if the property was not set at all. Since the default state of a checkbox is unchecked, everything will work fine though. If you then set checked to true, React thinks the property suddenly comes into existence! This is when React figures you switched from uncontrolled to controlled, since now the prop checked exists.
In your example, you can get rid of this warning by changing checked={this.settings[setting]} to checked={!!this.settings[setting]}. Notice the double bang (!!). They will convert null or undefined to false (and leave true alone), so React will register your checked property with a value of false and start off with a controlled form component.
I had this problem too and I, too, read the docs about controlled-components several times to no avail, but I finally figured it out, so I thought I'd share. Also, since version 15.2.0, normal inputs are triggered to be controlled by setting value, while checkboxes are initialized as controlled by setting checked, regardless of their value property, which added a bit to the confusion.
Amoebe's answer is correct, but I think there's a cleaner solution than the double bank (!!). Simply add a defaultProps property with value false for checked prop of your Checkbox component:
import React from 'react';
const Checkbox = ({checked}) => (
<div>
<input type="checkbox" checked={checked} />
</div>
);
Checkbox.defaultProps = {
checked: false
};
export default Checkbox;
Basically, the defaultChecked means you don't want to control the input – it just renders with this value and then there is no way to control it. Also, value shouldn't be used, but checked instead, so your second code should be correct. And you shouldn't use them both simultaneously.
<input
type="checkbox"
checked={this.state.checked}
onChange={this.onChangeAction.bind(this)}
/>
Can you create a small fiddle with this behaviour?
Here is an answer using hooks should you choose to convert the class component to a functional one...
export default Checklist = () => {
const [listOfItems, setListOfItems] = useState([
{name: 'bubbles', isChecked: true},
{name: 'gregory', isChecked: false}
]);
const updateListOfItems = (itemIndex, isChecked) => {
const updatedListOfItems = [...listOfItems];
updatedListOfItems[itemIndex].isChecked = isChecked;
setListOfItems(updatedListOfItems);
}
return (
{listOfitems.map((item, index) =>
<index key={index} type="checkbox" checked={item.isChecked} onChange={() => updateListOfItems(index, !item.isChecked)} />
)}
)
}
You can assign your data to the state and then make use of the checked property associated with the individual checkbox to set the state as
{ this.state.data.map(function(item, index) {
return (
<input type="checkbox" checked={item.value} onChange={this.handleChange.bind(this, index)}/>
);
}.bind(this))
}
Sample Data in state is as
data: [{name:'bubbles', value: true}, {name:'gregory', value: false}]
JSFIDDLE
What finally worked, combining other answers:
const [categories, setCategories] = useState(
[
{
field: 'Label 1',
checked: true
},
{
field: 'Label 2',
checked: false
},
{
field: 'Label 3',
checked: false
}
]
)
const updateList = (item, isChecked) => {
const updatedList = [...categories];
updatedList[item].checked = isChecked;
setCategories(updatedList);
}
... in your markup:
{
categories.map((item, index) =>
<div key = {item.field}>
<label>
<span>{item.field}</span>
<input key={index}
type="checkbox"
checked={item.checked}
onChange={() => updateList(index, !item.checked)}
/>
</label>
</div>
)
}