Trying to use react-hook-form in combination with react-input mask - reactjs

I have the following setup. My mask will show up, but when I type in it it just skips to the end of the line I am not quite sure what I am doing wrong here. I have tried putting all the props in the parent component and passing them all with a spread, That didn't work. I can provide more debugging if someone can give me an idea on where to debugg first and I'll do it.
Thanks ahead of time
import React from "react"
import { useForm } from "react-hook-form"
import MaskedInput from "react-input-mask"
const Quote = () => {
const { register, handleSubmit, watch, errors } = useForm();
const [tel, setTel] = React.useState("");
render(
<MaskedInput
mask="(780) 000-0000"
alwaysShowMask
onChange={(e) => setTel(e.target.value)}
value={tel}
name={data.title}
>
{(inputProps) => (
<input
ref={register({
required: true,
pattern: /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/im,
})}
value={inputProps.tel}
name={inputProps.name}
{...inputProps}
/>
)}
</MaskedInput>
);
};

For those who are using react-hook-form version 7, here is an example how to get it to work:
<Controller
name="your input name"
control={control}
defaultValue=""
rules={{
required: true,
}}
render={({ field }) => (
<MaskedInput
mask="99/99"
maskChar=""
value={field.value}
onChange={field.onChange}
>
{(inputProps: any) => (
<input
{...inputProps}
type="text"
/>
)}
</MaskedInput>
)}
/>

This solution work for me.
You will need the following packages on your package.json:
"react-hook-form": "^7.34.0",
"#hookform/resolvers": "^2.9.7",
"react-input-mask": "^3.0.0-alpha.2",
"#types/react-input-mask": "^3.0.1",
You can install this version of react-input-mask with the comand ...
yarn add react-input-mask#next
yarn add #types/react-input-mask
Here is the code:
<InputMask
// mask options
mask={"99.999.999/9999-99"}
alwaysShowMask={false}
maskPlaceholder=''
// input options
type={'text'}
placeholder="Ex: 00.000.000/0000-00"
// react hook form register
{...register("cnpj", { required: true })}
/>

Mask format was wrong needed to be in something like this
mask="(+7 (999) 999-99-99)"

To help others
If you're using not controlled Input Fields (like native input), ou can use a function to mask the input
This cant be used with controlled input fields like (Material UI)
Example #component/input.tsx
import React from 'react'
import { Container, ErrorMessage } from './styles'
interface InputProps {
label?: string | true
defaultValue?: string
name?: string
type?: string
mask?: (value: string) => string
placeholder?: string
disabled?: boolean
error?: any
value?: string
register?: any
}
const Input: React.FC<InputProps> = ({
label,
defaultValue,
name,
type,
mask = (value: string) => value,
value,
placeholder,
disabled,
error,
register,
...inputProps
}) => {
return (
<Container>
{label && (
<label htmlFor={name}>
{(typeof label === 'string' && label) ||
placeholder ||
'Preencha este campo'}
</label>
)}
<input
onChange={e => (e.target.value = `${mask(e.target.value)}`)}
disabled={disabled}
ref={register}
id={name}
name={name}
type={type}
value={value}
placeholder={placeholder}
defaultValue={defaultValue}
{...inputProps}
/>
{error && <ErrorMessage>{error.message}</ErrorMessage>}
</Container>
)
}
export default Input
Usage example #page/form.tsx
function CPFMask(v: string): string {
v = v.replace(/\D/g, '')
v = v.replace(/^(\d{3})(\d)/g, '$1.$2')
v = v.replace(/^(\d{3})\.(\d{3})(\d)/, '$1.$2.$3')
v = v.replace(/^(\d{3})\.(\d{3})\.(\d{3})(\d)/, '$1.$2.$3-$4')
v = v.replace(/^(\d{3})\.(\d{3})\.(\d{3})\/(\d{2})(\d)/, '$1.$2.$3-$4')
return v.substring(0, 14)
}
...
<Input
type="text"
mask={CPFMask}
placeholder="CPF"
name="cpf"
label
register={register({
required: {
value: true,
message: 'CPF é obrigatório',
},
pattern: {
value: /([0-9]{2}[\.]?[0-9]{3}[\.]?[0-9]{3}[\/]?[0-9]{4}[-]?[0-9]{2})|([0-9]{3}[\.]?[0-9]{3}[\.]?[0-9]{3}[-]?[0-9]{2})/i,
message: 'CPF inválido',
},
})}
error={errors.cpf}
/>
...

Here's an example, wrap the children with a function, let's think of InputMask as Controller and children as render. So I put the ref prop on the children to redirect the ref errors directly to the children or render, not the Controller.
<InputMask name="npwp" mask="99.999.999.9-999.999">
{(inputProps) => (
<input
{...inputProps}
ref={register}
type="text"
placeholder="Input NPWP"
/>
)}
</InputMask>

This is how I did it without using register, and with a different masking library (s-yadav/react-number-format).
A work-around, as I could not get it to work with {...register()}.
<NumberFormat
type="tel"
defaultValue={formDefaultValue}
onValueChange={(values) => {
// Mirror the value for form validation
setFormValue("amount", values.value);
}}
customInput={(props) => {
return (
<Input
name="amount"
formatProps={props}
errors={formErrors}
/>
);
}}
/>
Inside Input component:
<input
{...(formatProps ? formatProps : {})}
/>

Related

Input not writing into input field.. Issue caused by React useForm Hook register validations

const { register, handleSubmit, errors, setValue } = useForm();
<div className="col pl-0 pr-3">
<FormInput
id="id"
name="name"
isAllowed={e => e.value == '' || (e.value.length <= 14 && e.floatValue >= 0)}
allowLeadingZeros={true}
decimalScale={0}
onChange={e => setName(e.target.value)}
value={Name}
ref={register({ required: { value: true, message: "Please enter name" } })}
/>
<ErrorMessage
errors={errors}
className="col-md-6"
name="name"
as="small"
/>
</div>
In above mentioned code,, here FormInput is customized from StyledInput.
After displaying invalid message, when I am trying to enter something in input field, first character is not writing in field but it is clearing invalid error message from second character writing into field. What's the problem How to solve it Can anyone help me on this ?
UPDATED
By using control input, 3rd party library can work with react-hook-form.
https://react-hook-form.com/get-started#IntegratingControlledInputs
<Controller
as={
<NumberFormat
thousandSeparator={true}
allowLeadingZeros={true}
decimalScale={0}
onValueChange={(value) => {
console.log(value);
}}
isAllowed={(e) =>
e.value === "" || (e.value.length <= 14 && e.floatValue >= 0)
}
/>
}
id="id"
name="name"
defaultValue="1234"
control={control}
rules={{
required: true,
minLength: 2
}}
/>
There is no need to use another state to control the value.
The component is register in react-hooks-form with name "name", it will create the name state and handled state change for you.
Remove the following lines:
const [name, setName] = useState(); // I think you have your own name state, remove it
value={name} // remove it
onChange={(e) => setName(e.target.value)} // remove it

why is first letter dead in react-hook-form input

i need help to understand why the first letter in the simple input component is not registered.
I've created a simple controlled input from the examples but its not working properly.
i created an example for you https://stackblitz.com/edit/react-9zezqx
const App = () => {
const { register, handleSubmit, watch, errors, reset, control, formState } = useForm({ mode: 'onChange' });
console.log(errors)
return (
<div>
<form onSubmit={handleSubmit(() => console.log("submit"))}>
<Controller
as={TextInput}
name="firstname"
defaultValue=""
control={control}
inputRef={register({ required: true, minLength: 8 })}
hasErrors={errors.firstname !== undefined}
/>
<br/>
<Controller
as={TextInput}
name="hobby"
defaultValue=""
control={control}
inputRef={register({ required: true, minLength: 8 })}
hasErrors={errors.hobby !== undefined}
/>
</form>
</div>
);
}
render(<App />, document.getElementById('root'));
import * as React from 'react';
export const TextInput = (props) => {
return(
<>
<input
type="text"
name={props.name}
value={props.value}
onChange={props.onChange}
ref={props.inputRef}
/>
{props.hasErrors && (<h2>errors!!</h2>)}
</>
)
}
okay i found the bug causing it in 'onBlur' aswell
reValidateMode: 'onBlur'
Swap the mode to onBlur, this will improve the performance, will trigger only when the user leaves the field, which is suppose to happen, and also does not swallow the first input.
onChange skips the first letter, because the default value will be used for the initial onChange call, which is empty.
You are probably using an uncontrolled input. If that's the case use defaultValue instead of value.
Reference
This is the problem here, you are try to use Controller with register:
https://react-hook-form.com/api#Controller
<Controller
as={TextInput}
name="hobby"
defaultValue=""
control={control}
/>
or
https://react-hook-form.com/api#register
<TextInput inputRef={register} />

Send props to Number Format - Material UI TextField

I want to create a TextField element to handle numerical fields. I want to handle this component dynamically, in this way it would help me not only to manage credit card formats, telephone etc. I am using the react-number-format library in the same way as the Material-UI example does.
I'm trying to send by props "prefix" and "format" without favorable result.
I wanted to know how I should send those properties, if I have a way to do it.
Thanks in advance !
function NumberFormatCustom(props) {
const { inputRef, onChange, ...other } = props;
return (
<NumberFormat
{...other}
getInputRef={inputRef}
onValueChange={values => {
onChange({
target: {
value: values.value
}
});
}}
thousandSeparator={","}
decimalSeparator={"."}
isNumericString
prefix={props.prefix} //"$"
/>
);
}
NumberFormatCustom.propTypes = {
inputRef: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired
};
class NumberTextField extends Component {
state = {
numberformat: this.props.value
};
handleChange = event => {
const targetField = this.props.name;
const targetValue = event.target.value;
this.setState({
...this.state,
numberformat: targetValue
});
this.props.updateCurrentUserFieldsOnChange(targetField, targetValue);
};
render() {
const { fullWidth, label, name, readOnly, prefix } = this.props;
return (
<Fragment>
<TextField
fullWidth={fullWidth ? fullWidth : true}
label={label ? label : "react-number-format"}
name={name}
value={this.state.numberformat}
onChange={this.handleChange}
InputProps={{
inputComponent: NumberFormatCustom,
readOnly: Boolean(readOnly),
prefix: prefix
}}
/>
</Fragment>
);
}
}
You must use the customInput props which will allow you to integrate the style of material-ui. You can also pass several props to be able to control as you wish the mask. Also if you want a prefix just use the prefix props. thousandSeparator is a boolean but by default the numbers are separated by commas, if you prefer spaces you just have to add it as in my example
import NumberFormat from 'react-number-format';
import TextField from 'material-ui/TextField';
<NumberFormat
{...props}
value={value}
name={name}
mask={mask}
customInput={TextField}
prefix={'$'}
format={format || null}
type="text"
thousandSeparator={thousandSeparator ? ' ' : null}
onValueChange={({ value: v }) => onChange({ target: { name, value: v } })}
/>
If you want to have format your textfield as numberformat you can add your number format filed to FormControl field of material ui like below code.
<FormControl focused className="col " variant="outlined">
<InputLabel className="mText">your label</InputLabel>
<NumberFormat customInput={TextField}
variant="outlined"
thousandSeparator={true}
onChange={handleChange}
autoComplete="off"/>
</CustomFormControl>
Best regards.

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;

React - Date input value not updated to state

In my React App, I am able to set the state and update the database for all values except the date input field. My code is below:
import React, { Component } from 'react'
...
...
import DateInput from '../others/input/datePicker'
...
..
change = (what, e) => this.setState({ [what]: e.target.value })
changeDOB() {
this.setState({ age: document.getElementByClassNames("datePicker").value })
}
render() {
let {
...
...
age,
...
} = this.state
...
...
//date of birth
let stringAge = age.toString()
stringAge =
stringAge.substring(0, 4) +
'-' +
stringAge.substring(4, 6) +
'-' +
stringAge.substring(6, 8)
...
<DateInput
type="date"
change={this.changeDOB}
placeholder={stringAge}
className="datePicker"
/>
...
...
const mapStateToProps = store => ({
ud: store.User.user_details,
tags: store.User.tags,
session: store.User.session,
})
export default connect(mapStateToProps)(EditProfile)
export { EditProfile as PureEditProfile }
Here is DateInput code:
import React from 'react'
import { string, func, oneOf, bool } from 'prop-types'
const DateInput = ({ type, placeholder, ...props }) => (
<input
type={type}
placeholder={placeholder}
spellCheck="false"
autoComplete="false"
{...props}
/>
)
DateInput.defaultProps = {
type: 'date',
placeholder: '',
disabled: false,
}
DateInput.propTypes = {
type: oneOf(['date']),
placeholder: string.isRequired,
maxLength: string,
disabled: bool,
}
export default DateInput
I tried this.change like other fields but that does not work either.
How to get the new value updated in the state ?
Note: The text is red is the value currently in the database.
You need to add onChange attribute for the input field in the DateInput component as
const DateInput = ({ type, placeholder, ...props }) => (
<input
type={type}
placeholder={placeholder}
spellCheck="false"
autoComplete="false"
onChange = {props.Onchange}
{...props}
/>
)
Then your main component should be as
changeDOB(e) {
this.setState({ age: e.target.value });
}
render() {
return(
<DateInput
type="date"
Onchange={this.changeDOB}
placeholder={stringAge}
className="datePicker"
/>
)
}
Please find a working example here
You are passing all the props to input component but you need to pass your event handler function to onchange input element or Try onkeypress instead. Something like below. You can also try getting input value with event instead of document
Arrow function: No need of manual binding
changeDOB = (event) => {
this.setState({ age: event.target.value
})
}
<DateInput
type="date"
change={this.changeDOB}
placeholder={stringAge}
className="datePicker"
value={this.state.age}
/>
<input
type={type}
placeholder={placeholder}
spellCheck="false"
autoComplete="false"
onchange={(event) => props.change(event)}
value={props.value}
{...props}
/>
Normal function: Binding required and only in constructor
this.changeDOB = this.changeDOB.bind(this);
changeDOB(event){
this.setState({ age: event.target.value
})
}
<DateInput
type="date"
change={this.changeDOB}
placeholder={stringAge}
className="datePicker"
value={this.state.age}
/>
<input
type={type}
placeholder={placeholder}
spellCheck="false"
autoComplete="false"
onchange={props.change}
value={props.value}
{...props}
/>
The date is being taken in the ISO format whereas the display expects it in the localformat.
This worked for me:
const [deadline, setDeadline] = useState(new Date());
<input
type="date"
id="start"
name="trip-start"
value={deadline.toLocaleDateString}
onChange={(event) => setDeadline({deadline:event.target.value})}
min="2022-01-01"
max="2022-12-31"
>
</input>

Resources