React-Hook-Form conditional fields in a map function - reactjs

I'm curious if anyone has used conditional fields in react-hook-from but within a map function. I've got the basic rendering happening, but since it is in a list and they all refer to the same .map() criteria, it is populating the conditional input for all fields, regardless if they are checked. Heres the basic idea for using conditional fields in RHF: https://codesandbox.io/s/react-hook-form-conditional-fields-qgr41
Heres what I've got so far:
{goals && goals.map((goal) => (
<>
<FormControlLabel
control={
<Switch
key={goal.title}
//id..? marked? title?
{...register('marked')}
name='marked'
value={goal.marked}
/>}
label={goal.title}
/>
<br />
{marked && (
<>
<Input
key={goal.title}
{...register('goal.note')}
id="note"
type='text'
label="Progress Note"
name="note"
onChange={(e) => {
e.target.value = e.target.value
}}
/>
<br />
</>
) }
The problem is that from the UI side, when I toggle the switch (or mark a checkbox), that all the fields populated in the map are referring to the same prop in the switch, 'marked'. So when toggling for 1, it populates the additional input field for all elements in the map function.
An additional issue I have yet to look into is passing the data for the individual fields to the data collected by the form for the submit. Right now, my assumption is that all fields would be treated as one value, since the form is recognizing them all together. So ideally if one item in the map is toggled and additional info is provided, then only those values are passed to the form for the submit, rather than all fields (toggled or not) being passed.
UPDATE
So I got the toggle functioning by creating a child component in order to more easily manage the state of the toggle, and leveraging the index within the props so the browser would not treat each conditional field as the same:
parent component:
{goals && goals.map((goal, index) => (
<GoalInput
goal={goal}
index={index}
register={register}
control={control}
errors={errors}
/>
new child component:
function GoalInput({ goal, index, register, control, errors }) {
const [toggle, setToggle] = useState(false)
return (
<>
<FormControlLabel
control={
<Switch
key={index}
{...register(`goals.${index}.marked`)}
checked={toggle}
name={`goals.${index}.marked`}
value={toggle}
onChange={() => setToggle(!toggle)}
/>
}
label={goal.title}
/>
<br />
{toggle ? (
<>
<Controller
control={control}
name={`goals.${index}.note`}
id={`goals.${index}.note`}
render={({field}) => (
<Input
type='text'
index={index}
error={!!errors.note}
value={field.value}
onChange={(e)=>field.onChange(e)}
label="Progress Note"
/>
)}
/>
<br />
</>
) : <></>}
</>
)
}
So the toggles work independently, appropriately record whether the value of the toggle is true/false, and the subsquent conditional input in being tracked as well. Still have some struggles with the data being passed correctly through to the backend, but that is another issue. Hope this helps anyone coming along.

Related

How to use material UI Checkbox with formik?

I am not able to code in the right way such that formik.values will reflect into Material UI's Checkbox
Checkboxes are a little tricky to include in third party forms packages, and Material UI doesn't help with that either.
What you need to do is use the FormControlLabel component to handle the onChangge event that formik needs. Then on the Checkbox you just set the checked prop to be whatever formik has in its values.
Here is an example I use for a Checkbox to set an isAdmin value.
const formik = useFormik({
initialValues:{
isAdmin: false
},
});
<FormControlLabel
control={<Checkbox checked={formik.values.isAdmin} />}
label="is Admin"
name="isAdmin"
onChange={formik.handleChange}
/>
Using 'as'
Use the as prop with Formik Field and pass any props required to the Field itself. It will pass them onto FormControlLabel. For example, here the label field is passed down to FormControlLabel:
<Field
type="checkbox"
name="myFormikName"
as={FormControlLabel}
control={<Checkbox />}
label="This will trigger my formik name field"
/>
Multiple checkboxes using 'as'
If you have multiple checkboxes with the same name (array) you need to pass the "checked" value like so. Checks if the Formik 'name' array includes the name.
const { values } = useFormikContext()
render (
{names?.map(name => (
<Field
type="checkbox"
name="names"
value={name.fullName}
key={name.id}
as={FormControlLabel}
control={<Checkbox/>}
checked={values.names.includes(name.fullName)}
label={name.fullName}
/>
))}
)
Using 'setFieldValue'
const { values, setFieldValue } = useFormikContext()
<FormControlLabel
control={<Checkbox />}
label="My mui label"
checked={values.myMuiCheck}
onChange={() =>
setFieldValue(
'myMuiCheck',
!values.myMuiCheck,
)
}
/>

Cannot get Material UI radio buttons to work with Formik

I am trying to use Material UI radio buttons with Formik, and they are not clicking properly. I've reduced the problem to the following example: https://codesandbox.io/s/amazing-currying-s5vn0
If anyone knows what I might be doing wrong, or if there is a bug in either system, then please let me know. When clicking on the buttons in the above example, they do not stay clicked. I have a more complex react functional component that uses other library components, so I cannot include it here. It is behaving a little differently: the buttons stay clicked even after clicking a different button. It may or may not be the same issue. Thanks in advance.
You need to be rendering the radio buttons inside of the FormikRadioGroup component you are rendering as a Formik Field. That way you can actually pass the props being managed by Formik down to the components to be used, as well as allow the RadioGroup component to make sure only one button is clicked at a time. I added an options prop to provide a way to pass an array of radio options and removed all of the elements you were rendering outside of that component:
const FormikRadioGroup = ({
field,
form: { touched, errors },
name,
options,
...props
}) => {
return (
<React.Fragment>
<RadioGroup {...field} {...props} name={name}>
{options.map(option => (
<FormControlLabel value={option} control={<Radio />} label={option} />
))}
</RadioGroup>
{touched[fieldName] && errors[fieldName] && (
<React.Fragment>{errors[fieldName]}</React.Fragment>
)}
</React.Fragment>
);
};
Fork here.
EDIT: Updated the sandbox with an alternate example using a render function as a child within the Field component.
import { FormControlLabel, Radio, LinearProgress } from '#material-ui/core';
import { Formik, Field } from 'formik';
import { RadioGroup } from 'formik-material-ui';
<Formik {...otherProps}>
{({ isSubmitting }) => (
<Field component={RadioGroup} name="activity">
<FormControlLabel
value="painting"
control={<Radio disabled={isSubmitting} />}
label="Painting"
disabled={isSubmitting}
/>
<FormControlLabel
value="drawing"
control={<Radio disabled={isSubmitting} />}
label="Drawing"
disabled={isSubmitting}
/>
<FormControlLabel
value="none"
control={<Radio disabled={isSubmitting} />}
label="None"
disabled
/>
</Field>
)}
</Formik>;
Now this is documented! Using RadioGroup from formik-material-ui.
https://stackworx.github.io/formik-material-ui/docs/api/material-ui/

Conditionally Checking for Errors with Formik Field Component

I have set up the following form using Formik:
const RenderForm = ({ errors, isSubmitting, isValid, touched }) => {
return (
<Form>
<h3>Sign Up</h3>
<Email
name="email"
error={touched.email && errors.email ? 'error' : null}
/>
<Password
name="password"
error={touched.password && errors.password ? 'error' : null}
/>
<Button disabled={!isValid || isSubmitting} type="submit">
Submit
</Button>
{/* {errors.firebaseErrorMessage && ( */}
{/* <Help>{errors.firebaseErrorMessage}</Help> */}
{/* )} */}
</Form>
)
}
The Email component and the Password components are just wrappers for the Formik Field Component. I want to be able to add a red border a particular field if it fails validation.
To do that, I have set up a conditional for the error prop. IF it is touched AND IF the error is passed, then it renders that prop with a value of 'error'.
Now there are a few problems with this.
FIRST PROBLEM
I don't need to render a value of 'error' (or any other value) to the error attribute. I just need to have the attribute 'error' itself (without any value) and then it will be styled appropriately.
SECOND PROBLEM
It's a bit annoying to write out the same conditional test for each field. I would like to know if there is something like the ErrorMessage component, except for a prop. Basically, something like this:
<Email name="email" errorProp />
<Password name="password" errorProp />
The idea being that errorProp will set an attribute of 'error' if an error exists and nothing if an error does not exist.
So here is my question...
How can I create my own errorProp or, at the very least, simplify what I am trying to do?
Thanks.

react final form conditional form fields are not getting values while rendered

I have a field in a form that I want to render based on another field's value.
I listen to "OnChange" event of the field, and then trigger form.change of the new field (which is not rendered yet), but it won't get the value while rendered.
I created a sandbox for the issue: https://codesandbox.io/embed/priceless-keldysh-gfd3b
THe expected result: when selecting "Heat", scaling factor should be displayed and get the value '1'
What is the best practice to solve that? assuming there are a lot of dependencies inside a form.
Since <Form /> need visible <Field /> for subscribe
but your field scalingFactor would be removed when values.meterType !== "heat"
{values.meterType && values.meterType === "heat" && (
<Field name="scalingFactor" component="input" />
)}
You need to change your code as bellow:
<Field name="scalingFactor">
{({ input }) => {
return (
<React.Fragment>
{values.meterType && values.meterType === "heat" &&
<input {...input} />
}
</React.Fragment>
);
}}
</Field>

Redux-Form—Create Password Field with »retype«

I am building a redux form and I would like to create a »Retype Password« field which is rendered next to the password field. I have this code:
const renderRow = (field) => {
return (
<div className={`form-row ${field.type}`}>
<label>{field.label}</label>
<Field field={field} component={renderPassword} name={field.name} />
</div>
);
}
const renderPassword = ({field, input, meta: { error, touched }}) => {
return (
<div className="password-inputs-wrap">
<input type="password" {...input} />
<input type="password" {...input} name="password-retype" />
{touched && error && <RenderError error={error} />}
</div>
);
}
Right now this renders two password fields, but changing one, changes the other at the same time.
The renderRow is a bit simplified here, since the component for that field is not hard coded as show here. So this password field is an exception, since it consists of two <input>s. For all other fields, which consist of a single <input>/<select />, etc, it works fine.
How can I »decouple« this retype <input> from the first one?
You need two fields (two Fields).

Resources