react-hook-form custom resolver only checking after submit - react-hook-form

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

Related

How to write unittest for table row selection using Antd Library in React

I have a react application and I'm using #testing-library/jest-dom for unit testing and antd 3.x library for UI design. In one of my UI screen there is a table and a button where the button only enables when one of the row in table is checked. So I wanted to do a unittest for this. So below is my src code,
import {Modal, Button, Form, Input, DatePicker, Table, Card, Tooltip} from 'antd';
...
...
return (
<>
<Form>
...
<Card>
<Table
data-testid={'lift-hold-grid'}
className="holdListResultsTable"
rowSelection={rowSelection}
columns={columns}
dataSource={dataSourcee}
components={{
body: {
row: showRestorationTooltip
}
}}
/>
</Card>
...
<div style = {...}>
<Button data-testid={'lift-hold-button'} disabled={...} onClick={...}>
Lift Hold
</Button>
</div>
<Form>
</>
)
and below is the unittest
import { render } from "#testing-library/react";
import { Provider } from "react-redux";
import thunk from "redux-thunk";
function renderWithReduxAndThunk(ui, initialState) {
const createdStore = createStore(rootReducer, initialState, applyMiddleware(thunk));
return {
...render(<Provider store={createdStore}>{ui}</Provider>),
createdStore,
}
}
const changedState = {
...
}
it('should enable the lift hold button after checkbox selection and note input', () => {
const { container, getByTestId } = renderWithReduxAndThunk(<LegalHoldLiftScreen />, changedState);
const checkBox = container.querySelector('.ant-checkbox-input');
fireEvent.click(checkBox);
const downloadButton = getByTestId('lift-hold-button');
expect(downloadButton).toBeTruthy();
expect(downloadButton).not.toBeDisabled();
});
but this fails with below message
● should enable the lift hold button after checkbox selection and note input
Unable to fire a "click" event - please provide a DOM element.
what am I missing here??

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 ant design typescript form validation not working

I used react typescript project to ant design and i used this ant design form validation, but its not working correctly, anyone know how to fix that issue?
Thanks
git a this error
index.tsx?789d:32 Uncaught TypeError: Cannot read property
'getFieldDecorator' of undefined
import * as React from "react";
import { Input, Form, Icon, Button, } from "antd";
import 'antd/dist/antd.css';
import "./style.css";
export class Registerform extends React.Component<any> {
handleSubmit = (e:any) => {
e.preventDefault();
this.props.form.validateFieldsAndScroll((err:any, values:any) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
/* Start add bulk upload form*/
<div className="remindersform-section">
<Form onSubmit={this.handleSubmit}>
<Form.Item
label={
<span>
Nickname
<Icon type="question-circle-o" />
</span>
}
>
{getFieldDecorator('nickname', {
rules: [{ required: true, message: 'Please input your nickname!', whitespace: true }],
})(<Input />)}
</Form.Item>
<Form.Item >
<Button type="primary" htmlType="submit">
Register
</Button>
</Form.Item>
</Form>
</div>
);
}
}
Because default form props are not assigned with their types, antd provided a solution for it, Try like below
import * as React from "react";
import { FormComponentProps } from "antd/es/form";
import { Input, Form, Icon, Button, } from "antd";
import 'antd/dist/antd.css';
interface UserFormProps extends FormComponentProps {
form: any;
}
class Registerform extends React.Component<UserFormProps> {
handleSubmit = (e:any) => {
e.preventDefault();
this.props.form.validateFieldsAndScroll((err:any, values:any) => {
if (!err) {
console.log('Received values of form: ', values);
}
});
};
render() {
const { getFieldDecorator } = this.props.form;
return (
/* Start add bulk upload form*/
<div className="remindersform-section">
<Form onSubmit={this.handleSubmit}>
<Form.Item
label={
<span>
Nickname
<Icon type="question-circle-o" />
</span>
}
>
{getFieldDecorator('nickname', {
rules: [{ required: true, message: 'Please input your nickname!', whitespace: true }],
})(<Input />)}
</Form.Item>
<Form.Item >
<Button type="primary" htmlType="submit">
Register
</Button>
</Form.Item>
</Form>
</div>
);
}
}
const WrappedForm = Form.create<UserFormProps>({})(Registerform);
export default WrappedForm;

A form is not submitted when the form is used as a title prop of TreeNode (andt design)

In this code we use Button with Modal in TreeNode. There is the form in Modal and Ok Button is bound to form with using form ('myForm') attribute. But form is not submitted with button. It looks like submition doesn't work at all.
According to my investiagation it is related with Tree and TreeNode components.
The example of my code:
import React, { useState, useMemo } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { useForm, Controller } from "react-hook-form";
import { Modal, Button, Input, Tree } from "antd";
const { TreeNode } = Tree;
const renderForm = (handleSubmit, onSubmit, control) => (
<form id="myForm" onSubmit={handleSubmit(onSubmit)}>
<Controller
as={<Input placeholder="Type" />}
control={control}
name="title"
/>
</form>
);
const Comp = () => {
const { handleSubmit, control } = useForm();
const [visible, setVisible] = useState(false);
const showModal = () => {
setVisible(true);
};
const handleOk = e => {
setVisible(false);
};
const handleCancel = e => {
setVisible(false);
};
const onSubmit = ({ title }) => {
console.log(title);
};
return (
<div>
<Button type="primary" onClick={showModal}>
Open Modal
</Button>
<Modal
title="Basic Modal"
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
okButtonProps={{
htmlType: "submit",
form: "myForm"
}}
>
{useMemo(() => renderForm(handleSubmit, onSubmit, control), [
control,
handleSubmit
])}
</Modal>
</div>
);
};
const App = () => (
<Tree>
<TreeNode key="ert" title={<Comp />} />
</Tree>
);
ReactDOM.render(<App />, document.getElementById("container"));

how to get user input from antd Input.Password?

I am trying to get user input from antd input.password field. Is it possible?
I didnt see any information on antd docs. I wonder if its possible
I am expecting a string for user input.password since i'll be saving them into local storage
You can always use onChange method like onChange={e => console.log(e.target.value) }
CodeSandbox: https://codesandbox.io/s/5283xn4vo4
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Input, Button } from "antd";
class PasswordToLocalStorage extends React.Component {
state = {
password: undefined
};
render = () => {
return (
<React.Fragment>
<Input.Password
onChange={e => this.setState({ password: e.target.value })}
placeholder="Enter Password"
/>
<Button
onClick={() => {
if (this.state.password) {
localStorage.setItem("password", this.state.password);
alert("saved to local storage: " + localStorage.password);
} else {
alert("There is no password to save");
}
}}
>
Save to localStorage
</Button>
</React.Fragment>
);
};
}
ReactDOM.render(
<PasswordToLocalStorage />,
document.getElementById("container")
);

Resources