React-hook-form + dynamic form: Render element upon dropdown selection - forms

I am working in form using react-hook-form. This form use useFieldArray, it has to be dynamic.
Right now is very simple, it contains a react-select component with a few options and a textfield that get rendered depending on the option that the user select on the select component.
The problem I have is that the textfield component renders when the state updates, which is correct until I add a new group of element to the form. Since the textfield is listening to the same state it doesn't matter which select I use to render the textfield element, it gets rendered in all groups.
I am looking a way to specify which textfield should be rendered when the user change the select.
I the sandbox you can see what I have done. To reproduce the problem click on the "Add"-button and you will see two areas, each one with a select component.
When you choose "Other" in the select component a textfield appears, but not only in the area where the select was changed but in all areas.
How can I avoid that behavior?
https://codesandbox.io/s/vibrant-fast-381q0?file=/src/App.tsx
Extract:
const [isDisabled, setIsDisabled] = useState<boolean>(true);
const { control, handleSubmit, getValues } = useForm<IFormFields>({
defaultValues: {
managerialPositions: [
{
authority: 0,
chiefCategory: 0,
title: 0,
otherTitle: ""
}
]
}
});
useFieldArray implementation:
const {
fields: managerialPositionsFields,
append: managerialPositionsAppend,
remove: managerialPositionsRemove
} = useFieldArray({
name: "managerialPositions",
control
});
Here i update the state when the user select "Other title" in the select component:
const watchChange = (value?: number, i?: number) => {
let values: any = getValues();
if (values.managerialPositions[i].title === 3) {
setIsDisabled(false);
}
};
And here is where I render the button to create a new group of elements and the select component and the textfield that should be rendered if "isDisabled" is false.
{managerialPositionsFields.map((field, index) => {
return (
<Stack className="sectionContainer" key={field.id}>
<Stack horizontal horizontalAlign="space-between">
<StackItem>
<CommandBarButton
iconProps={{ iconName: "AddTo" }}
text="Add"
type="button"
onClick={() => {
managerialPositionsAppend({
authority: 0,
chiefCategory: 0,
title: 0,
otherTitle: ""
});
}}
/>
</StackItem>
</Stack>
<Stack horizontal tokens={{ childrenGap: 20 }}>
<StackItem>
<Label className="select-label requiredIkon">Title</Label>
<Controller
control={control}
name={`managerialPositions.${index}.title`}
render={({ field: { onChange, value, ref } }) => (
<>
<Select
className="react-select-container authoritySelect"
classNamePrefix="react-select"
placeholder="Select title"
options={titelList}
id={`managerialPositions.${index}.title`}
value={
titelList.find((g) => g.value === value)
? titelList.find((g) => g.value === value)
: null
}
onChange={(val) => {
onChange(val.value);
watchChange(val.value, index);
}}
/>
{
// this input is for select validation
<input
tabIndex={-1}
autoComplete="off"
style={{ opacity: 0, height: 0 }}
value={
titelList.find((g) => g.value === value)
? titelList
.find((g) => g.value === value)
.toString()
: ""
}
required={true}
//Without this console will get an error:
onChange={() => {}}
/>
}
</>
)}
/>
</StackItem>
{!isDisabled && (
<StackItem className="">
<Controller
name={`managerialPositions.${index}.otherTitle`}
control={control}
render={({
field: { onChange, name: fieldName, value }
}) => (
<TextField
label="Other title"
name={fieldName}
onChange={(e) => {
onChange(e);
}}
value={value}
/>
)}
/>
</StackItem>
)}
</Stack>
</Stack>
);
})}

Related

Formik - arrayfields -- validation handling

I am working with formik/material ui -- and yup validation. I am struggling to get validation showing/working on field arrays
my schema and validation looks like this currently for each field.
"fields": [
{
"type": "date",
"label": "Start Date",
"name": "startDate",
"validation": yup.date().default(function () { return new Date() }).required("date is required").nullable().typeError(''),
"minDate": moment().add(1, 'weeks'),
"maxDate": moment().add(8, 'weeks'),
"disablePast": true,
"disableFuture": false,
//"disabled": true
},
{
"type": "date",
"label": "End Date",
"name": "endDate",
"validation": yup.date().default(function () { return new Date() }).required("date is required").nullable().typeError(''),
"minDate": moment().add(1, 'weeks'),
"maxDate": moment().add(8, 'weeks'),
"disablePast": true,
"disableFuture": false,
//"disabled": true
}
]
I've seen on formik - they have some validation like this - but how do I apply it my code base for dates?
https://formik.org/docs/api/fieldarray
const schema = Yup.object().shape({
friends: Yup.array()
.of(
Yup.object().shape({
name: Yup.string().min(4, 'too short').required('Required'), // these constraints take precedence
salary: Yup.string().min(3, 'cmon').required('Required'), // these constraints take precedence
})
)
.required('Must have friends') // these constraints are shown if and only if inner constraints are satisfied
.min(3, 'Minimum of 3 friends'),
});
my fieldarray looks like this -- and I believe errors should appear under the field group -- the fields outer border goes red -- but it doesn't seem to work for when I null the date - like is required date working?
<>
<FieldArray
name={item.name}
onChange={event => {
console.log("event field array change", event)
}}
>
{({ insert, remove, push }) => (
<div className="field field-array">
<div className="row" key={0}>
{item.fields.map((ch, inx) => (
<span key={"x"+inx}>
<div className="col-x">
<Field
name={`${item.name}.${ch.name}`}
>
{({
field, // { name, value, onChange, onBlur }
form,
meta,
}) => (
<>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
label={ch.label}
disablePast={ch.disablePast}
disableFuture={ch.disableFuture}
minDate={moment(ch.minDate)}
maxDate={moment(ch.maxDate)}
value={field.value? moment(field.value).format('YYYY-MM-DD'): moment().format('YYYY-MM-DD')}
{...field}
onChange={(value) => {
form.setFieldValue(field.name, value);
this.props.onHandle(field.name, value);
}}
renderInput={(params) => {
return (<TextField {...params} name={field.name} />)
}}
/>
</LocalizationProvider>
{meta.touched && meta.error && (
<div className="error">{meta.error}</div>
)}
</>
)}
</Field>
</div>
{inx === 0 &&
(<span></span>)
}
</span>
))}
</div>
</div>
)}
</FieldArray>
</>
I worked this out
"validation": yup.array().of( yup.object().shape({ firstName: yup.string().min(4, 'too short').required('Required'), lastName: yup.string().min(3, 'cmon').required('Required'), }) ).min(1, 'Minimum of 1 friends')
-- but in the display of errors had to check if it was an array or a string to avoid a render error
under the add more button to display array errors of the main list.
<FormHelperText
error={(form.errors[parent.name] && form.errors[parent.name].length > 0 ? true : false)}
>
{typeof form.errors[parent.name] === "string" &&
<>{form.errors[parent.name]}</>
}
</FormHelperText>
and under the fields - meta errors
{(getHelperVisibility(values, ch)) &&
<FormHelperText
error={meta.touched && (meta.error && meta.error.length > 0 ? true : false)}
>
{meta.error}
</FormHelperText>
}

Fomik FormGroup textfields

I am working on an application and I have made a form framework -- but there is a request to have dual fields.
Min and Max age
rather then having two fields -- "min_age" and "max_age" -- I think they would want an array field "age"
so instead of min_age: 18 and max_age: 33 --- I think they would want an array - age: [18, 33]
I have seen and implemented Radio and Checkbox groups.
-- but when I've tried to swap out the controlled field to a TextField -- the field is malfunctioning and not changing value.
this is what I have got -- as a textfield array group
<>
<FormGroup
row
name={item.name}
disabled={item.disabled}
{...field}
>
{
item.options.map((itm, j) => {
return (
<FormControlLabel key={j}
disabled={item.disabled}
control={
<div className="field field-text">
<TextField
fullWidth={false}
label={itm.label}
value={field.value[j]}
inputProps={{
maxLength: item.charLimit? item.charLimit:null,
autoComplete: item.autoComplete? item.autoComplete:"off"
}}
rows={(item.type === "comment") ? 6 : null}
multiline={(item.type === "comment") ? true : false}
/>
</div>
}
//label={itm.label}
onChange={(e, value) => {
//form.setFieldValue(item.name, value)
//this.props.onHandle(item.name, itm.value);
}}
/>
)
})
}
</FormGroup>
</>
and this is the radio group field that works just fine -- I've not seen any other example where textfields are controlled by the formgroup
<>
<RadioGroup
row
name={item.name}
{...field}
>
{
item.options.map((itm, j) => {
return (
<FormControlLabel key={j}
value={itm.value}
disabled={itm.disabled}
control={<Radio />}
label={itm.label}
onChange={(e, value) => {
//form.setFieldValue(item.name, value)
this.props.onHandle(item.name, itm.value);
}}
/>
)
})
}
</RadioGroup>
</>
I've tried to wrap tags around it - from this example -- but then the field_names are uncontrolled.
https://codesandbox.io/s/formik-multi-step-set-value-context-wrapper-sezzs?file=/src/App.js:2157-2449
:rf: : "111"
:rh: : "222"
age : 18
age_array : [18, 30]
button_field : "3"

ag-Grid Switch goes from checked to unchecked upon down sroll

Getting this weird error where any checked material UI switch becomes unchecked as I scroll down out of that data view.
Below is the Switch return with conditional rendering.
let freeTier = props.params.data.tier;
return freeTier === "FREE" ? (
<FormGroup>
<FormControlLabel
control={
<Switch
disableRipple
focusVisibleClassName={classes.focusVisible}
classes={{
root: classes.root,
switchBase: classes.switchBase,
thumb: classes.thumb,
track: classes.track,
checked: classes.checked
}}
{...props}
/>
}
/>
</FormGroup>
) : null;
And this is where I call the above into cellRendererFramework
cellRendererFramework: params => {
const handleClick = params => {
console.log(params.data);
};
return (
<PaypalSwitch
params={params}
data={params.data}
otherProps={this.props}
onClick={() => handleClick(params)}
/>
);
}
From what it seems like the grid re-renders? I'm getting this error.
ag-Grid: React Component 'cellRendererFramework' not created within
1000ms

React: how to use child FormItem components without getting Warning: validateDOMNesting: <form> cannot appear as a descendant of <form>

Given the parent component, I am using a child component DynamicFieldSet that is a grouping of FormItems. But I am receiving the error:
Warning: validateDOMNesting(...): <form> cannot appear as a descendant of <form>. See CreateTopic > Form > form > ... > DynamicFieldSet > Form > form.
I have tried to remove the <Form> </Form> tags in my child component, but then it is a compile error.
Is there a way I can disable rendering of the child Form view?
Parent component
class CreateTopic extends React.Component {
render() {
return (
<div className="create-topic-container">
<h3>Create an event</h3>
<Form onSubmit={this.handleSubmit}>
<FormItem>...</FormItem>
<FormItem>...</FormItem>
<FormItem>...</FormItem>
<FormItem
{...formItemLayout}
label="Results"
style={{ marginBottom: SPACING_FORM_ITEM }}
>
{getFieldDecorator('results', {
rules: [
{
required: true,
message: 'Results cannot be empty.',
},
],
})(<DynamicFieldSet
form={this.props.form}
/>)}
</FormItem>
</Form>
</div>
);
}
}
DynamicFieldSet - Child component
export class DynamicFieldSet extends React.Component {
render() {
getFieldDecorator('keys', { initialValue: ['0', '1'] });
const keys = getFieldValue('keys');
const formItems = keys.map((k, index) => {
return (
<FormItem
{...formItemLayoutWithOutLabel}
required={false}
key={k}
>
{getFieldDecorator(`results[${k}]`, {
validateTrigger: ['onChange', 'onBlur'],
rules: [
{
required: true,
whitespace: true,
message: 'Result name cannot be empty.',
},
{
validator: this.validateLength,
},
],
})(<Input placeholder={`Result #${index + 1}`} style={{ width: '80%', marginRight: 8 }} />)}
{keys.length > 2 ? (
<Icon
className="dynamic-delete-button"
type="minus-circle-o"
disabled={keys.length === 1}
onClick={() => this.remove(k)}
/>
) : null}
</FormItem>
);
});
return (
<Form>
{formItems}
<FormItem {...formItemLayoutWithOutLabel}>
{keys.length < 10 ? (
<Button type="dashed" onClick={this.add} style={{ width: '80%' }}>
<Icon type="plus" />Add Result
</Button>
) : null}
</FormItem>
</Form>
);
}
}
I faced this issue when using ant design table and turns out its not ant design which throws the warning. It's the web standards description
"Every form must be enclosed within a FORM element. There can be several forms in a single document, but the FORM element can't be nested."
So, there should not be a form tag inside a form tag.
To solve the issue (in our case), remove the Form tag inside the DynamicFieldSet "return" and replace with a div tag
Hope it helps :)
You can portal a form like this:
import Portal from '#material-ui/core/Portal';
const FooComponent = (props) => {
const portalRef = useRef(null);
return <>
<form>
First form
<div ref={portalRef} />
</form>
<Portal container={portalRef.current}>
<form>Another form here</form>
</Portal>
</>;
}
In the example above I use the react material-ui Portal component. But you can try to implement it with React Portals as well
If you're using MUI, the Box component contains an attribute that identifies them as any native HTML container; form is one of them. E.g:
<Box
xs={6}
sx={{
"& > :not(style)": { m: 1, width: "25ch" },
}}
component="form"
noValidate
autoComplete="off"
>
In such case, we just need to delete that attribute, it will default to a DIV. The form will continue to work as expected, and the error will disappear off the console.
In my case this is occur bcoz of i declared <form> inside another <form/> tag.

RadioGroup not working in redux-form

I am using the custom radio button component in redux-form. I am not able to make it work properly. Following the custom component code.
import React, { PropTypes } from 'react';
import {
Col,
ControlLabel,
FormGroup,
Label,
} from 'react-bootstrap';
import { RadioGroup, Radio } from 'react-radio-group';
import _ from 'lodash';
import './style.css';
const FormRadioGroup = props => (
<FormGroup controlId={props.name || ''}>
<Col
componentClass={ControlLabel}
sm={props.labelColValue || 2}
className={props.labelClass || ''}
>
{ props.label || 'Checkbox Label'}
</Col>
<Col sm={props.radioColValue || 6}>
<RadioGroup
name={props.name}
selectedValue={props.input.value}
onChange={value => { props.input.onChange(value); }}
className="react-radio-group-button"
>
{
_.map(props.options, option =>
(
<Label>
<Radio value={option.key} />
{option.value}
</Label>
))
}
</RadioGroup >
</Col>
</FormGroup>
);
FormRadioGroup.propTypes = {
name: PropTypes.string,
labelColValue: PropTypes.number,
labelClass: PropTypes.string,
label: PropTypes.string,
radioColValue: PropTypes.number,
options: PropTypes.array,
disabled: PropTypes.boolean,
radioClass: PropTypes.string,
sendBoolean: PropTypes.boolean,
input: PropTypes.object,
meta: PropTypes.object,
};
export default FormRadioGroup;
In redux-form I am using as follows.
<Field
name="json_boolean_property"
component={FormRadioGroup}
format={value => (value ? 'yes':'no')}
parse={value => (value === 'yes')}
type="radio"
labelColValue={2}
label="Radio Group Label"
radioBoxColValue={6}
sendBoolean
options={[
{ key: 'yes', value: 'Yes' },
{ key: 'no', value: 'No' },
]}
/>
I am not able to set the default value when it comes form the server also when i select the radio button it fire the change event but along with that it also give redux-form/UPDATE_SYNC_ERRORS.
I am not sure, what wrong I am doing but its failing very badly.