I'm building a dynamic form in react.
I try to bind checked value of a radio button but the comparaison seems to be executed immediatelly.
How do I differ comparasion ?
buildRadio(field) {
const radios = field.questionnairefieldlabelSet.map((label) => {
const radioValue = `${field.id}-${label.id}`
return <Form.Radio
key={radioValue}
label={label.text}
checked={this.state[field.id] === radioValue} // evaluated once, never changes on state change
value={radioValue}
onChange={(e, { value }) => {
this.setState({ [field.id]: value })}} />
})
return <Form.Group key={field.id}>
{radios}
</Form.Group>
}
I've tried to pass a function instead but I get an error message
checked={() => this.state[field.id] === radioValue}
Failed prop type: Invalid prop checked of type function supplied to Checkbox, expected boolean
Related
I have one onChange function basically main job is to select the radio button. The function is working fine but the problem is onChange function getting stuck in infinite loop whenever i switch to new tab/window then in console we can the function is getting executed again and again without doing any kind of action.
Below is code which is written in react.
const radioButton = (props) => {
const [value, setValue] = useState("");
useEffect(() => {
setValue(props.radioValue)
}, [props.radioValue]);
// below function getting stuck in infinite loop when we switch into new tab/window. Basically on switching the window/tab , function get executed again
const handleRadioButton = (value) => {
setValue(value)
}
return (
<Radio
options={props.radioOptions}
onChange={value => handleRadioButton(value)}
value={value}
/>
)
}
export default RadioButton;
You can check if your previous value for radio is equal to new value and do not update the state if they are equal.
const handleRadioButton = (oldValue,value) => {
if(oldValue === value){
return;
}
setValue(value);
}
<Radio
options={props.radioOptions}
onChange={newValue => handleRadioButton(value,newValue)}
value={value}
I have a React hook I call useToggles that I use for various checkboxes and radio buttons in my app. So far I have been able to get away with something like the following:
const useToggles = (initialValues = {}) => {
const [toggleValues, setToggleValues] = useState(initialValues);
const handleToggle = e => {
const name = e.currentTarget.attributes.name.value;
const value = toggleValues[e.currentTarget.attributes.name.value];
setToggleValues(values => ({ ...values, [name]: !value }));
};
return {
toggleValues,
setToggleValues,
handleToggle,
};
};
export default useToggles;
An example checkbox component:
<CheckBox
checked={toggleValues.gluten || false}
label="Gluten"
onChange={handleToggle}
name="gluten"
/>
So although my "toggleValues" object starts off as {}, any time a checkbox is checked, it populates the object. So we might have:
{
gluten: true,
soy: false
}
Because there's only one layer to this object, spreading out the values and using [name]: !value to flip the Boolean value will work.
However, this falls apart when there is the need for more organization. On another page, I have several groups of checkboxes, the values of which I will need to group together to populate individual database fields. To handle this, I've added a layer of organization to the checkboxes:
<CheckBox
checked={toggleValues.dietType.paleo || ''}
label="Paleo"
onChange={handleToggle}
name="dietType.paleo"
/>
We have used this method of organization elsewhere in our app in order to group data, and have parsed the string with dot-object. Example from useFormValues(): dot.str(e.target.name, value, tmp);
This method does not work with useToggles because we rely on previously existing data in the toggleValues object. Using dot-object consistently creates new layers of the object every time you click the checkbox. But I haven't found a way of using [name] to select a second or third level of an object.
To visualize this, what I need to be able to do is take this object and flip the value of paleo to true based on the function receiving the name "dietType.paleo":
{
dietType: {
paleo: true;
},
intolerances: {}
}
You can use currying and pass the checkbox group name as parameter to handleToggle:
const handleToggle = sub => e => {
const name = e.currentTarget.attributes.name.value;
if (!sub) setToggleValues({ ...toggleValues, [name]: !toggleValues[name] });
else {
const newSub = { ...toggleValues[sub], [name]: !toggleValues[sub][name] };
setToggleValues({ ...toggleValues, [sub]: newSub });
}
};
Now use onChange={handleToggle()} for the top level and onChange={handleToggle("dietType")} for the paleo checkbox.
Edit:
Another way is to check if the name has a period in it and branch accordingly:
const handleToggle = e => {
const name = e.currentTarget.attributes.name.value;
if (!~name.indexOf("."))
setToggleValues({ ...toggleValues, [name]: !toggleValues[name] });
else {
const [sub, prop] = name.split(".");
const newSub = { ...toggleValues[sub], [prop]: !toggleValues[sub][prop] };
setToggleValues({ ...toggleValues, [sub]: newSub });
}
};
This way you can keep your existing JSX 1:1.
I´m working on a React project, which involves displaying a value (DisplayValue) and then storing that value inside state so that I can use it later. Problem is state is always one step behind (for instance, if displayValue is "12", value is just 1). I need both values to be the same. Is it because setState is async? How can I fix it?
inputDigit(digit) {
const {
pendingOperation,
displayValue
} = this.state;
if (pendingOperation) {
this.setState({
displayValue: String(digit),
pendingOperation: false
})
}
value1 = parseFloat(displayValue);
this.setState({
displayValue: displayValue === "0" ? String(digit) : displayValue + String(digit),
value: value1
}, () => {
console.log(this.state.value)
})
};
Codepen: https://codepen.io/HernanF/pen/jXzPJp
You're breaking a fundamental React rule: Never set state based on existing state by passing an object into setState. Instead, use the callback form, and use the state object the callback form receives. You also probably want to call setState once, not (potentially) twice.
So, you want those changes in the update callback, something like this:
inputDigit(digit) {
this.setState(
({pendingOperation, displayValue}) => {
const newState = {};
if (pendingOperation) {
newState.displayValue = String(digit);
newState.pendingOperation = false;
}
newState.value = parseFloat(displayValue);
// Not sure what you're trying to do with the second setState calls' `displayValue: displayValue === "0" ? String(digit) : displayValue + String(digit),`...
return newState;
},
() => {
console.log(this.state.value)
}
);
}
There seem to be a problem in the code, not React
value1 = parseFloat(displayValue);
should be
value1 = parseFloat(displayValue + String(digit));
The same as for displayValue
Let's say I have the following rendered by a React.Component implementing material-ui components:
{data.map(value => (
<ListItem
key={data.indexOf(value)}
primaryText={value}
leftCheckbox={
<Checkbox
onCheck={this.props.handleAddOption}>
</Checkbox>}>
</ListItem>
When the Checkbox is chosen, I want to push the value into the array in state
handleAddOption = (value) => {
this.setState((....))
}
How do I go about doing that?
UPDATE
found the solution here Passing a function with parameters through props on reactjs
You need to pass the value from the CheckBox component to the function prop. You can do this by:
<ListItem
key={data.indexOf(value)}
primaryText={value}
leftCheckbox={
<Checkbox
onCheck={(e, isChecked) => this.props.handleAddOption(value, isChecked)}>
</Checkbox>}>
</ListItem>
And for your handler:
handleAddOption(value, isChecked) {
this.setState((prevState, props) => {
// Get the old state's value, sticking with immutable pattern
let yourProperty = prevState.yourProperty;
// Determine if the value already exists in your property's array
const exists = yourProperty.find(v => v === value);
if (isChecked) {
// If the checkbox is checked...
// If the property exists, don't do anything
// If it isn't there, add it
!exists && yourProperty.push(value);
} else {
// If the checkbox is NOT checked...
// If the property exists, filter the array to remove it
// If it isn't there, do nothing
exists && (yourProperty = yourProperty.filter(v => v !== value));
}
// Return the new state
return { yourProperty };
});
}
UPDATE
I've updated the solution a bit with documentation and a couple of typos, and created a working example on CodeSandBox here: https://codesandbox.io/s/pj0m4w3qp7
I'm attempting to created a form validation using react-bootstrap, and would like to avoid making a validation function for each input.
I have this input:
<Input
hasFeedback
type="text"
label="Username"
bsStyle={this.state.UsernameStyle}
onChange={this.handleChange.bind(this, 'Username')}
value={this.state.Username}
bsSize="large"
ref="inptUser"
placeholder="Enter Username"
/>
Plus, I have two function to handle the validation and updating of the state:
handleChange: function(name, e) {
var state = {};
state[name] = e.target.value;
this.setState(state);
var namestyle = name + 'Style';
this.setState(this.validationState(namestyle, e));
},
&
validationState: function(style, event) {
var length = event.target.value.length;
if (length > 4) this.setState({style: 'success'});
else if (length > 2) this.setState({style: 'warning'});
return {style};
}
At this point if I change style to Username I can get this solution to work for that specific input as well. I could make an if statement and depending on the string I get in style I can call the appropriate setState, but is their a better way to do this?
Thanks!
var yourVariable = "name";
this.state[yourVariable];
is equivelant to:
this.state.name;
if you want to use your variable in set state you can use this:
var value = "peter";
this.setState({[yourVariable]: value});
is equivelant to:
this.setState({name: value)};
handleChange should collect all of the information required to change state and call setState a single time. vaslidationState should not be changing state. It should just be returning the new style:
validationState(value) {
return (value.length > 4) ? "success" : (value.length > 2) ? "warning" : "error";
}
handleChange(name, e) {
const value = e.target.value;
const style = this.validationState(value);
const stateUpdates = {
[name]: value,
[`${name}Style`]: style
};
this.setState(stateUpdates);
}