Validate one field based on another Field in redux Form - reactjs

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)

Related

unable to edit pre populated textboxes using react

I have a form that has textboxes that are prepopulated from an WebAPI. When I try to delete the text in the textbox to make a change it doesn't delete the prepopulate text. If I try to type over the text, I can see only the first letter of the word I'm typing in the console, but nothing changes on the UI: It' like the textbox is in readonly mode WHICH IT IS NOT
const Details = () => {
const [ server, setServer] = useState([]);
useEffect(() = > {
getServerNames();
}
const getServerName = async() => {
//gets the list of server and their details from the API
}
const serverNameChange = (e) => {
setServer(e.target.value);
}
return (
<div>
{ details.map((data) => {
<input type="text" name="server" onChange={serverNameChange} value={data.serverName} />
))}
</div>
)
};
What am I missing to allow the users to edit the textbox? The textbox is prepopulated with data, however, it can be changed. This is only happening on textboxes that are prepopulated. I don't want to click an Edit button, I want to give the user the ability to make a change in the textbox and then save it.
That might be due to the fact, that data.serverName never changes. It’s basically a static value. If you set the value of an Input, you have to handle the changes (when typing) in the onchange event.
From what I assume, according to your code is that you have multiple input boxes with preloaded values in them and you want to change your serverName if one of them get changed by the value that is in the textinput.
If so, map your details into a state variable:
const [serverNames, setServerNames] = useState(details.map( data => data.serverName));
Map the inputs from your state variable like so:
{serverNames.map((name,index) => {
< input type="text" name="server" onChange={(e) => {updateServerState(e, index)}} value={serverNames[index]} />
}
}
And your updateServerState method looks like that:
updateServerState(e, index) {
let myStateData = [...serverNames];
myStateData[index] = e.target.value;
setServerNames(myStateData);
setServer(e.target.value);
}
Caution: I haven‘t tested the code, just wrote it down. But that should give you an idea of how to solve your issue.
TL;DR; Never use non-state variables for a dynamic value.

How to use react-select-table's rows as options to react-select's input field

I have an input field that should accept multiple inputs from options that are already set. - This input field needs to appear like tag input fields so I used the React-select library:
The set options are going to come from a react-bootstrap-table so I disabled the built-in dropdown in the react-select input field.
The problem is that I cannot remove the selected items by deselecting the row from the react-bootstrap-table.
I think I am doing something wrong on how I'm passing the values between the input field and the table but can't figure it out.
Here is the codesandbox
The problem is you try to compare two Objects, however there is no generic means to determine that an object is equal to another.
You could JSON.stringify them both and compare but thats not reliable way.
Instead, you can filter the array by object key, lets say label.
In this case, your function should look like this:
const setSelected = (row: any, isSelect: any, rowIndex: any, e: any) => {
if (isSelect) {
setSelectProducts([...selectedProducts, row]);
} else {
const newArray = selectedProducts.filter(
(item) => item.label !== row.label
);
setSelectProducts(newArray);
}
};
Here is codesandbox to preview
#Edit
If you wanna do something onClear
onChange={(selectedOption, triggeredAction) => {
if (triggeredAction.action === 'clear') {
// Clear happened
}

Material UI 5 Stepper + react hook form 7

I want multi step form. I have MUI5 and react-hook-form 7.
I have 2 form one is signup and second is address.
I just want to update this example: https://codesandbox.io/s/unruffled-river-9fkxo?file=/src/MultiStepForm.js:2597-2608.
I tried something like this https://codesandbox.io/s/mui5-react-hook-form-7-multi-step-form-9idkw?file=/src/App.js
but value is reset on step change and also validation is not working.
I also want to get those values in last step and submit first.
and Can i create defaultValues object step wise?
const defaultValues = {
"step1": {
firstname: "",
lastname: "",
}
"step2": {
address: "",
city: ""
}
}
because I want to submit first form data. and rest of data on last step.
So is this approach is okay? or should I have to do another way?
Can you please help me.
You can save to values to localStorage and if user go back you can easily set defaultValues.
For example:
useEffect(() => {
if (location.pathname === '/step1') {
setActiveStep(3);
} else if (location.pathname === '/step2') {
setActiveStep(0);
} else if (location.pathname === '/step3') {
setActiveStep(1);
} else if (location.pathname === '/step4') {
setActiveStep(2);
} else {
}
}, [location.pathname]);
There were a lot of errors in your code. I have changed a few things in it, and you can find the working code here (Works at least)
Here are the errors I found:
In InputController.js, you were using useForm instead of useFormContext
const { control } = useForm(); this is wrong
const { control } = useFormContext(); this is correct
In App.js, firstname and lastname variables (in defaultValues) were written differently from how they were written in the validation schema.
in defaultValues, you wrote firstname, but in validation schema you wrote firstName (notice the difference in camelCase). The same issue was present in the lastname variable. This is why validation was not working.
I moved the submit button into the App.js so that It can be rendered conditionally. You only want it to show on the last step :)
Also note that you need to add defaultValues as a prop on the controller component for the TextField because it's a controlled component. Check how I solved this in InputController.js. Lastly, react hook form stores the value for you (this is taken care of by spreading the {...field} on the TextField.
Hope this helps.

How to associate multiple component in one redux-form Field component?

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/

redux-form: How do I dynamically exclude one sync validator?

My redux-form decorated form component conditionally has an email field, depending on my redux state (depending on whether or not the user is a guest.)
I only want to sync-validate that field when it is present (conditionally rendered.) Currently the form's validator includes an email field validator, and this validator runs even when the field has been excluded from the form, during render.
When I "instantiate" the form's validator, and when I pass the validator as an argument to the redux-form decorator, state isn't known. So I'm currently unable to decide whether to include the email field validator, in either of these places.
What's the best way to dynamically include / exclude a single field's redux-form validator, at "runtime" / "validation time", based on state?
MyForm.js
import validate from './MyFormValidator';
// [form class whose render optionally shows the email component]
export default reduxForm(
{
form: 'myForm',
fields,
validate
}
)(MyForm)
MyFormValidator.js
import {createValidator, required, email} from '../../utils/validation';
export default createValidator({
email: [email],
country: [required],
// ...
});
utils/validation.js
export function email(value) {
const emailRegex = /.../i;
if (!emailRegex.exec(value)) {
return 'Please provide a valid email address.';
}
}
export function required(value, message) {
if (isEmpty(value)) {
return message || 'Required.';
}
}
export function createValidator(rules) {
return (data = {}) => {
const errors = {};
Object.keys(rules).forEach((key) => {
const rule = join([].concat(rules[key]));
const error = rule(data[key], data);
if (error) {
errors[key] = error;
}
});
return errors;
};
}
My sync validation is modeled after this implementation, linked to from the 4.2.0 redux-form docs (I'm using 5.3.1): https://github.com/erikras/react-redux-universal-hot-example/blob/master/src/utils/validation.js
Thanks to #lewiscowper who suggested this solution on some JavaScript Slack.
Since the input is a text field, the value will come through as an empty string if the input was rendered.
So if value is null/undefined, we can skip validation for the field, since we know it wasn't rendered.
To avoid false-positive usages of the validator, I renamed it emailUnlessNull.
export function emailUnlessNull(value) {
// Don't validate the text field if it's value is undefined (if it wasn't included in the form)
if (value == null) {
return;
}
const emailRegex = /.../i;
if (!emailRegex.exec(value)) {
return 'Please provide a valid email address.';
}
}
In the end I think a solution that totally excludes the validator --- and doesn't complicate the validator's logic and it's usage --- would be much better, but for now this works.
Happy to accept a better answer.
EDIT:
You also have to make sure that, in the optional-field-render scenario, you also trigger it's value to be an empty string. Before it's been touched, its value comes through the validator as null.
MyForm.js
componentWillReceiveProps(nextProps) {
const { values, fields, isGuestOrder } = this.props;
if (isGuestOrder && values.email == null) {
fields.email.onChange('');
}
}
If you want to consider React-Redux-Form, this is already built-in:
import { Field } from 'react-redux-form';
import { required, email } from '../../utils/validation';
// inside render():
<Field model="user.email"
errors={{ email }}
/>
Two things will happen:
The error validators (in error) will run on every change. You can set validateOn="blur" to modify this behavior.
Validation will only occur if the <Field> is rendered. If the <Field> is unmounted, validation will be reset for that field.

Resources