React-Select with redux-form value undefined after first submit - reactjs

I have integrated React-Select (multi) with redux-form. It works if you select a value and submit. However if you need to submit the form again (ie. they had an invalid field) the React-Select(multi) has an array with undefined values.
<Field
ref={ref => this.refTest = ref}
className = "form-control"
name="categories"
options={
this.state.categories.map(c => {
return { value: c.id, label: c.categoryName };
})
}
onBlur={this.onBlur}
component={multiSelect}
/>
The multiSelect is using this component:
export const multiSelect = (field) => {
console.log(field);
return(
<div>
<Select
name={field.name}
{...field.input}
className="section"
multi={true}
options={field.options}>
</Select>
{field.meta.touched && field.meta.error &&
<label id="basic-error" className="validation-error-label">This field is required.</label>}
</div>
);
};
No matter what I do, the second "Submit" click always has an array with undefined for "category".

After some serious debugging - it was due to how I was performing handleInitialise(). ReduxForm expects arrays of values to be in a specific format, specifically:
[{value: "1", label: "Test"}, ...]
Anyone experiencing 'undefined' values pay careful attention to how you are setting and retrieving values. Avoid mutating the object in both cases as I think this lead to some strange behaviour.

Related

How do i default check a radio button in react?

I am trying to default check the first radio button which the following code helps me to do. When loaded the page the first radio button is checked but the problem i am facing is that it doesn't allow me to check the other buttons that also are present in the array.
constructor(props: any) {
super(props);
this.state = {
selectedSort: '',
sort: ['Apple', 'Orange '],
}
}
this.state.sort.map((sortType:string, index:number) => {
return <span key={`${sortType}${index}` onClick={() => this.setSort(sortType)} >
<input type="radio" id={sortType}
value={this.state.selectedSort}
name={sortType} defaultChecked={index===0}
}/>
<span>{sortType}</span>
})
private setSort = (selectedSort: string) => {
this.setState({
selectedSort: selectedSort
});
}
Issue
The defaultChecked value is a boolean but your condition sortType === 0 will always evaluate false since your sortType is only ever one of your sort state values, i.e. ["Apple", "Orange "].
Solution
If you want the first radio button to be default checked then you should compare against the mapped index.
defaultChecked={index === 0}
Other Issues & Suggestions
Radio button group inputs should all have the same name attribute.
Use a semantic label to wrap your inputs so they are more accessible.
Use the radio input's onChange event callback versus an onClick, to update state.
The sortType values alone should be sufficient for a React key.
Code:
{this.state.sort.map((sortType, index) => (
<label key={sortType}>
<input
type="radio"
id={sortType}
value={selectedSort}
name="sortType"
defaultChecked={index === 0}
onChange={(e) => this.setState({ selectedSort: e.target.id })}
/>
{sortType}
</label>
))}
Additionally, I suggest converting this to a fully controlled input since you have already all the parts for it. Remove the value attribute and use the checked prop. Set what you want the initial checked state to be. This will allow you have have already valid checked state.
state = {
selectedSort: 'Apple',
sort: ['Apple', 'Orange '],
}
{this.state.sort.map((sortType, index) => (
<label key={sortType}>
<input
type="radio"
id={sortType}
name="sortType"
checked={sortType === this.state.selectedSort}
onChange={(e) => this.setState({ selectedSort: e.target.id })}
/>
{sortType}
</label>
))}
Demo

In redux-form, show all validation errors in Field-Level Validation

I am new in react and am trying to create a form where I show multiple validation errors in each field.
This is the field
<Field
name="field_name"
type="text"
component={renderField}
customClass="long-input"
normalize={normAll([...])}
validate={[rules.required, rules.another_rule, rules.this_rule]}
/>
This is my renderField
const renderField = ({ input, label, customClass, disabled, type, meta: { touched, error } }) => (
<div class={customClass}>
<label>{label}</label>
<div>
<input {...input} type={type} disabled={disabled} />
{console.log(error)}
<div class="error-message">{touched && error && <span>{error}</span>}</div>
</div>
</div>
)
And here is the Field-Level Validation rules
rules.required = value => (value || typeof value === 'number' ? undefined : 'Required')
rules.another_rule = value => (value && value === '3' ? undefined : 'This is not 3')
rules.this_rule = value => (value && value === '5' ? undefined : 'This is not 5')
The problem is that if the first rule in the list is not validated, it doesn't show the validation errors for the other rules.
Is there any way to show all of them? As you see, I checked with console.log(error) and only the first one is passed in the renderField.
I'm not sure about Field Level validation, but I believe that with Sumbit Validation you could push each error into an array and then map that array in your custom Input component.
I haven't tested it though, I'm just proposing a solution.

How to update specific form field in formik when value changes in redux store?

I am using formik in my react application. I have initialized all the form values from a state. But I wanted to update a specific form field if props in the redux store change. Below is my formik form:
<Formik
initialValues={this.state.branchData}
enableReinitialize={true}
onSubmit={this.formSubmit} >
{({ values, setFieldValue }) => (
<Form >
<Row>
<Col lg="12">
<FormGroup className="col-6">
<Input type="select" value={values.StateId} name="StateId" onChange={this.handleChange(setFieldValue)}>
<option value='' >Select State</option>
{this.props.stateList && this.props.stateList.map((item, i) => (
<option key={i} value={item.StateId}>
{item.StateName}
</option>
))}
</Input>
{this.validator.message('state', values.StateId, 'required')}
<Label className="impo_label">Select State</Label>
</FormGroup>
<FormGroup className="col-6">
<Field className="form-control" name="GstNo" type="text" maxLength="15" />
<Label className="impo_label">GST No</Label>
{this.validator.message('gst no', values.GstNo, 'required|min:15|max:15')}
</FormGroup>
</Col>
</Row>
</Form>
)}
Now, When I change state from the dropdown, an api will get invoke which will return gst no by state id. I want to update the gst no field with the value recieved from the api in the props. If the gst no received is null then I want user to input the gst no, but if gst no is recieved from the api, I want to update the gst no field in form with the value received in props and disable the form input. I cannot update the gstno in this.state.branchData as this will reset the form values with values in this.state.branchData.
Does anyone have any idea about how to achieve this in formik?
You can enable enableReinitialize property of Formik to update the field.
<Formik
enableReinitialize={true}
initialValues={...}
>
....
When data comes from api. You can just easy change value of field like this:
setFieldValue('GstNo', valueFromApi)
It's better to use setFieldValue, When the API gets some value or any props/dependency value changes, you can use useEffect to handle it,
For Example:
I'm getting setFieldValue function as a prop from parent component.
useEffect(() => {
if (
values.parentCategoryId &&
!categories?.data?.find(category => category.id === values.categoryId)
?.hasFurnishingStatus
) {
setFieldValue('furnishingStatus', '');
}
}, [values?.categoryId]);
Here the furnishingStatus is the name of the field for which I'm setting the value.
<Field name="furnishingStatus" component={RadioGroup} row>{...}</Field>
you can use this method
useEffect(() => {
// get user and set form fields
userService.getById(id).then(user => {
const fields = ['title', 'firstName', 'lastName', 'email', 'role'];
fields.forEach(field => setFieldValue(field, user[field], false));
setUser(user);
});
}, []);
I defined a state variable for maintaining the gstNo that will be fetched from the API based on the state id selected from the drop-down. Like this:
this.state = {
gstNo: null,
};
So, when the API will get invoked and the response is received, gstNo props will be updated in the redux store.
this.state.gstNo will be updated inside the UNSAFE_componentWillReceiveProps(), when this.props.gstNo changes. Like Below:
if (this.props.gstNo !== nextprops.gstNo) {
let gstNo;
if (nextprops.gstNo) {
gstNo = nextprops.gstNo;
}
else {
gstNo = null;
}
this.setState({
gstNo: gstNo,
});
And to assign this.state.gstNo value to formik, I simply wrote this:
<FormGroup className="col-6">
<Field
className="form-control"
name="GstNo"
disabled={this.state.gstNo}
type="text"
maxLength="15"
value={
(values.GstNo = this
.state.gstNo
? this.state.gstNo
: values.GstNo)
}
/>
<Label className="impo_label">
GST No
</Label>
</FormGroup>
So as soon as this.state.gstNo would be having some value, it would be reflected in formik form's text field and the textbox will be disabled.

Send an array to a Redux Form to generate options in a select drop down

Working on the setting security questions part of the authentication. The server responds with a list of the 20 or so questions in the form of an array. I can get the form and select box to render, but only one option at a time by specifying the index.
If I try to send the entire array I get an undefined error. Tried to do a for loop in the ` to iterate through each index, which generated an error.
I'm trying to figure out how to pass the entire array so it makes an option for each entry in the array.
This is what I have so far:
// ./set_security_questions.js
// This renders errors regarding the form inputs
renderField(field) {
const {
label,
placeholder,
type,
name,
questions,
meta: {
touched,
error
}
} = field;
return (
<div className='form-group'>
<label>{label}</label>
<select className='form-control' name={name}>
<option value={questions}>{questions}
</option>}
</select>
<input
className='form-control'
type={type}
placeholder={placeholder}
{...field.input}
/>
<div className='text-danger'>
{touched ? error : ""}
</div>
</div>
);
}
// The main body to be rendered
render() {
if (this.props.permitRender) {
const { handleSubmit } = this.props;
return (
<div>
<h3>Set Security Questions</h3>
<p>Please select two security questions that will be easy for you to remember.</p>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
{this.renderAlert()}
<Field
questions={this.props.questions.data.security_question}
label='Question 1'
placeholder='Answer 1'
name='a1'
type='text'
component={this.renderField}
/>
<Field
label='Question 2'
placeholder='Answer 2'
name='a2'
type='text'
component={this.renderField}
/>
<button type="submit" className="btn btn-primary">Submit</button>
</form>
</div>
);
} else if (!this.props.permitRender) {
return (
<div> { this.renderAlert() } </div>
);
}
}
In addition, my JSON that comes back from the server looks pretty strange, so I will need to iron that out, but still wondering how to pass an array into the Form. this.props.questions.data:
data:
id: 123
key: "key_request"
security_q1: null
security_q2: null
security_question: Array(29)
0: {security_question: "In what city or town did you meet your spouse / partner?"}
1: {security_question: "In what city or town did your mother and father meet?"}
2: {security_question: "In what town or city was your first full time job?"}
3: {security_question: "What is the first name of your spouse's father?"}
......
Here is an example I'm currently using to populate a set of checkboxes. There isn't any special logic going on in the checkbox component, I just am using custom html for styling purposes.
Here is my data, a simple array:
const env = {
FONT_FORMATS: ['OTF', 'TTF', 'WOFF', 'WOFF2']
}
Here is the code in the render() function for my component. I'm storing each item in the Redux form under the object key -> fileTypes.
<ul className='tags'>
{envConfig.FONT_FORMATS.map((tag: string) => (
<Field
key={tag}
value={tag}
name={`fileTypes.[${tag}]`}
id={tag.toLowerCase().replace(' ', '_')}
type='checkbox'
component={checkBox}
label={tag}
/>
))}
</ul>
I hope this helps you out!

Sync validation on a radiogroup (v6)

How can I display sync-validation errors for my radiogroup when using the v6 version of redux-form? One option would be to create multiple renderField functions, in which only one (the last one) would display the errors. I currently have it setup like this:
const renderField = ({ input, meta: { touched, error } }) => (
<span>
<input
{...input}
/>
{
touched &&
error &&
<div>
{error}
</div>
}
</span>
);
// Form-component
// I loop trough multiple values
<Field
type="radio"
component={renderField}
id={`${field.name}-${value}`}
value={value}
name={field.name}
/>
<Field
type="radio"
id={`${field.name}-${value}`}
value={value}
name={field.name}
/>
In this way, the errors get presented multiple times (for each value). I could pass an extra prop when it is the last value, to enable the errors for just that field. Although that would work, it feels kinda nasty.
This is an interesting question. Another option would be to write a component that was just in charge of rendering errors.
const renderError = ({ meta: { touched, error } }) =>
touched && error && <div>{error}</div>
Then, after you had rendered your radio buttons, you could do a:
<Field name={field.name} component={renderError}/>

Resources