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/
Related
Formik has two options controlling when validation occurs: validateOnChange and validateOnBlur.
Problem with using validateOnChange - user will get errors as they start typing, say, email - because it is not valid when you just started to type first letters.
In case of validateOnBlur - let's say user typed in invalid email, left the field, returned and fixed to correct email - the error will be shown anyway until they leave the field, so this also does not work for me.
What I would like to achieve - first time validate a field on blur and starting that point - on change.
Every field should get this treatment individually.
I tried to find native ways to do it but couldn't find any, so I came up with this solution that I do not really like because it is not flawless:
TL;DR: I override standard onBlur and onChange coming from Field's props and a) onBlur - mark field as changed once (probably I could use touched here) b) onChange I check if field was touched once and if so - call validation.
0) Disable validation
<Formik
validateOnChange={false}
validateOnBlur={false}
...
1) Added changedFields to my state
public state: IState = {
changedFields: {}
};
2) Use special method that overrides default onBlur and onChange
<Field name="userName" render={(props: FieldProps<MyFormInterface>) => (
<input type="text" label={'Name'} {...formikCustomValidation(props, this)} />
)}/>
3) The method. Passing Field's props and the parent component where state with changedFields is located.
function formikCustomValidation({ field, form }: FieldProps, ownerCmp: Component<any, any>) {
return {
...field,
onBlur: e => {
if (field.value !== form.initialValues[field.name]) {
ownerCmp.setState({
changedFields: {
...ownerCmp.state.changedFields,
[field.name]: true
}
});
}
field.onBlur(e);
setTimeout(form.validateForm);
},
onChange: e => {
field.onChange(e);
if (ownerCmp.state.changedFields[field.name]) {
setTimeout(form.validateForm);
}
}
};
}
The question is - is there a way to do it easier?
The problem with this solution - if I Submit and untouched form and some errors come up - focus and fix value does not mark field as valid - because it is the first 'touch' and field is still in stat waiting for first blur state that supposed to make listen for 'changes'.
Anyway, maintaining this could be a pain, so looking for better solution.
Thanks!
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'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.
I have a parent component in React that holds the state of the application (I'm building a filterable table). In the state I have a the filters gathered by some inputs, the state looks like this filters: [{"filter1": "value1"}, {"filter2": "value2"}, { }, { }, ...]. The filters inputs are build in a child component FilterBar that receives the filters array as a propertie <FilterBar filters={this.state.filters}/>.
So when the user writes something in the input field it updates the state of the filters (parent) according to the field name and the new input value. So far everything works as I expected. But now I want to implement a button to clean the filters, so when the user click on it the state of the filters become empty (" " string for each filter value). I success on it, when I click the button it setState as I expected, and the table is updated as I wanted. But I'm doing some mistake, the input fields receive the new filters array properties but it doesn't clean the input text (so if I had a word or letter in the input text, it still there after the rerender, and I want the input empty as the state).
Here is the parent function to clean the filters (and I realize that it works as expected):
cleanFilters() {
let filters = this.state.filters.slice();
let f;
for (f in filters) {
filters[f]['value']="";
}
this.setState({filters: filters}, function(){this.updateData();});
}
And here is the function that renders the text inputs on the FilterBar component:
renderFilterInputs() {
var filters = [];
for (let i=0; i < this.props.totalFilters; i++) {
filters.push(<td key={i}><input type='text' className="form-control" defaultValue={this.props.filtersApplied[i].value} onChange={(evt) => this.handleFilterChange(evt, i)} /></td>);
}
return filters;
}
render() {
return (
<tr key="0" className="filters-bar">
{this.renderFilterInputs()}
</tr>
);
}
I don't understand why the text input still save the text when I click the button because it rerenders again and the filter state is empty so the defaultValue of that inputs should be ("") empty. Thanks you
Because you are using defaultValue not value means uncontrolled component. Default value will assign the initial value (default value) to input element only, it will not update the value.
Solution:
Since you are using onChange function with input element and updating the parent state also, so use controlled input field, use value property instead of defaultValue.
value = {this.props.filtersApplied[i].value}
I am using redux-form, and my Component has several FieldArray. One of the FieldArray component generates table like shown in the screenshot. Here each row is a Field component including the checkbox. What I want to achieve is, if checkbox component on that row is checked, then only price field should be required.
I tried to solve this by using validate.js as described in docs, but since, this component has structure as:
<FirstComponent>
<FieldArray
component={SecondComponent}
name="options"
fruits={fruitsList}
/>
</FirstComponent>
In my SecondComponent I am iterating over fruitsList and if length is greater than 1, then I render ThirdComponent. This component is responsible for generating the Fruit lists as show in the screenshot. Having some degree of nesting, when I validate with values, it has a lot of performance lag, my screen freezes until it renders the ThirdComponent. Each component has bit of Fields so can not merge them easily. Any easier way to solve this in elegant way would be helpful. The logic for validating is as follow:
props.fruitsList.map((fruit, index) => {
const isChecked = values.getIn(['fruit', index, 'checked'])
if (isChecked) {
const price = values.getIn(['fruit', index, 'price'])
if (price === undefined || price.length === 0) {
errors.fruits[index] = { price: l('FORM->REQUIRED_FIELD') }
}
}
return errors
})
The synchronous validation function is given all the values in the form. Therefore, assuming your checkbox is a form value, you have all the info you need.
function validate(values) {
const errors = {}
errors.fruits = values.fruits.map(fruit => {
const fruitErrors = {}
if (fruit.checked) { // only do fruit validation if it's checked
if (!fruit.price) {
fruitErrors.price = 'Required' // Bad user!!
}
}
return fruitErrors
})
return errors
}
...
MyGroceryForm = reduxForm({
form: 'groceries',
validate
})(MyGroceryForm)