react-hook-form reset is not working with Controller + antd - reactjs

I'm trying to use react-hook-form together with the antd <Input /> component
I'm not getting reset to work with <Controller />
Here is my code:
const NormalLoginForm = () =>{
const {reset, handleSubmit, control} = useForm();
const onSubmit = handleSubmit(async ({username, password}) => {
console.log(username, password);
reset();
});
return (
<form onSubmit={onSubmit} className="login-form">
<Form.Item>
<Controller as={<Input
prefix={<Icon type="user" style={{color: 'rgba(0,0,0,.25)'}}/>}
autoFocus={true}
placeholder="Benutzername"
/>} name={'username'} control={control}/>
</Form.Item>
<Form.Item>
<Controller as={<Input
prefix={<Icon type="lock" style={{color: 'rgba(0,0,0,.25)'}}/>}
type="password"
placeholder="Passwort"
/>} name={'password'} control={control}/>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" className="login-form-button">
Log in
</Button>
</Form.Item>
</form>
);
}
I'm expecting that the two input fields are getting cleared when the form is submitted. But that doesn't work.
Am I missing something here?
Example on Stackblitz
https://stackblitz.com/edit/react-y94jpf?file=index.js
Edit:
The RHFInput mentioned here React Hook Form with AntD Styling is now part of react-hook-form and has been renamed to Controller. I'm already using it.
I've figured out that chaning
reset();
to
reset({
username:'',
password:''
});
solves the problem.
However - I wanted to reset the whole form without explicitly assigning new values.
Edit 2:
Bill has pointed out in the comments that it's almost impossible to detect the default values for external controlled inputs. Therefore we're forced to pass the default values to the reset method. That makes totally sense to me.

You must wrapper the components for antd and create a render component, it is very similar if you use Material UI, So the code can be like:
import { Input, Button } from 'antd';
import React from 'react';
import 'antd/dist/antd.css';
import {useForm, Controller} from 'react-hook-form';
const RenderInput = ({
field: {
onChange,
value
},
prefix,
autoFocus,
placeholder
}) => {
return (
<Input
prefix={prefix}
autoFocus={autoFocus}
placeholder={placeholder}
onChange={onChange}
value={value}
/>
);
}
export const NormalLoginForm = () =>{
const {reset, handleSubmit, control} = useForm();
const onSubmit = ({username, password}) => {
console.log(username, password);
reset();
};
return (
<form onSubmit={handleSubmit(onSubmit)} className="login-form">
<Controller
control={control}
name={'username'}
defaultValue=""
render={ ({field}) => (
<RenderInput
field={field}
autoFocus={true}
placeholder="Benutzername"
/>
)}
/>
<Controller
render={ ({field}) => (
<RenderInput
field={field}
type="password"
placeholder="Passwort"
/>
)}
defaultValue=""
name={'password'}
control={control}
/>
<Button
type="primary"
htmlType="submit"
className="login-form-button"
>
Log in
</Button>
</form>
);
}
export default NormalLoginForm;
You can notice that I did't put the Icon ,it was because I tested using the version 4 for antd and something change in how to use the icons.

Related

MUI v5: How can I auto focus form inputs with errors?

I am trying to make my form accessible.
Here is my sandbox link: https://codesandbox.io/s/typescript-material-ui-textfield-forked-0xh13?file=/src/App.tsx
My requirements are the following:
Upon submitting a form with validation errors, the 1st input with an error should be focused
Exactly like this form: https://a11y-guidelines.orange.com/en/web/components-examples/forms/
Is there an option in material ui to achieve this?
This is my code:
import React from "react";
import TextField from "#mui/material/TextField";
import { Form, Field } from "react-final-form";
const required = (value: string) =>
value ? undefined : "This field cannot be blank";
const App = () => (
<Form
onSubmit={(form_data) => console.log(form_data)}
render={({ handleSubmit, submitting }) => (
<form onSubmit={handleSubmit}>
<Field
name="line1"
validate={required}
render={({ input, meta }) => (
<TextField
{...input}
placeholder="Required"
label="Required"
helperText={meta.error}
error={meta.touched && meta.error}
/>
)}
/>
<Field
name="line2"
render={({ input, meta }) => (
<TextField
{...input}
placeholder="Optional"
label="Optional"
helperText={meta.error}
error={meta.touched && meta.error}
/>
)}
/>
<button onClick={handleSubmit}>Save</button>
</form>
)}
/>
);
export default App;
I ended up using a plugin for react-final-form called final-form-focus. I don't think there's anything for MUI
https://codesandbox.io/s/typescript-material-ui-textfield-forked-vep9e?file=/src/App.tsx

Console Log from Props

I'm confused as I have managed to get my data to be logged via different means, but confused as to why when I use props for the data (rather than repeating code) it will not log the input.
For reference, I have a field component that will take props to drive what my react-hook-form TextField will request. I'd like to expand on the component but until it logs my data, I cannot proceed!
Below is the code that actually logs my data:
import React from "react";
import { useForm, Controller } from "react-hook-form";
import { TextField, Button } from "#material-ui/core/";
const NewRequest = () => {
const { register, handleSubmit, control } = useForm();
const onSubmit = (data) => console.log(data);
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name='firstName'
render={({ field: { onChange, onBlur, value, name, ref } }) => (
<TextField
label='First Name'
variant='filled'
size='small'
onBlur={onBlur}
onChange={onChange}
checked={value}
inputRef={ref}
/>
)}
/>
<br />
<br />
<Button type='submit' variant='contained'>
Submit
</Button>
</form>
</div>
);
};
export default NewRequest;
I have then moved the Controller, TextField to create a component:
import React from "react";
import { Controller, useForm } from "react-hook-form";
import { TextField } from "#material-ui/core/";
const TextFieldComponent = (props) => {
const { name, label, size, variant } = props;
const { control } = useForm();
return (
<div>
<Controller
control={control}
name={name}
render={({ field: { onChange, onBlur, value, ref } }) => (
<TextField
label={label}
variant={variant}
size={size}
onBlur={onBlur}
onChange={onChange}
checked={value}
inputRef={ref}
/>
)}
/>
</div>
);
};
export default TextFieldComponent;
Which I am using inside of another component (to generate a full form) and passing through my props (I will make a different component for Button, but for now it is where it is):
import React from "react";
import { useForm, Controller } from "react-hook-form";
import TextFieldComponent from "./form-components/text-field";
import { Button } from "#material-ui/core/";
const NewRequest= () => {
return (
<div>
<TextFieldComponent
name='firstName'
label='First Name'
size='small'
variant='filled'
/>
<br />
<br />
<Button type='submit' variant='contained'>
Submit
</Button>
</div>
);
};
export default NewRequest;
Now pushing that component into an index.js file to render a form:
import React from "react";
import NewVendorForm from "../components/new-vendor-request";
import { useForm } from "react-hook-form";
const Home = () => {
const { handleSubmit } = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<NewVendorForm />
</form>
);
};
export default Home;
I'm stumped as to why this way would
a) customise my TextField in my form as intended
b) but not log my data as requested
I'm sure there is a very valid, basic reason as to why and it is my lack of understanding of console logging, but am in need of help to resolve!
Many thanks in advance.
The issue is that, in the refactored code, you're calling useForm twice, each of which generates a different control and data. You probably want to call useForm at the top level only, and pass in whatever you need (in particular control) to the form fields.
const Home = () => {
const { handleSubmit, control } = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<NewVendorForm control={control} />
</form>
);
};
const NewRequest= ({control}) => {
return (
<div>
<TextFieldComponent
name='firstName'
label='First Name'
size='small'
variant='filled'
control={control}
/>
<br />
<br />
<Button type='submit' variant='contained'>
Submit
</Button>
</div>
);
};
const TextFieldComponent = (props) => {
const { name, label, size, variant, control } = props;
return (
<div>
<Controller
control={control}
name={name}
render={({ field: { onChange, onBlur, value, ref } }) => (
<TextField
label={label}
variant={variant}
size={size}
onBlur={onBlur}
onChange={onChange}
checked={value}
inputRef={ref}
/>
)}
/>
</div>
);
};

react-hook -form with SliderComponent input

Im new to react and I am trying to use a SliderComponent in a react Hook form but I cant seem able to fully understand how Controller works.
Here is my SliderComponent using react-input-slider:
export default function SliderComponent(props) {
const { axis, xmax, xmin, xstep, onChange } = props;;
return (
<div>
<Slider
axis={axis}
x={value.x}
xmax={xmax}
xmin={xmin}
xstep={xstep}
onChange={onChange}
/>
</div>
);
}
And here is my form:
export default function BiometricForm() {
const { handleSubmit, control } = useForm();
return (
<div className="registerForm_Container">
<form onSubmit={handleSubmit(onsubmit)}>
<Controller
control={control}
name="test"
render={({ props: { x, axis, xmax, xmin, xstep } }) => (
<SliderComponent
axis={"x"}
xmax={100}
xmin={1}
xstep={1}
value={x}
onChange={(e) => x.onChange(parseInt(e.target.value))}
/>
)}
/>
<button className="registerForm_Container_button" type="submit">
Register
</button>
</form>
</div>
);
}
I think it might be something to do with useState and that I am not able to reach useState of component. I have read that maybe its not necessary , any help? Thank you!
You are absolutely right, if you use RHF you don't need useState because RHF handles the form state for you. The important thing here is to pass an onChange handler to your <SliderComponent />, so that RHF can watch for value changes of your <SliderComponent /> and update the form state. You should also provide a defaultValue for the field, if you don't want it to be undefined if the user doesn't change the slider before submitting.
Also without the useState inside <SliderComponent />, you can also omit the <SliderComponent /> and just use <Slider />.
function BiometricForm() {
const { handleSubmit, control } = useForm();
const onSubmit = (data) => console.log(data);
return (
<div className="registerForm_Container">
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="test"
defaultValue={50}
render={({ field: { value, onChange } }) => (
<Slider
axis={"x"}
xmax={100}
xmin={1}
xstep={1}
onChange={({ x }) => onChange(x)}
x={value}
/>
)}
/>
<input type="submit" />
</form>
</div>
);
}

ForwardRef warning React-hook-forms with Material UI TextField

I am trying to build a form with react-hook-forms with Material UI's inputs (my custom variant of TextField in this case). Although the form seems to work completely fine, it triggers a warning message in the console when rendering the form.
Warning: Function components cannot be given refs. Attempts to
access this ref will fail. Did you mean to use React.forwardRef()?
I am using react-hook-form's Controller to wrap my TextField (as suggested by the docs)
Any suggestions or solutions are very welcome!
Below both the TextField component and the form where this issue occurs:
Component TextField
const TextField = props => {
const {
icon,
disabled,
errors,
helperText,
id,
label,
value,
name,
required,
...rest
} = props;
const classes = useFieldStyles();
return (
<MuiTextField
{...rest}
name={name}
label={label}
value={value || ''}
required={required}
disabled={disabled}
helperText={helperText}
error={errors}
variant="outlined"
margin="normal"
color="primary"
InputProps={{
startAdornment: icon,
classes: {
notchedOutline: classes.outline,
},
}}
InputLabelProps={{
className: classes.inputLabel,
}}
/>
)
};
TextField.propTypes = {
icon: PropTypes.node,
disabled: PropTypes.bool,
label: PropTypes.string,
id: PropTypes.string,
value: PropTypes.any,
required: PropTypes.bool,
helperText: PropTypes.string,
};
export default TextField;
Component LoginForm
const LoginForm = () => {
const { handleSubmit, errors, control } = useForm();
const onSubmit = values => console.log(values);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Typography variant="h5" color="primary" gutterBottom>
Login
</Typography>
<Box py={3} height="100%" display="flex" flexDirection="column">
<Controller
as={TextField}
label="Username"
name="username"
control={control}
errors={errors}
required
/>
<Controller
as={TextField}
label="Password"
type="password"
name="password"
control={control}
errors={errors}
required
/>
<Link>
Forgot your password?
</Link>
</Box>
<Button variant="contained" color="primary" fullWidth type="submit">
Submit
</Button>
</form>
)
};
Try to use Controller's render prop instead of as, because TextField's exposed ref is actually called inputRef, while Controller is trying to access ref.
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { useForm, Controller } from "react-hook-form";
import Header from "./Header";
import { TextField, ThemeProvider, createMuiTheme } from "#material-ui/core";
import "react-datepicker/dist/react-datepicker.css";
import "./styles.css";
import ButtonsResult from "./ButtonsResult";
let renderCount = 0;
const theme = createMuiTheme({
palette: {
type: "dark"
}
});
const defaultValues = {
TextField: "",
TextField1: ""
};
function App() {
const { handleSubmit, reset, control } = useForm({ defaultValues });
const [data, setData] = useState(null);
renderCount++;
return (
<ThemeProvider theme={theme}>
<form onSubmit={handleSubmit((data) => setData(data))} className="form">
<Header renderCount={renderCount} />
<section>
<label>MUI TextField</label>
<Controller
render={(props) => (
<TextField
value={props.value}
onChange={props.onChange}
inputRef={props.ref}
/>
)}
name="TextField"
control={control}
rules={{ required: true }}
/>
</section>
<section>
<label>MUI TextField</label>
<Controller
render={(props) => (
<TextField
value={props.value}
onChange={props.onChange}
inputRef={props.ref}
/>
)}
name="TextField1"
control={control}
rules={{ required: true }}
/>
</section>
<ButtonsResult {...{ data, reset, defaultValues }} />
</form>
</ThemeProvider>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
you can click the following link for actual behavior, now with ref assigned properly with Controller, we can successfully focus on the field when there is an error for better accessibility.
https://codesandbox.io/s/react-hook-form-focus-74ecu
The warning is completely right as suggested by the official docs it think you did not reach to the functional components part. Link to the offical docs
You cannot give ref to functional components as they do not have instances
If you want to allow people to take a ref to your function component, you can use forwardRef (possibly in conjunction with useImperativeHandle), or you can convert the component to a class.
You can, however, use the ref attribute inside a function component as long as you refer to a DOM element or a class component like this:
function CustomTextInput(props) {
// textInput must be declared here so the ref can refer to it
const textInput = useRef(null);
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}

Checkbox onChange event is not handled by handleChange props by Formik

I was building simple Form using React and Formik library.
I have added check box inside the form tag which is wrapped by withFormik wrapper of formik library.
I have tried to changing from
<input
type="checkbox"
name="flag"
checked={values.flag}
onChange={handleChange}
onBlur={handleBlur}
/>
to
<input
type="checkbox"
name="flag"
value={values.flag}
onChange={handleChange}
onBlur={handleBlur}
/>
but none is working.
the component is as following
import { withFormik } from 'formik';
...
const Form = props => (
<form>
<input
type="checkbox"
name="flag"
checked={props.values.flag}
onChange={props.handleChange}
onBlur={props.handleBlur}
/>
<input
type="text"
name="name"
checked={props.values.name}
onChange={props.handleChange}
onBlur={props.handleBlur}
/>
</form>
);
const WrappedForm = withFormik({
displayName: 'BasicForm',
})(Form);
export default WrappedForm;
It should change props.values when clicking checkbox.
but it doesn't change props data at all.
Btw, it changes props data when typing in text input box.
This only happens with checkbox.
Using the setFieldValue from Formik props, you can set the value of the check to true or false.
<CheckBox
checked={values.check}
onPress={() => setFieldValue('check', !values.check)}
/>
My answer relates to react-native checkbox.
This article is very helpful. https://heartbeat.fritz.ai/handling-different-field-types-in-react-native-forms-with-formik-and-yup-fa9ea89d867e
Im using react material ui library, here is how i manage my checkboxes :
import { FormControlLabel, Checkbox} from "#material-ui/core";
import { Formik, Form, Field } from "formik";
<Formik
initialValues={{
check: false
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{({ values, setFieldValue }) => (
<Form className="gt-form">
<FormControlLabel
checked={values.check}
onChange={() => setFieldValue("check", !values.check)}
control={<Checkbox />}
label="save this for later"
/>
</Form>
)}
</Formik>

Resources