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

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

Related

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

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.

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>

Use react-input-mask with antd in react-final-form

I would like to use react-input-mask with Ant Design Input in react-final-form. In order to use antd with react-final-form I also had to install redux-form-antd. So the file looks like this:
import React from "react";
import ReactDOM from "react-dom";
import { Button } from "antd";
import { Form, Field } from "react-final-form";
import InputMask from "react-input-mask";
import { TextField } from "redux-form-antd";
import "antd/dist/antd.css";
const onSubmit = async values => {
window.alert(JSON.stringify(values, 0, 2));
};
const Input = props => <InputMask {...props} />;
function App() {
return (
<Form
onSubmit={onSubmit}
render={({ handleSubmit, values }) => (
<form onSubmit={handleSubmit}>
<Field
name="mask"
parse={value =>
value
.replace(/\)/g, "")
.replace(/\(/g, "")
.replace(/-/g, "")
.replace(/ /g, "")
}
render={({ input, meta }) => (
<div>
<label>mask phone</label>
<Input mask="+7 (999) 999-99-99" {...input} />
{meta.touched && meta.error && <span>{meta.error}</span>}
</div>
)}
/>
<Field
name="antd"
component={TextField}
label="antd phone"
placeholder="Phone"
/>
<Button className="submit-button" type="primary">
Send
</Button>
<pre>{JSON.stringify(values, 0, 2)}</pre>
</form>
)}
/>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Here is a codesandbox example.
I could only get to work a regular input with an InputMask (input 1) or an antd input without a mask (input 2).
How can I add an InputMask to antd input?
I've managed to use react-input-mask with antd and react-final-form without any external libraries.
Here is my component:
import React from "react";
import InputMask from "react-input-mask";
import { Input } from "antd";
import FormItem from "antd/lib/form/FormItem";
const MaskInput = props => {
const { disabled, mask, label, meta, required } = props;
return (
<FormItem
label={label}
validateStatus={
meta.touched ? (meta.error ? "error" : "success") : undefined
}
help={meta.touched ? (meta.error ? meta.error : undefined) : undefined}
hasFeedback={meta.touched ? true : false}
required={required}
>
<InputMask
mask={mask}
disabled={disabled}
autoComplete="off"
{...props.input}
>
<Input />
</InputMask>
</FormItem>
);
};
export default MaskInput;
Then it is passed to the component prop of the Field:
<Field
name="phone"
label="Phone"
component={MaskInput}
mask="+7 (999) 999-99-99"
required
/>
Here is the link to the codesandbox example.
I've never used either of those libraries, but you might want to check out using format-string-by-pattern with react-final-form's built-in parsing and formatting functionality to achieve a similar thing.
I bet you could throw redux-form-antd components into here pretty easily...

How to add react-phone-number-input to -react-final-form?

I'm currently creating a form using react-final-form and trying to use react-phone-number-input with it through integration as an adapter, as displayed through this example.
I attempted to use the example to learn how it is done, but I'm not sure how to access the component and create the adapter for it properly.
import React from 'react';
import { Form, Field } from 'react-final-form';
import PhoneInput from 'react-phone-number-input';
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
const onSubmit = async values => {
await sleep(300)
window.alert(JSON.stringify(values, 0, 2))
}
const PhoneAdapter = ({ input, meta, ...rest }) => (
<PhoneInput
{...input}
{...rest}
value={input.value}
onChange={(event, value) => input.onChange(value)}
/>
)
class ContactForm extends React.Component {
render() {
return (
<>
<Form
onSubmit={onSubmit}
initialValues={{ }}
render={({ handleSubmit, form, submitting, pristine, values }) => (
<form onSubmit={handleSubmit}>
<fieldset>
<Field component={PhoneAdapter} />
</fieldset>
<fieldset>
<button type="submit" disabled={submitting || pristine}>
Submit
</button>
</fieldset>
<pre>{JSON.stringify(values, 0, 2)}</pre>
</form>
)}
/>
</>
);
}
}
export default ContactForm;
Update: July 2019
Apparently, all you need to do is to spread the input property of Field. Works flawlessly. Learn about spreading if you're not familiar with it.
const PhoneAdapter = ({ input }) => (
<PhoneInput {...input} />
)
<Field name="phone" placeholder="Enter phone number" component={PhoneAdapter} />
I ended up experimenting with the FieldRenderProps props until it worked out. I wasn't so sure whether it would work or not, as react-phone-number-input is two elements in a component. I thought it would implement the input on only one of the elements.
By using input, I gain access to the input's props. Hence, I called upon it's value, as the default looks like so:
<PhoneInput
placeholder="Enter phone number"
value={ this.state.value } // This is what I called.
onChange={ value => this.setState({ value }) }/>
I then did the same for the onChange function prop.
const PhoneAdapter = ({ input }) => (
<PhoneInput value={input.value.value} onChange={value => input.onChange(value)} />
)
Finally, I used the component adapter like so:
<Field name="phone" placeholder="Enter phone number" component={PhoneAdapter} />

React Datepicker with redux-form

How to make the react-datepicker bind with redux-form?
I have this redux-form field:
<Field name="due_date" component={props =>
<DatePicker
{...props}
readOnly={true}
selected={this.state.due_date}
value={this.state.due_date}
onChange={this.handleDueDate}
/>
} />
Whenever i submit the redux form does return an empty object not binding the datepicker's value...
my onChange :
handleDueDate(date) {
this.setState({
due_date: moment(date).format('L')
}, () => {
// do I need to dispatch and action here to update the redux-form in state tree?
})
}
you have to make a custom component to validate and use datepicker with redux-form
Try this
const datePicker = ({ input, label, type, className, selected, meta: { touched, error } }) => (
<div>
<div>
<DatePicker {...input}
selected={selected} placeholder={label}
type={type} className={className}
peekNextMonth
showMonthDropdown
showYearDropdown
dropdownMode="select"
/>
{touched && error && <span className="error_field">{error}</span>}
</div>
</div>
)
and then pass props to that custom component
<Field
name="date_of_birth"
component={datePicker}
type="text"
selected={this.state.date_of_birth}
onChange={this.handleChange.bind(this)}
className="form-control"
/>
For future readers, just go to this link: DatePicker in Redux Form
first, I created the component like metioned in link above and just passed the selected prop in it. in my case:
<Field
name="due_date"
component={DatePickerComponent}
selected={this.state.date_of_briefing} />

Resources