Final Form reset specific field value - react-final-form

I have two Field components, a Parent and a Child. As Child is dependant on the Parent, I want to reset Child whenever Parent is updated.
I tried this:
<Field name="parent">
{(props) => {
return (
<Parent
onChange={(newValue) => {
props.input.onChange(newValue);
form.change('child', undefined);
form.resetFieldState('child');
}}
value={props.input.value}
/>
);
}}
</Field>
<Field name="child">
{(props) => {
return (
<Child
value={props.input.value} // the value is not updated here,
onChange={props.input.onChange}
/>
);
}}
</Field>
but it does not seem to be working. What is the right way to do it?

You can achieve this using form decorator, for example, having a form with two fields firstName and lastName, to reset lastName whenever firstName is changed you could:
<Form
onSubmit={onSubmit}
initialValues={{ stooge: "larry", employed: false }}
decorators={[
(formApi) => {
let before = formApi.getState();
const unsubscribe = formApi.subscribe(
(state) => {
if (before.values.firstName !== state.values.firstName) {
formApi.change("lastName", "");
}
before = state;
},
{ values: true }
);
return unsubscribe;
}
]}>...</Form>
in the example above, we subscribe for form state changes and compare the field value with the previous one, when it's changed, reset another one. Here is a codesandbox https://codesandbox.io/s/so-final-form-reset-specific-field-value-drvr4l?file=/index.js:593-619
Oh, btw, there is ready-made package for this: https://github.com/final-form/final-form-calculate

Related

How to setValue and getValue when using useFormContext in map Function React hook form

How to setValues in textField and get Values when updates it.
Child Component
const MyTextField = () => {
const { control } = useFormContext()
<Controller control={control}
name={name}
render={({
field: {onChange, onBlur, value},
fieldState: { error }
}) => {
return (<TextField fullWidth />
)
}}
/>
)
}
export default MyTextField
let data = [ 100, 200, 300]
data.map(item => (<MyTextField name='price' value={item} />)
If you want to somehow transform the input value whenever it changes you can do this
const MyTextField = () => {
const { control } = useFormContext()
<Controller control={control}
name={name}
render={({
field,
fieldState: { error }
}) => {
return (<TextField {...field} fullWidth onChange={({target:{value}}) => {
// do somethign with the value
// and then call the field onChange to update the field state
field.onChange(value)
}} />
)
}}
/>
)
}
export default MyTextField
Yo don't need to call getValues while you're inside Controller, that's field.value for. If you wnat to get the input value outside the Controller then the getValue must be the one returned by useForm

react-hook-form custom resolver only checking after submit

I'm building an abstract form component with react-hook-form and Yup for validation. The form works, and validation works, but only after the submit button is pressed.
It's on codesandbox, but ...
import React, { cloneElement } from "react";
import "./styles.css";
import { Controller, FormProvider, useForm } from "react-hook-form";
import { yupResolver } from "#hookform/resolvers/yup";
import { string as yupString, object as yupObject } from "yup";
import {
Box,
Button,
Dialog,
DialogActions,
DialogContent,
TextField
} from "#mui/material";
let renderCount = 0;
export const FormContent = ({ content }) => {
return content.map((item, i) => {
const name = item.component.props.name;
return (
<Controller
key={name + "_" + i}
name={name}
defaultValue=""
render={({ field, fieldState: { error }, formState: { isDirty } }) => {
return cloneElement(item.component, {
...field,
error: isDirty && !!error,
helperText: isDirty && error?.message,
FormHelperTextProps: { error: true }
});
}}
/>
);
});
};
export default function App() {
renderCount++;
const usernameInput = {
validation: yupString().required("Username is required"),
component: (
<TextField required label="Username" name="username" type="text" />
)
};
const passwordInput = {
validation: yupString().required("Password is required"),
component: <TextField required label="Password" name="password" />
};
const content = [usernameInput, passwordInput];
let validationSchema = yupObject().shape({});
// construct schema
content.forEach((item) => {
validationSchema = validationSchema.concat(
yupObject().shape({
[item.component.props.name]: item.validation
})
);
});
const methods = useForm({
resolver: yupResolver(validationSchema)
});
const onFormSubmit = (data) => {
console.log(data);
};
return (
<Dialog open>
<Box>Render Count: {renderCount}</Box>
<FormProvider {...methods}>
<Box component="form" onSubmit={methods.handleSubmit(onFormSubmit)}>
<DialogContent>
<FormContent content={content} />
</DialogContent>
<DialogActions>
<Button
type="submit"
fullWidth
name="login"
variant="contained"
color="primary"
size="large"
>
Login
</Button>
</DialogActions>
</Box>
</FormProvider>
</Dialog>
);
}
If you type some data in the fields, and then erase the data without pressing the button, nothing happens. If you leave the fields empty and press the button, it gives the native component error message for required (i.e., it doesn't do the Yup resolving). But, if you enter some data, press the button, and then erase the data, then the Yup validation kicks in. How do I make it work before the button is pressed?
You need to remove required prop from input components because otherwise native html validation will kick in.
And if you want start validation before pressing submit button you need to use some other mode for form, for example:
const methods = useForm({
resolver: yupResolver(validationSchema),
mode: 'onChange' // or 'onBlur' for example
});
Codesandbox
More info in the docs

Form handling without Controller, react-hook-form

I have been working on form using the react-hook-form for quite a long, I have used v6 and v7 of the library.
While handling form for material-ui, react-hook-form docs states that we should use Controller Component, link for the doc.
When using the Controller we have to write a lot of code which I personally don't like, so I have been using a different approach to handle form using watch, without using Controller Component.
Till now I have not faced any issues using this approach and I have to write less code as well.
I just wanted to know the drawbacks of this approach, as I don't want to change my code in future because of performance issues.
import { Button, Container, FormLabel, Grid, MenuItem, Select } from '#mui/material'
import { useForm } from 'react-hook-form'
export default function Home() {
const countries = [
{ id: 1, name: 'India' },
{ id: 2, name: 'USA' },
{ id: 3, name: 'Canada' }
]
const { register, handleSubmit, control, watch } = useForm({
defaultValues: {
country: 'India'
}
})
const watchFields = watch()
const submit = (data) => {
console.log({ data })
}
return (
<Container sx={{ marginTop: '10px' }}>
<form onSubmit={handleSubmit(submit)}>
<Grid item xs={6}>
<FormLabel>Please select your country</FormLabel>
<Select
fullWidth
defaultValue={'India'}
value={watchFields.country}
label="Country"
{...register('country')}
>
{countries.map(country => <MenuItem key={country.id} value={country.name}>{country.name}</MenuItem>)}
</Select>
</Grid>
<Button type='submit'>Submit</Button>
</form>
</Container>
)
}

Transform onChange when using register

Is it possible to trasnform outgoing values with just register and v7 of react-hook-form?
I did this by overwriting the e I pass to onChange however it never becomes the value I set it. 325
const { onChange, ...registration } = props.form.register('foo');
const handleChange = e => {
const value = e.target.value;
const transformedValue = 325;
onChange({
type: e.type,
target: {
name: e.target.name,
type: e.target.type,
value: transformedValue
}
});
};
return <input {...registration} onChange={handleChange} />
The problem here is that no ref can be initialised by your call to register outside the <input /> element.
For the registration of a ref you can either add a custom register, this would give you the possibility to modify the current value of your <input /> element after a change via the setValue method (which is provided by useForm). Or another way would be to share your ref to your <input />.
However, I think that setValueAs should also work well in your case. This will run the provided function before returning your input value. Here is the relevant section from the docs.
<input {...register("foo", { setValueAs: value => `${value} 325` })} />

Should a component adapter wrap just the component or should it also wrap <Field>

Best concept for wrapping react-final-form components
Background
I'm using react-native and will probably start using react-final-form as soon as I have wrapped my head around it. I will build a component library based on react-native-paper.
All examples I've seen on wrapping 3:rd party components for use as react-final-form fields (i.e. <Field>), does not wrap the actual field component. Instead the wrapped component is injected via the component property of the Field component.
If I'm doing all the work wrapping the components, why not go all the way? Since I haven't found any examples of the "complete wrapping", I'm kind of worried that it's not a good idea.
Both solutions seems to work fine, btw. The code below is psuedo:ish.
Only wrapping the component
export default function App() {
const CheckboxAdapter = ({input: {onChange, value}, meta}) => (
<Checkbox
status={value}
onPress={() => {
onChange(value === 'checked' ? 'unchecked' : 'checked');
}}
errorText={meta.touched ? meta.error : ''}
/>
);
return (
<Form
..
render={({handleSubmit}) => {
return (
..
<Field
name="myFieldname"
component={CheckboxAdapter}
/>
)
}
/>
)
}
Wrapping the component inside of the <Field> component
export default function App() {
const MyCheckbox = ({name}) => (
<Field
name={name}
component={({input: {onChange, value}, meta, children, ...rest}) => (
<Checkbox
status={value}
onPress={() => {
onChange(value === 'checked' ? 'unchecked' : 'checked');
}}
errorText={meta.touched ? meta.error : ''}
/>
)};
/>
);
return (
<Form
..
render={({handleSubmit}) => {
return (
..
<MyCheckbox
name="myFieldname"
/>
)
}
/>
)
}
Not much interest in this question. I ended up wrapping the <Field>as well. This gives much more readable form code.
import React from 'react';
import TextInput from '../components/TextInput';
import {Field} from 'react-final-form';
const TextInputAdapter = ({input, meta, ...rest}) => {
const onChangeText = value => {
input.onChange(value);
};
return (
<TextInput
{...input}
{...rest}
onChangeText={onChangeText}
errorText={meta.touched ? meta.error : ''}
/>
);
};
const TextInputField = ({...rest}) => {
return <Field component={TextInputAdapter} {...rest} />;
};
export default TextInputField;

Resources