I using EXTReact and have TextField
Here is my code.
<Textfield
required
label="Required Field"
requiredMessage="This field is required."
errorTarget="under"
name="field"
onChange={(e) => handleChaneValue(e)}
/>
<Button
ui="confirm"
text="BTN submit"
handler={handleClick}
style={{border: '1px solid black'}}
/>
When the submit Button is clicked, if the field does not have any value it should show the error message.
You should:
- step 1 : When textfField change, update the state of your component (with the value of textfield) ==> Two ways binding will be necessary (Data binding in React)
- step 2 : Then, you create your error message div/text/paragraph anyway (where you want and with the style you need), and you add style display:none. Here you just set the html and css of the feature.
- step 3 : Then, on click (button), you check the state value of textField (the one you made at step 1). If it's empty, you change style of the error message div ==> display:block
Please refer to the following code.
Note: The code is only looking for a single TextField. I have used material-ui TextField and Button component(I am not sure which one you're using). However, the logic should remain the same.
import React, { useState } from 'react';
import TextField from '#mui/material/TextField';
import Button from '#mui/material/Button';
function App() {
const [field, setField] = useState('');
const [error, setError] = useState(false);
const handleClick = () => {
if (!field) {
setError(true);
return null;
}
};
const handleChangeValue = (e) => {
setField(e.target.value);
};
return (
<div>
<TextField
required
label="Required Field"
value={field}
error={!!error}
name="field"
onChange={(e) => handleChangeValue(e)}
helperText={error ? 'this is required' : ''}
/>
<Button onClick={handleClick} style={{ border: '1px solid black' }}>
Submit
</Button>
</div>
);
}
export default App;
You can use Formik Formik-official.
import React from 'react';
import ReactDOM from 'react-dom';
import { useFormik } from 'formik';
import * as yup from 'yup';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
const validationSchema = yup.object({
email: yup
.string('Enter your email')
.email('Enter a valid email')
.required('Email is required'),
password: yup
.string('Enter your password')
.min(8, 'Password should be of minimum 8 characters length')
.required('Password is required'),
});
const WithMaterialUI = () => {
const formik = useFormik({
initialValues: {
email: 'foobar#example.com',
password: 'foobar',
},
validationSchema: validationSchema,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<div>
<form onSubmit={formik.handleSubmit}>
<TextField
fullWidth
id="email"
name="email"
label="Email"
value={formik.values.email}
onChange={formik.handleChange}
error={formik.touched.email && Boolean(formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
/>
<TextField
fullWidth
id="password"
name="password"
label="Password"
type="password"
value={formik.values.password}
onChange={formik.handleChange}
error={formik.touched.password && Boolean(formik.errors.password)}
helperText={formik.touched.password && formik.errors.password}
/>
<Button color="primary" variant="contained" fullWidth type="submit">
Submit
</Button>
</form>
</div>
);
};
ReactDOM.render(<WithMaterialUI />, document.getElementById('root'));
Related
I'm currently in the process of creating a simple form using MUI TextFields, Formik and some Yup validation.
I ran into some issues with performance with lower spec devices when scrolling and I have extracted stuff like MUI styles and other components such as toastify notifications.
I am relatively new to react, but I have used MUI + Formik + Yup before with a much more complex form without any lag when I scroll and I now can't seem to fix it. The documentation for MUI has also been extremely confusing with many different versions, depracated use cases and so on. I figured useMemo() could work in this instance as there is sure to be some excessive re-rendering, but I am not sure how to implement it.
I'll gladly give more info if needed.
import React from "react";
import { ToastContainer } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import {emailSuccess, emailError, emailSubmitting} from "../lib/toasts";
import {useSubmit} from "../lib/useSubmit";
import {postJson} from "../lib/http";
import {useStyles} from "../lib/inputStyle";
import {TextField} from "#mui/material";
import SendRoundedIcon from "#mui/icons-material/SendRounded";
import { useFormik } from "formik";
import * as Yup from "yup";
export default function Contact () {
const toastId = React.useRef(null);
const classes = useStyles();
const formik = useFormik({
initialValues: {
name: '',
email: '',
message: ''
},
validationSchema: Yup.object({
name: Yup
.string()
.max(255)
.required(
'Name is required'),
email: Yup
.string()
.email(
'Must be a valid email')
.max(255)
.required(
'E-mail address is required'),
message: Yup
.string()
.max(1200)
.required(
'You cannot send an empty message')
}),
onSubmit: () => {
//This should be optional... Please fix!
}
});
const { handleSubmit: handleEmail, submitting, error } = useSubmit(
async () => {
emailSubmitting(toastId);
await postJson("/api/mail", {
name: formik.values.name,
email: formik.values.email,
message: formik.values.message,
});
},
() => {
emailSuccess(toastId);
},
);
if (error) {
emailError(toastId)
}
return (
<div className="contact">
<div className="section">
<div className="contact-innerRef"/>
<h4>Contact</h4>
<div className="contact-card">
<h5>Get In Touch</h5>
<hr/>
<form className="contact-form" action="" method="post" encType="text/plain">
<TextField className={classes.root}
error={Boolean(formik.touched.name && formik.errors.name)}
helperText={formik.touched.name && formik.errors.name}
label="Name"
margin="normal"
name="name"
onChange={formik.handleChange}
value={formik.values.name}
variant="outlined"
/>
<TextField className={classes.root}
error={Boolean(formik.touched.email && formik.errors.email)}
helperText={formik.touched.email && formik.errors.email}
label="Email"
margin="normal"
name="email"
onChange={formik.handleChange}
value={formik.values.email}
variant="outlined"
/>
<TextField className={classes.root}
error={Boolean(formik.touched.message && formik.errors.message)}
helperText={formik.touched.message && formik.errors.message}
label="Message"
margin="normal"
name="message"
onChange={formik.handleChange}
value={formik.values.message}
variant="outlined"
multiline
rows={7}
/>
</form>
<button onClick={handleEmail} disabled={!formik.isValid || !formik.dirty || submitting}
className="btn">
<SendRoundedIcon height="50%"/>
</button>
<ToastContainer />
</div>
</div>
</div>
);
}```
I am building a login form with Formik with Materail UI, but Formik doesn't recognize Button of Material UI. If I replace the Button with html button everything works. Could anybody explain why it's not working with the Button component. Below is my code:
import React, { ReactElement } from "react";
import TextField from "#material-ui/core/TextField";
import FormControl from "#material-ui/core/FormControl";
import { Formik, Form } from "formik";
import { makeStyles } from "#material-ui/core/styles";
import Button from "#material-ui/core/Button";
import Box from "#material-ui/core/Box";
const useStyles = makeStyles((theme) => ({
root: {
margin: theme.spacing(1),
width: 200,
display: "flex",
},
input: {
marginTop: 5,
marginBottom: 5,
},
}));
interface Props {}
export default function loginForm({}: Props): ReactElement {
const classes = useStyles();
return (
<Box className={classes.root}>
<Formik
initialValues={{ username: "", password: "" }}
validate={(values) => {
const errors: { username?: string; password?: string } = {};
if (!values.username) {
errors.username = "Required";
} else if (!values.password) {
errors.password = "Required";
}
return errors;
}}
onSubmit={(values, { setSubmitting }) => {
console.log("values: ", values);
setSubmitting(false);
}}
>
{({
values,
errors,
touched,
handleChange,
handleBlur,
handleSubmit,
isSubmitting,
}) => (
<form onSubmit={handleSubmit}>
<FormControl>
<TextField
className={classes.input}
error={errors.username ? true : false}
type="username"
name="username"
onChange={handleChange}
onBlur={handleBlur}
value={values.username}
label="Username"
variant="outlined"
helperText={
errors.username && touched.username && errors.username
}
/>
<TextField
className={classes.input}
error={errors.password ? true : false}
type="password"
name="password"
onChange={handleChange}
onBlur={handleBlur}
value={values.password}
label="Password"
variant="outlined"
helperText={
errors.password && touched.password && errors.password
}
/>
</FormControl>
<Box display="flex" justifyContent="space-between">
<Button
variant="contained"
color="primary"
disabled={isSubmitting}
>
Submit
</Button>
<Button
variant="contained"
color="secondary"
disabled={isSubmitting}
>
Cancel
</Button>
</Box>
</form>
)}
</Formik>
</Box>
);
}
please note the code above doesn't work, but if you replace Button with button, the form works.
In most browsers, a HTML button by default has the type=submit which means that Formik's submit handler will be called. A Material-UI button does not have this default so the submit handler will never be called. Try adding type=submit to your <Button> props.
(Also, check out Formik's Material-UI integration examples)
You should add the id to the form.
<form onSubmit={handleSubmit} id="myForm">
And bind the form id with submit button
<Button
type="submit"
form="myForm"
>
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>
I using material-ui with react.
I want to do validations such as require and maxlength and minlength. according to material-ui docs I have to use the error prop and helperText to display the error. which mean I have to trigger a function myself that check the control and display the error. :(
I wounder if this is the right way to handle validation with react, the Textfield itself can't display require message (for example)? I have to specify each error myself? for example in angular I just add require or minlength and the control display the correct error.
Or maybe there is an easy way to do it?
got it :) here my ex :
import { Link } from 'react-router-dom';
import useForm from "react-hook-form";
import * as yup from 'yup';
const LoginFormSchema = yup.object().shape({
password: yup.string().required().min(4),
username: yup.string().required().min(4)
});
export function LoginForm(props) {
const { register, handleSubmit, watch, errors } = useForm({ defaultValues, validationSchema: LoginFormSchema });
const onSubmit = data => { props.onSubmit(data); }
<div className="form-container">
<form className="form" onSubmit={handleSubmit(onSubmit)}>
<div className="form-header">
<i className="material-icons">
account_circle
</i>
<h2>Login Form</h2>
</div>
<TextField name="username" label="username" inputRef={register} />
<span className="error-message">
{errors.username && errors.username.type === "required" && "username is required"}
{errors.username && errors.username.type === "min" && "username required to be more than 4 characters"}
</span>
<TextField type="password" name="password" label="password" inputRef={register} />
<span className="error-message">
{errors.password && errors.password.type === "required" && "password is required"}
{errors.password && errors.password.type === "min" && "password required to be more than 4 characters"}
</span>
</form>
You need to install yup and formik: npm i -s yup formik
Here is a working sample with formik yup and material-ui:
import React from "react";
import { Formik, Form, useField } from "formik";
import { TextField } from "#material-ui/core";
import * as yup from "yup";
//Reusable Textbox
const MyTextField = ({
placeholder,
type = "normal",
...props
}) => {
const [field, meta] = useField<{}>(props);
const errorText = meta.error && meta.touched ? meta.error : "";
return (
<TextField
variant="outlined"
margin="normal"
type={type}
placeholder={placeholder}
{...field}
helperText={errorText}
error={!!errorText}
/>
);
};
const validationSchema = yup.object({
username: yup
.string()
.required()
.max(30)
.min(2)
.label("Username"),
password: yup
.string()
.required()
.max(30)
.min(2)
.label("Password")
});
const Signin = ({ history }) => {
return (
<div className="SignupOuter">
<Formik
validateOnChange={true}
initialValues={{
username: "",
password: "",
loading: false
}}
validationSchema={validationSchema}
onSubmit={async (data1, { setSubmitting }) => {
setSubmitting(true);
//Call API here
}}
>
{({ values, errors, isSubmitting }) => (
<Form className="Signup">
<MyTextField placeholder="Username" name="username" />
<MyTextField
placeholder="Password"
name="password"
type="password"
/>
</Form>
)}
</Formik>
</div>
);
};
export default Signin;
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