React Hook Form using Controller, yup and Material UI - validation issue - reactjs

I have a simple form with a single Material UI TextField inside React Hook Form. I use Yup schema validation (via yupResolver). I try to validate form and display errors visually (boolean prop 'error' in TextField). I use Controller with 'render' prop, however it fails to update error on TextField change. Does anyone know what I am doing wrong here?
Link to codesandbox
import React from "react";
import ReactDOM from "react-dom";
import { TextField } from "#material-ui/core";
import { Controller, useForm } from "react-hook-form";
import * as yup from "yup";
import { yupResolver } from "#hookform/resolvers/yup";
import "./styles.css";
const schema = yup.object().shape({
title: yup.string().required("Required")
});
function App() {
const {
handleSubmit,
formState: { errors },
control
} = useForm({
resolver: yupResolver(schema)
});
const onSubmit = (data) => console.log("onSubmit", data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
render={({ formState, fieldState }) => {
return <TextField label="Title" error={!!formState.errors?.title} />;
}}
name="title"
control={control}
/>
<input type="submit" />
</form>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Additonally 'fieldState' in Controller seems to be an empty object all the time. Shouldn't it show properties as listed in
Link

I found the answer following #damjtoh suggestion. I've noticed there is a 'field' parameter in the render function in RHF code examples. Adding it to TextField connected component with the form. Just remember to add 'defaultValue' to avoid 'changing uncontrolled input' error. Here's how it should look like:
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
render={({ field, formState }) => (
<TextField
{...field}
label="Title"
error={!!formState.errors?.title}
/>
)}
name="title"
control={control}
defaultValue=""
/>
<input type="submit" />
</form>

You aren't connecting the material-ui component to the react-hook-form, you should check the Integrating Controlled Inputs section of react-hook-form documentation.

Related

Unable to assign name or ID to DatePicker component for the purpose of Yup validation

I have a Formik form that uses Yup for validation. One of my fields is a Datepicker, but I am unable to integrate it into yup validation.
Below is code which works as far as rendering the component, but as soon as I try wrap <DatePicker/> inside <Field name="date></Field> tags, nothing renders.
const [fieldDate,setFieldDate] = useState ("");
const dateSchema = Yup.object().shape({
date: Yup.date().required('Date is required'),
});
const initialValues = {date:''};
return (
<div>
<FormContainer>
<Formik
initialValues={initialValues}
//validationSchema={loginValidationSchema}
validationSchema={dateSchema}
onSubmit={()=>{console.log ("ok")}}
>
{({ isSubmitting, values, setFieldValue, handleChange, handleBlur, errors, touched }) => (
<Form className="form">
<LocalizationProvider dateAdapter={DateFnsUtils}>
<DatePicker
label="Date"
value={fieldDate}
onChange={(newValue:any) => {
setFieldDate(newValue);
}}
renderInput={(params) => <TextField {...params} />}
/>
</LocalizationProvider>
<div className="buttonWrapper">
<SubmitButton type="submit" className="SubmitButton">Submit</SubmitButton>
</div>
</Form>
)}
</Formik>
</FormContainer>
</div>
And here are my imports...I'm including them because I find that there are multiple libraries with the same name from MUI that have different parent directories with different requirements which has somewhat added to my confusion when trying to solve this via online solutions:
import React, { useState} from "react";
import { Formik, Form, Field, ErrorMessage } from "formik";
import {FormContainer,FieldContainer, SubmitButton } from "../GlobalStyle";
import { TextField } from "#mui/material";
import * as Yup from "yup";
import "react-datepicker/dist/react-datepicker.css";
import { LocalizationProvider } from '#mui/x-date-pickers/LocalizationProvider';
import DateFnsUtils from "#date-io/date-fns";
import { DatePicker } from '#mui/x-date-pickers/DatePicker';
Thanks!
import React from "react";
import { Formik, Form, Field, } from "formik";
import { TextField } from "#mui/material";
import * as Yup from "yup";
import { LocalizationProvider } from '#mui/x-date-pickers/LocalizationProvider';
import DateFnsUtils from "#date-io/date-fns";
import { DatePicker } from '#mui/x-date-pickers/DatePicker';
const dateSchema = Yup.object().shape({
date: Yup.date().required('Date is required'),
});
const initialValues = { date: '' };
export const App = () => {
return (
<div>
<Formik
initialValues={initialValues}
validateOnMount
validationSchema={dateSchema}
onSubmit={() => {
console.log('ok');
}}
>
<Form className="form">
<Field name="date">
{({ field, meta, form }) => (
<LocalizationProvider dateAdapter={DateFnsUtils}>
<DatePicker
label="Date"
renderInput={(params) => <TextField {...params} />}
// Get your date value from your form
value={field.value}
// Set your date in the form
onChange={(date) => form.setFieldValue('date', date)}
/>
{/* Error from yup */}
<div>{meta.error}</div>
</LocalizationProvider>
)}
</Field>
<div className="buttonWrapper">
<button type="submit" className="SubmitButton">
Submit
</button>
</div>
</Form>
</Formik>
</div>
);
};
export default App;

React hook form + material UI + Yup validation submit not working

Hi I am trying to combine React hook form, material UI, Yup validation but I just cant make it work, RHF and MUI works together but not with yup and RHF and YUP works but not with MUI.
anyway here is my work until now the only thing that is not working is that, the handleSubmit never fires and when I type something it gives me a uncontrolled error
import axios from "axios";
import styles from "../../styles/RegisterPage.module.css";
import TextField from "#mui/material/TextField";
import { Container } from "#mui/material";
import { useForm, Controller } from "react-hook-form";
import { yupResolver } from "#hookform/resolvers/yup";
import * as yup from "yup";
const schema = yup.object({
age: yup
.number()
.positive()
.integer()
.typeError("Amount must be a number")
.required(),
});
const MedicalHistoryPage = () => {
const {
handleSubmit,
control,
formState: { errors },
} = useForm({
resolver: yupResolver(schema),
});
const onSubmit = (data)=>{
console.log(data)
}
return (
<>
<Container maxWidth="md" className={styles.container}>
<form
onSubmit={handleSubmit(onSubmit)}
>
<div className={styles.title}>Ενημέρωσε το ιατρικό ιστορικό σου</div>
<div className={styles.form}>
<div className={styles.formsContainer}>
<div className={styles.input}>
<Controller
control={control}
name="age"
render={({ field }) => (
<TextField
{...field}
fullWidth
type="Number"
label="Input your age"
variant="outlined"
defaultValue=""
helperText={errors.age?.message}
error={!!errors.age?.message}
/>
)}
/>
</div>
</div>
<div className={styles.buttonsContainer}>
<div className={styles.loginButton}>
<input type="submit" className={styles.buttonStyle} />
</div>
</div>
</div>
</form>
</Container>
</>
);
};
export default MedicalHistoryPage;
Thanks for your time

Cannot read properties of undefined (reading 'getFieldProps') with the FastField in formik

I have a form with the Formik and I am going to decrease re-renders so I used the FastField.
I got the error Cannot read properties of undefined (reading 'getFieldProps') and I don't have an idea how I can solve it.
there is full code here
I found about it here and here
import React from "react";
import ReactDOM from "react-dom";
import { useFormik, FastField } from "formik";
import * as yup from "yup";
import Button from "#material-ui/core/Button";
const validationSchema = yup.object({});
const WithMaterialUI = () => {
const formik = useFormik({
initialValues: {
firstName:''
},
validationSchema: validationSchema,
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
}
});
return (
<div>
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<FastField name="firstName" placeholder="Weezy" />
<Button color="primary" variant="contained" fullWidth type="submit">
Submit
</Button>
</form>
</div>
);
};
ReactDOM.render(<WithMaterialUI />, document.getElementById("root"));
In the documentation you'll find
** Be aware that , , , connect(), and will NOT work with useFormik() as they all require React Context.
So you can't keep using the hook if you want to use <FastField/>, so I guess you have to fallback to the old style of wrapping your form with <Formik> component

react-hook-form when editing doesn't update

I can't find a way to provide the existing information from the DB to the react-hook-form library to revalidate when clicking submits and to update it if the data was changed.
The problem is when clicking on submit button, this object will shown without any value for firstName and lastname:
{firstName: undefined, lastname: undefined}
Simplified version of my component:
import React from "react";
import { useForm } from "react-hook-form";
import { TextField } from "#material-ui/core";
const App = (props) => {
const { register, handleSubmit} = useForm();
const onSubmit = (data) => {
console.log(data); // ---> here
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h2>Step 1</h2>
<label>
First Name:
<TextField {...register("firstName")} defaultValue="firstname" />
</label>
<label>
Last Name:
<TextField {...register("lastname")} defaultValue="lastname" />
</label>
<input type="submit" />
</form>
);
};
export default App
Any idea how to create a form that allows you to edit the fields?
Check out on codesandbox
The problem is with the TextFiled component. the register is not set correctly.
According to the MUI documentation:
props:inputProps type:object description:Attributes applied to the input element.
So you need to pass the register throw the inputProps:
<TextField inputProps={register("firstName")} defaultValue="firstname" />
<TextField inputProps={register("lastname")} defaultValue="lastname" />

Formik Material UI and react testing library

I am finding it hard to work with react testing library and understand the queries I need to use in order to select the components I need to test. The queries are too simplistic when the DOM gets more and more verbose with frameworks like material ui and formik.
I have created a code sandbox to illustrate the problem. You can check the failing test there.
https://codesandbox.io/embed/px277lj1x
The issue I am getting is, the query getLabelTextBy() does not return the component. It looks like the aria label by or the for attribute is not being rendered by material ui. Not sure how to fix this error.
The code is below here for reference too
//Subject under test
import React from "react";
import { Button } from "#material-ui/core";
import { TextField } from "formik-material-ui";
import { Field, Form, Formik } from "formik";
import * as yup from "yup";
const validationSchema = yup.object().shape({
name: yup.string().required("Office name is required"),
address: yup.string().required("Address is required")
});
export default () => (
<Formik
initialValues={{
name: "",
address: ""
}}
validationSchema={validationSchema}
onSubmit={(values, { setSubmitting }) => {
setSubmitting(false);
console.log("form is submitted with", values);
}}
render={({ submitForm, isSubmitting, isValid }) => (
<Form>
<Field
label="Office Name"
name="name"
required
type="text"
component={TextField}
/>
<Field
label="Address Line 1"
name="addressLine1"
type="text"
component={TextField}
/>
<Button
variant="contained"
color="primary"
fullWidth={false}
size="medium"
disabled={isSubmitting || !isValid}
onClick={submitForm}
data-testid="submitButton"
>
Submit
</Button>
</Form>
)}
/>
);
// Test
import React from "react";
import { render, fireEvent } from "react-testing-library";
import App from "./App";
describe("Create Office Form tests", () => {
it("button should not be disabled when all required fields are filled up", () => {
const { getByLabelText, getByTestId, debug } = render(<App />);
const values = {
"office name": "office",
address: "address 1"
};
for (const key in values) {
const input = getByLabelText(key, { exact: false, selector: "input" });
fireEvent.change(input, { target: { value: values[key] } });
}
const button = getByTestId("submitButton");
expect(button.disabled).not.toBeDefined();
});
});
You must add an id to your Field because the label's for attribute expect the ID of the input element it refers to:
<Field
id="myName"
label="Office Name"
name="name"
required
type="text"
component={TextField}
/>
A working example:
Few things. The use of { getByLabelText, getByTestId, debug } is not advised by the creator of the library. You should use screen.getByLabelText().
If there is a change, it's possible that it doesn't wait for the new render, so it would be better to await it or wrap the expect in a waitFor method.
Also, expect(button.disabled).not.toBeDefined() should not be correct. I think you're just checking if the button is disabled or not, right?
So you can use expect(button).toBeDisabled() or not.toBeDisabled()
At least to test it I think you should take off the for loop and check it normally. You can use screen.debug(component) to have the HTML shown after some action.

Resources