React Hook Form with AntD Styling - reactjs

I'm trying to figure out how to use react-hook-form with antd front end.
I have made this form and it seems to be working (it's part 1 of a multipart form wizard) except that the error messages do not display.
Can anyone see what I've done wrong in merging these two form systems?
I'm not getting any errors, but I think I have asked for both form fields to be required but if I press submit without completing them the error messages are not displayed.
import React from "react";
import useForm from "react-hook-form";
import { BrowserRouter as Router, Route } from "react-router-dom";
import { StateMachineProvider, createStore } from "little-state-machine";
import { withRouter } from "react-router-dom";
import { useStateMachine } from "little-state-machine";
import updateAction from "./updateAction";
import { Button, Form, Input, Divider, Layout, Typography, Skeleton, Switch, Card, Icon, Avatar } from 'antd';
const { Content } = Layout
const { Text, Paragraph } = Typography;
const { Meta } = Card;
createStore({
data: {}
});
const General = props => {
const { register, handleSubmit, errors } = useForm();
const { action } = useStateMachine(updateAction);
const onSubit = data => {
action(data);
props.history.push("./ProposalMethod");
};
return (
<div>
<Content
style={{
background: '#fff',
padding: 24,
margin: "auto",
minHeight: 280,
width: '70%'
}}
>
<Form onSubmit={handleSubmit(onSubit)}>
<h2>Part 1: General</h2>
<Form.Item label="Title" >
<Input
name="title"
placeholder="Add a title"
ref={register({ required: true })}
/>
{errors.title && 'A title is required.'}
</Form.Item>
<Form.Item label="Subtitle" >
<Input
name="subtitle"
placeholder="Add a subtitle"
ref={register({ required: true })}
/>
{errors.subtitle && 'A subtitle is required.'}
</Form.Item>
<Form.Item>
<Button type="secondary" htmlType="submit">
Next
</Button>
</Form.Item>
</Form>
</Content>
</div>
);
};
export default withRouter(General);

react-hook-form author here. Antd Input component doesn't really expose inner ref, so you will have to register during useEffect, and update value during onChange, eg:
const { register, setValue } = useForm();
useEffect(() => {
register({ name: 'yourField' }, { required: true });
}, [])
<Input name="yourField" onChange={(e) => setValue('yourField', e.target.value)}
i have built a wrapper component to make antd component integration easier: https://github.com/react-hook-form/react-hook-form-input
import React from 'react';
import useForm from 'react-hook-form';
import { RHFInput } from 'react-hook-form-input';
import Select from 'react-select';
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
function App() {
const { handleSubmit, register, setValue, reset } = useForm();
return (
<form onSubmit={handleSubmit(data => console.log(data))}>
<RHFInput
as={<Select options={options} />}
rules={{ required: true }}
name="reactSelect"
register={register}
setValue={setValue}
/>
<button
type="button"
onClick={() => {
reset({
reactSelect: '',
});
}}
>
Reset Form
</button>
<button>submit</button>
</form>
);
}

This is my working approach:
const Example = () => {
const { control, handleSubmit, errors } = useForm()
const onSubmit = data => console.log(data)
console.log(errors)
return (
<Form onSubmit={handleSubmit(onSubmit)}>
<Controller
name="email"
control={control}
rules={{ required: "Please enter your email address" }}
as={
<Form.Item
label="name"
validateStatus={errors.email && "error"}
help={errors.email && errors.email.message}
>
<Input />
</Form.Item>
}
/>
<Button htmlType="submit">Submit</Button>
</Form>
)
}

On writing such code:
<Input
name="subtitle"
placeholder="Add a subtitle"
ref={register({ required: true })}
/>
You assume that Input reference is bound to input, but that's not true.
In fact, you need to bind it to inputRef.input.
You can check it with the next code:
const App = () => {
const inputRef = useRef();
const inputRefHtml = useRef();
useEffect(() => {
console.log(inputRef.current);
console.log(inputRefHtml.current);
});
return (
<FlexBox>
<Input ref={inputRef} />
<input ref={inputRefHtml} />
</FlexBox>
);
};
# Logs
Input {props: Object, context: Object, refs: Object, updater: Object, saveClearableInput: function ()…}
<input></input>
Note that antd is a complete UI library (using 3rd party "helpers" should "turn a red light"), in particular, Form has a validator implemented, you can see a variety of examples in docs.

In Ant Design v4.x + react-hook-form v6.x. We can implement as normally
import { useForm, Controller, SubmitHandler } from 'react-hook-form';
import * as yup from 'yup';
import { yupResolver } from '#hookform/resolvers/yup';
import { useIntl } from 'react-intl';
import { Input, Button, Form } from 'antd';
const SignInSchema = yup.object().shape({
email: yup.string().email().required(),
password: yup.string().required('required').min(6, 'passwordMin'),
});
interface PropTypes {
defaultValues?: {
email: string;
password: string;
};
handleFormSubmit: SubmitHandler<{ email: string; password: string }>;
}
function SignInForm({ defaultValues, handleFormSubmit }: PropTypes) {
const intl = useIntl();
const { handleSubmit, control, errors } = useForm({
defaultValues,
resolver: yupResolver(SignInSchema),
});
return (
<Form onFinish={handleSubmit(handleFormSubmit)}>
<Form.Item
validateStatus={errors && errors['email'] ? 'error' : ''}
help={errors.email?.message}
>
<Controller
as={Input}
name="email"
autoComplete="email"
control={control}
placeholder={intl.formatMessage({ id: 'AUTH_INPUT_EMAIL' })}
/>
</Form.Item>
<Form.Item
validateStatus={errors && errors['password'] ? 'error' : ''}
help={errors.password?.message}
>
<Controller
as={Input}
name="password"
type="password"
control={control}
autoComplete="new-password"
defaultValue=""
placeholder={intl.formatMessage({ id: 'AUTH_INPUT_PASSWORD' })}
/>
</Form.Item>
<Button type="primary" htmlType="submit">
{intl.formatMessage({ id: 'SIGN_IN_SUBMIT_BUTTON' })}
</Button>
</Form>
);
}
export default SignInForm;

In case anyone is still interested in getting close to the default styles of the Form Inputs provided by Ant, here's how I got it to work:
import { Form, Button, Input } from 'antd';
import { useForm, Controller } from 'react-hook-form';
function MyForm() {
const { control, handleSubmit, errors, setValue } = useForm();
const emailError = errors.email && 'Enter your email address';
const onSubmit = data => { console.log(data) };
const EmailInput = (
<Form.Item>
<Input
type="email"
placeholder="Email"
onChange={e => setValue('email', e.target.value, true)}
onBlur={e => setValue('email', e.target.value, true)}
/>
</Form.Item>
);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
as={EmailInput}
name="email"
control={control}
defaultValue=""
rules={{
required: true
}}
validateStatus={emailError ? 'error' : ''}
help={emailError}
/>
<Button block type="primary" htmlType="submit">
Submit
</Button>
</form>
);
}
Codesandbox sample

Related

how to do validation for a antd Text (typography) component?

It seems that we couldn't add validation for antd editable <Text> (typography).
The component doesn't accept value props like <Input> component. How do we do validation in that case?
Code:
const [title, setTitle] = useState("Battle");
<>
<Form id="myForm" name="basic" onFinish={onSubmit}>
<Form.Item
name="title"
rules={[
{
required: true,
message: "Please input your title!"
}
]}
>
<Text editable>{title}</Text>
</Form.Item>
</Form>
<Button form="myForm" key="formSubmit" htmlType="submit" type="primary">
Create
</Button>
</>
Above code validation works but if the data is still there, it show validation error.
Codesandbox link : https://codesandbox.io/s/basic-antd-4-16-9-forked-o8nxdl?file=/index.js
Here is a code that does some simple validations
function reducer (action , state) {
switch (action.type){
case 'title':
return {...state , data : { ...state.data , title : action.payload }
, errors : { ...state.errors
, title : title.length > 3 null : '😈'} } // title length must be > 3
}
}
const [state , dispatch] = useReducer (reducer , {data : {} , errors : {})
console.log (state)
return ( <> <Form.Item
name="title"
rules={[
{
required: true,
message: "Please input your title!"
}
]}
onChange = {(e) => dispatch ({type : 'title' , payload : e.target.value})
>
<Text editable>{title}</Text>
</Form.Item> </>)
Before submitting make sure all errors must be null
you can handle it using the onChange like this working demo is here
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Form, Button, Input, Typography } from "antd";
import SecForm from "./SecForm";
const { Text } = Typography;
const App = () => {
const [title, setTitle] = useState('Battle');
const data = ["1", "2", "3"];
const onHandleChange = (event) => {
setTitle(event);
};
const onSubmit = (e) => {
e.preventDefault();
console.log("Submitted");
};
return (
<>
<Form id="myForm" name="basic" onFinish={onSubmit}>
<Form.Item
label="name"
name="name"
rules={[
{
required: true,
message: "Please input your name!"
}
]}
>
<Input placeholder="Name" />
</Form.Item>
<Form.Item
label="rank"
name="rank"
rules={[
{
required: true,
message: "Please input your rank!"
}
]}
>
<Input placeholder="Rank" />
</Form.Item>
<Form.Item
name="title"
valuePropName={title}
rules={[
{
required: title?.length < 1,
message: "Please input your title!"
}
]}
>
<Text editable={{ onChange: onHandleChange }}>{title}</Text>
</Form.Item>
{data.map((d, index) => (
<SecForm index={index} />
))}
</Form>
<Button form="myForm" key="formSubmit" htmlType="submit" type="primary">
Create
</Button>
</>
);
};
ReactDOM.render(<App />, document.getElementById("container"));

How to Reset a form when I click submit button in React

I am working on a React project, For designing I am using Ant design framework. I have a form in that form when I entered all details in Input form and when I click submit button the Form has to be Reset, I tried to Reset a form after clicking submit button. but it is showing this kind of error. TypeError: Cannot read property 'reset' of null
So please help me to resolve this error
This is my code App.js
import React, { useRef, useState } from "react";
import { Row, Col, Button, Card, Form, Input, Select } from "antd";
import axios from 'axios'
import 'antd/dist/antd.css';
import {
SearchOutlined,
StopOutlined,
UserOutlined,
LeftOutlined,
DoubleRightOutlined,
} from "#ant-design/icons";
import './App.css';
const App = () => {
const { Option } = Select;
const [data, setData] = useState({});
const testData = async () => {
try {
const res = await axios.post('http://localhost:8000/api/user', data);
console.log(res);
} catch (error) {
console.log(error);
}
}
const handleChange = ({ target }) => {
const { name, value } = target;
const newData = Object.assign({}, data, { [name]: value });
setData(newData);
}
const handleSubmit = (e) => {
e.preventDefault();
console.log(data);
testData()
};
const myForm = useRef(null)
const resetForm = () => {
myForm.current.reset();
}
const prefixSelector = (
<Form.Item name="prefix" noStyle>
<Select
style={{
width: 50,
// height: 10,
}}
>
<Option value="86">+86</Option>
<Option value="87">+87</Option>
</Select>
</Form.Item>
);
return (
<div>
<div className="customizedCards">
<Card className="cardStyle">
<div className="main-form">
<h5 className="idDetails">StudentDETAILS</h5>
<Form style={{marginLeft: "-10px"}} ref={myForm}>
<Form.Item
name="name"
noStyle
rules={[{ required: true, message: 'firstname is required' }]}
>
<Input type="text" name='first_name' style={{ width: 400 }} onChange={handleChange} placeholder="Firstname" />
</Form.Item>
<Form.Item
name="name"
noStyle
rules={[{ required: true, message: 'lastname is required' }]}
>
<Input type="text" name='last_name' style={{ width: 400 }} onChange={handleChange} placeholder="Lastname" />
</Form.Item>
<Form.Item
name="name"
noStyle
rules={[{ required: true, message: 'email is required' }]}
>
<Input type="email" name='email' style={{ width: 400 }} onChange={handleChange} placeholder="Email" />
</Form.Item>
<Form.Item
name="phone"
rules={[
{
required: true,
message: 'Please input your phone number!',
},
]}
>
<Input type="number" name='phone_number' addonBefore={prefixSelector} style={{ width: 400 }} onChange={handleChange} placeholder="Phone Number" />
</Form.Item>
<Button onClick={(e) => handleSubmit(e), resetForm()} className="submit" type="primary" >Submit</Button>
</Form>
</div>
</Card>
</div>
</div>
)
}
export default App
```
As said here you just have a syntax trouble.
Try this instead:
const myForm = useRef(null)
const resetForm = () => {
myForm.reset();
}

Antd Form with React hook form

I am trying to use antd form with react-hook-form but couldn't get it to work.
Basically I am using a theme with antd and want to integrate react-hook-form for better validation and other things. So far this is the code
import { Button, Form, Input, Message, Row, Tooltip } from "antd";
import { useForm, Controller } from "react-hook-form";
import {
EyeTwoTone,
MailTwoTone,
PlaySquareTwoTone,
QuestionCircleTwoTone,
SkinTwoTone,
} from "#ant-design/icons";
import Link from "next/link";
import Router from "next/router";
import styled from "styled-components";
const FormItem = Form.Item;
const Content = styled.div`
max-width: 400px;
z-index: 2;
min-width: 300px;
`;
const Register = (props) => {
const defaultValues = {
name: null,
phone: null,
email: null,
password: null,
confirmPassword: null,
checked: false,
};
const { handleSubmit, reset, watch, control, errors, getValues } = useForm({
defaultValues,
});
// const { register, errors, handleSubmit, control } = useForm({
// mode: 'onChange',
// });
const onSubmit = async (data) => {
try {
console.log("data", data);
} catch (err) {
console.log("err", err);
}
};
return (
<Row
type="flex"
align="middle"
justify="center"
className="px-3 bg-white"
style={{ minHeight: "100vh" }}
>
<Content>
<div className="text-center mb-5">
<Link href="/signup">
<a className="brand mr-0">
<PlaySquareTwoTone style={{ fontSize: "32px" }} />
</a>
</Link>
<h5 className="mb-0 mt-3">Sign up</h5>
<p className="text-muted">create a new account</p>
</div>
<Form layout="vertical" onSubmit={handleSubmit(onSubmit)}>
<Controller
as={
<FormItem
label="Email"
name="email"
rules={[
{
type: "email",
message: "The input is not valid E-mail!",
},
{
required: true,
message: "Please input your E-mail!",
},
]}
>
<Input
prefix={<MailTwoTone style={{ fontSize: "16px" }} />}
type="email"
placeholder="Email"
/>
</FormItem>
}
control={control}
name="select"
/>
{/* <Input inputRef={register} name="input" /> */}
<button type="button" onClick={() => reset({ defaultValues })}>
Reset
</button>
<input type="submit" />
</Form>
</Content>
</Row>
);
};
export default Register;
Now, as you can see I am having regular form tag and inside that the input field. But using the regular form, I am not getting the layout props provided by the antd forms. Also I am not able to get the values during submit.
So my question is that how can I use AntD form component with react hook form so i can use the benefits of react-hook0form as well a Antd styling.
You can simply use the built-in useForm method of ant design, no need to pull in a thirdparty. It looks like:
const [form] = Form.useForm();
Also Form has onFinish method not onSubmit in ant, at least in version 4.
Use controller wrapper from react-hook-form, documentation link
Import Controller
import { useForm, Controller } from 'react-hook-form';
Call control from useForm
const { handleSubmit, control } = useForm();
Your Input
<Form.Item label="Email">
<Controller
name="email"
defaultValue=""
control={control}
render={({ onChange, value }) => (
<Input onChange={onChange} value={value} />
)}/>
</Form.Item>

Fetch and use response to change state in React

I would like to change the state of a component based on the response of a PUT request using react-refetch.
Especially when the response of the PUT is unsuccessful, as is the case with for example a 500 response.
The following example is an example in a form. When a user submits the form it should then fire off a PUT.
If the PUT response is fulfilled, it should reset the form. Otherwise nothing should happen, and the user should be able to retry.
./MyForm.jsx
import React from "react";
import PropTypes from "prop-types";
import { PromiseState } from "react-refetch";
import { Formik, Form, Field, ErrorMessage } from "formik";
import ResetOnSuccess from "./ResetOnSuccess";
const MyForm = ({ settingsPut, settingsPutResponse }) => {
const submitForm = (values, formik) => {
settingsPut(true);
// Here it should pick up the settingsPutResponse,
// and then do the following ONLY if it's successful:
//
// formik.resetForm({ values });
// window.scrollTo(0, 0);
};
return (
<div>
<Formik
noValidate
initialValues={{ name: "", password: "" }}
onSubmit={submitForm}
>
{({ dirty }) => (
<Form>
<ResetOnSuccess settingsPutResponse={settingsPutResponse} />
<Field type="text" name="name" />
<ErrorMessage name="name" component="div" />
<Field type="password" name="password" />
<ErrorMessage name="password" component="div" />
<button type="submit" disabled={dirty !== null ? !dirty : false}>
Submit
</button>
{settingsPutResponse && settingsPutResponse.rejected && (
<p style={{ color: "red" }}>Please try again</p>
)}
</Form>
)}
</Formik>
</div>
);
};
MyForm.propTypes = {
settingsPut: PropTypes.func.isRequired,
settingsPutResponse: PropTypes.instanceOf(PromiseState)
};
MyForm.defaultProps = {
userSettingsPutResponse: null
};
export default MyForm;
I might have a solution by creating a component:
./ResetOnSuccess.jsx
import React, { useEffect, useState } from "react";
import { useFormikContext } from "formik";
import PropTypes from "prop-types";
import { PromiseState } from "react-refetch";
const ResetOnSuccess = ({ settingsPutResponse }) => {
const { values, resetForm } = useFormikContext();
const [success, setSuccess] = useState(false);
useEffect(() => {
if (settingsPutResponse && settingsPutResponse.fulfilled) {
setSuccess(true);
}
}, [settingsPutResponse]);
// only if settingsPutResponse is fulfilled will it reset the form
if (success) {
resetForm({ values });
window.scrollTo(0, 0);
setSuccess(false);
}
return null;
};
ResetOnSuccess.propTypes = { settingsPutResponse: PropTypes.instanceOf(PromiseState) };
ResetOnSuccess.defaultProps = { settingsPutResponse: null };
export default ResetOnSuccess;
And then in ./MyForm.jsx add the reset component:
<Formik
noValidate
initialValues={{ name: "", password: "" }}
onSubmit={submitForm}
>
{({ dirty }) => (
<Form>
<ResetOnSuccess settingsPutResponse={settingsPutResponse} />
<Field type="text" name="name" />
<ErrorMessage name="name" component="div" />
<ResetOnSuccess settingsPutResponse={settingsPutResponse} />
// etc...
But since it's a component that returns a 'null'. This feels a bit like an anti-pattern.
Is there a better way?
I've created an codesandbox example here: https://codesandbox.io/s/quizzical-johnson-dberw

React phone input 2 with react hook form

I am using PhoneInput along with react hook form, I want to enable save button only if phone number is valid
Code:
<form onSubmit={handleSubmit(onSubmitRequest)}>
.....................other code..............
<Controller
as={
<PhoneInput
id="pNum"
placeholder="Enter phone number"
className={classes.phoneInput}
inputRef={register({required: true})}
isValid={(inputNumber, onlyCountries) => {
return onlyCountries.some((country) => {
return startsWith(inputNumber, country.dialCode) || startsWith(country.dialCode, inputNumber);
});
}}
/>
}
name="phoneNumber"
control={control}
/>
........................other code...................
<Button
fullWidth
type="submit"
variant="contained"
color={'primary'}
className={classes.submitBtn}
data-testid="customerFormButton"
disabled={!formState.isValid}
>
Save
</Button>
</form>
Here I used PhoneInput as controller along with isValid for it. How can I disable Save button for invalid phone number input?
How are you? I believe that your problem is because you are not configuring the rules for the controller.
You need to change your controller to something like this:
<Controller
as={
<PhoneInput
id="pNum"
placeholder="Enter phone number"
className={classes.phoneInput}
inputRef={register}
isValid={(inputNumber, onlyCountries) => {
return onlyCountries.some((country) => {
return startsWith(inputNumber, country.dialCode) || startsWith(country.dialCode, inputNumber);
});
}}
/>
}
name="phoneNumber"
control={control}
rules= {{required: true}}
/>
ref cannot be currently used on this element. react-phone-input-2.
Until its supported, you can provide a hidden input field which updates its value when the phone updates its value and put the ref on that
Example:
import React, { FC, useCallback } from 'react';
import { useFormContext } from 'react-hook-form';
import PhoneInput from 'react-phone-input-2';
import 'react-phone-input-2/lib/style.css';
interface Props {
handleChange: (name: string, val: string) => void;
defaultValue: string;
name: string;
}
const MyComponent: FC<Props> = ({ defaultValue, name, handleChange }) => {
const { register, setValue, watch } = useFormContext(); // Note: needs <FormProvider> in parent for this to be acessible
const nameHidden = `${name}Hidden`;
const handleChangePhone = useCallback(
(val: string) => {
setValue(nameHidden, val, { shouldValidate: true });
handleChange(name, val);
},
[handleChange]
);
return (
<>
<PhoneInput value={defaultValue as string} country="gb" onChange={handleChangePhone} />
<input
type="hidden"
name={nameHidden}
defaultValue={defaultValue}
ref={register({
// validate stuff here...
})}
/>
</>
);
};
export default MyComponent;

Resources