react-phone-input-2
react-phone-input-2 copied to clipboard
Use the component with Formik
There are two issues when trying to use the component with Formik:
- Formik emits a name attribute, which has to be passed to the input element. When trying to use your component, Formik logs:
Warning: Formik called `handleBlur`, but you forgot to pass an `id` or `name` attribute to your input:
<input class="styles_phoneInput__input__1fmVL phone-input-s9ig4t form-control" placeholder="Телефон" type="tel" value="+7 (1)">
Formik cannot determine which value to update. For more info see https://github.com/jaredpalmer/formik#handleblur-e-any--void
The right solution will be to add a 'name' prop to the component which will correspond to the name attribute in HTML input element. My solution is a bad hot-fix: I add a class to the element, find the input element by class and add the name attribute to it.
- onChange emits the event as the third parameter. We need to write a wrapper that calls Formik's handleChange with the event as the parameter.
Here's my React component that 'solves' these issues:
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import PhoneInput from 'react-phone-input-2';
import block from 'bem-css-modules';
import classNames from 'classnames';
import { createRandomIdentificator } from '../../../helpers/commonHelpers';
import styles from './styles.module.scss';
const b = block(styles);
export default function MaskedInput(props) {
const {
handleChange,
handleBlur,
className,
type,
placeholder,
name,
...restProps
} = props;
const inputClassPostfix = createRandomIdentificator();
useEffect(() => {
document.querySelector(`.phone-input-${inputClassPostfix}`).setAttribute('name', name);
});
const onValueChange = (phoneNumber, country, e) => {
handleChange(e);
};
return (
<PhoneInput
{...restProps}
country="ru"
preferredCountries={['ru']}
value={null}
placeholder={placeholder}
containerClass={b('container')}
inputClass={classNames(b('input'), `phone-input-${inputClassPostfix}`)}
buttonClass={b('button')}
onChange={onValueChange}
onBlur={handleBlur}
name={name}
/>
);
}
MaskedInput.defaultProps = {
type: 'text',
placeholder: '',
name: '',
className: '',
};
MaskedInput.propTypes = {
name: PropTypes.string,
placeholder: PropTypes.string,
type: PropTypes.string,
className: PropTypes.string,
handleBlur: PropTypes.func.isRequired,
handleChange: PropTypes.func.isRequired,
};
Facing a similar problem
I am also facing the same problem here. I think that it could be nice to have an optional name
props
Thank you @boriswinner for the hack!
For the name issue I have used the inputProps option and I've just taken your code to solve the handleChange issue too:
<PhoneInput ... inputProps={{name:"phoneNumber"}} onChange={(phoneNumber, country, e)=>{handleChange(e)}} />
Seems to work for me without a wrapper class!
I ended up with:
<Formik>
{({ errors, touched, values, handleChange, handleBlur }) => (
<Form>
<label htmlFor="phone">Phone Number</label>
<PhoneInput
inputProps={{name:"phone"}}
onChange={(phoneNumber, country, e)=>{handleChange(e)}}
onBlur={handleBlur}
/>
</Form>
)}
</Formik>
{ handleChange, handleBlur, className, type, placeholder, name, ...restProps }
Thx men, simple and easy solution, it worked for me.
const validationSchema = Yup.object().shape(
{
phone: Yup.string()
.nullable()
.notRequired()
.when(['phone'], (value, schema) => {
if (Number(value) > Number(phoneCode)) {
return schema
.required()
.min(MIN_PHONE_NUMBER_LENGTH_WITH_PHONE_CODE, 'Should not be less than 8 digits')
.max(MAX_PHONE_NUMBER_LENGTH_WITH_PHONE_CODE, 'Should not exceed 13 digits');
}
return schema;
}),
},
[['phone', 'phone']]
);
const formik = useFormik({
initialValues: {
phone: '',
},
validationSchema,
validateOnChange: true,
});
const { errors, handleBlur, isValid, values } = formik;
const onValueChange = (phoneNumber) => {
formik.setFieldValue('phone', phoneNumber);
};
return (
<StyledPhoneInput
inputProps={{
name: 'phone', // should match with the schema and initialValues
}}
disableDropdown
onBlur={handleBlur} // 👈
onChange={onValueChange} // 👈
countryCodeEditable={false}
preferredCountries={['gb', 'us']}
country={userCountryCode?.toLowerCase() || 'gb'}
containerClass={`react-phone-number ${errors.phone ? 'error' : ''}`}
onlyCountries={countriesList.map((o) => o.countryCode.toLowerCase())}
/>
);
Styled with error for css material ui
const StyledPhoneInput = styled(PhoneInput)(({ theme }) => ({
'&.react-phone-number': {
fontFamily: 'inherit',
},
'&.react-phone-number input.form-control': {
fontFamily: 'inherit',
width: '100%',
paddingTop: theme.spacing(1),
paddingBottom: theme.spacing(1),
borderRadius: '8px',
borderColor: 'rgba(145, 158, 171, 0.32)',
paddingLeft: theme.spacing(2.5),
'&:focus': {
borderColor: theme.palette.primary.main,
boxShadow: `0 0 0 1px ${theme.palette.primary.main}`,
},
},
'&.react-phone-number.error': {
'& input': {
borderColor: theme.palette.error.main,
'&:focus': {
borderColor: theme.palette.error.main,
boxShadow: `0 0 0 1px ${theme.palette.error.main}`,
},
},
'& .special-label': {
color: theme.palette.error.main,
},
},
}));