catamphetamine / react-phone-number-input

React component for international phone number input
http://catamphetamine.gitlab.io/react-phone-number-input/
MIT License
924 stars 194 forks source link

Can't figure out how to use this with Formik field component #298

Closed Shaker-Hamdi closed 4 years ago

Shaker-Hamdi commented 4 years ago

I'm using Formik and trying to use this package, but Formik can't see the value of the input and thus doesn't validate or submit the form.

Here's the PhonInputField component that I'm using ...

import PhoneInput from "react-phone-number-input";

const PhoneInputField = ({
    field,
    form
}) => {
    return (
        <div className="input-field">
            <PhoneInput
                placeholder="Enter phone number"
                name={field.name}
                value={field.value}
                onChange={field.onChange}
                onBlur={field.onBlur}
            />
        </div>
    );
};

export default PhoneInputField;

And here's how I'm using it in Formik ...

<Formik
    initialValues={initialValues}
    validationSchema={signInSchema}
    onSubmit={handleFormSubmit}
>
    {({
        isValid,
        isSubmitting,
        handleChange,
        handleBlur
    }) => (
        <Form>

                <Field
                    type="tel"
                    name="phone_number"
                    component={PhoneInputField}
                />

            <div className="cta">
                <button
                    type="submit"
                    disabled={!isValid || isSubmitting}
                    className="btn btn-primary big"
                >Submit</button>
            </div>
        </Form>
    )}
</Formik>

What am I doing wrong here?

catamphetamine commented 4 years ago

Perhaps the reason could be formik expecting onChange(event) instead of onChange(value). See if something like onChange={value => field.onChange({ target: { value } })} would work.

Shaker-Hamdi commented 4 years ago

It actually now gives me this warning in the console ...

image

He's confused on which value to update. And as you can see I did provide a "name" and the name shows in the inspector!

catamphetamine commented 4 years ago

Well, then it requires a properly constructed event it seems. Somehow it can't figure out that field.onChange is for field. You can experiment with constructing a synthetic React event of some sort. This is an advanced topic I guess.

catamphetamine commented 4 years ago

Or maybe not, maybe it's something else. I didn't use formik.

Shaker-Hamdi commented 4 years ago

When I do it like this the warnings go away, but still can't seem to detect that the field has a value and thus won't activate the "submit" button ...

<PhoneInput
        placeholder="Enter phone number"
        name={field.name}
        value={field.value}
        onChange={value => field.onChange({ value })}
        onBlur={value => field.onBlur({ value })}
/>
catamphetamine commented 4 years ago

You'll have to ask formik experts then.

Shaker-Hamdi commented 4 years ago

OK, thank you for your effort, I'll try to post this on StackOverflow, and maybe someone who faced this before can give his insight.

catamphetamine commented 4 years ago

There has also been a previous thread on Formik: https://github.com/catamphetamine/react-phone-number-input/issues/159#issuecomment-455710810 Maybe you could find something there.

AndrewStratigos commented 4 years ago

If it helps, here is my phoneinput/formik control

import React, { useState } from 'react';
import PhoneInput from 'react-phone-number-input';
import PropTypes from 'prop-types';
import 'react-phone-number-input/style.css';
import { getIn } from 'formik';

const PhoneInputField = (props) => {
  const {
    className,
    field: { name, value },
    form: {
      errors, handleBlur, setFieldValue, touched,
    },
    form,
    label,
    country,
    onChange,
    disabled,
  } = props;

  const [isFocused, setFocused] = useState(false);
  const isError = getIn(touched, name) && getIn(errors, name);
  const errorStyle = isError ? 'error' : '';
  const filledStyle = (isFocused || value) ? 'filled' : '';
  const disabledStyle = disabled ? 'disabled' : '';

  const handleInputBlur = (e) => {
    setFocused(false);
    handleBlur(e);
  };

  const handleInputFocus = () => setFocused(true);

  const onValueChange = (phoneNumber) => {
    setFieldValue(name, phoneNumber);

    if (onChange !== null) {
      onChange(phoneNumber);
    }
  };

  return (
    <div className={`${className} ${errorStyle} ${filledStyle} ${disabledStyle} text-input-group`}>
      <PhoneInput
        placeholder="Enter phone number"
        name={name}
        value={value}
        onChange={onValueChange}
        country={country}
      />
      <label className="transition ml-10" htmlFor={name}>
        {label}
      </label>
      <div className="flex h-5 items-end text-red-100 text-xs">
        {isError && getIn(errors, name)}
      </div>
    </div>
  );
};

PhoneInputField.propTypes = {
  className: PropTypes.string,
  form: PropTypes.any.isRequired,
  field: PropTypes.any.isRequired,
  onChange: PropTypes.func,
  label: PropTypes.string,
  country: PropTypes.string,
  disabled: PropTypes.bool,
};

PhoneInputField.defaultProps = {
  className: '',
  label: '',
  onChange: null,
  country: 'AU',
  disabled: false,
};

export default PhoneInputField;
Shaker-Hamdi commented 4 years ago

@AndrewStratigos That did actually work, Thanks a lot man. The trick in your code was this part ...

const onValueChange = phoneNumber => {
        setFieldValue(name, phoneNumber);

        if (onChange !== null) {
            onChange(phoneNumber);
        }
    };

Again, thanks a bunch 👍

cortehz commented 4 years ago

@shaker-hamdi. I'm still struggling to make this work for me. Can you take a look at my sandbox and see?

jdmg94 commented 4 years ago

I may be late to the party but for anyone else here having issues with their onChange handler, remember this also comes with a curried option, in a regular web input formik can take the name attribute from the target it receives in the change handler, React-native generally doesn't provide a synthetic event (it does but its different) most people tend to use the onChangeText property instead so formik needs other queues to know how to update the form's state, consider the following:

instead of calling the change (or blur) handlers like we do on web: onChange={formik.handleChange}

you call it with the attribute name: onChange={formik.handleChange('phoneNumber')} this also works for the blur handler onBlur={formik.handleBlur('phoneNumber')} also make sure you send the change values as string or formik won't accept it, it sometimes is a pain in the ass to work forms on RN

catamphetamine commented 4 years ago

@jdmg94 So what does the ultimate Formik component look like then? Where does formik.handleChange go in this code?

import React, { useCallback } from 'react'
import PropTypes from 'prop-types'
import PhoneInput from 'react-phone-number-input`

function FormikPhoneInput(ref, { name, onChange, formik, ...rest }) {
  const onChange_ = useCallback(value => onChange(value || ''), [onChange])
  return (
    <PhoneInput
      {...rest}
      ref={ref}
      name={name}
      onChange={onChange_}/>
  )
}

FormikPhoneInput = React.forwardRef(FormikPhoneInput)

FormikPhoneInput.propTypes = {
  name: PropTypes.string.isRequired,
  onChange: PropTypes.func.isRequired,
  formik: PropTypes.shape({
    handleChange: PropTypes.func.isRequired,
    handleBlur: PropTypes.func.isRequired
  }).isRequired
}

export default FormikPhoneInput

Also, in v3.x of this library, onBlur no longer sets event.target.value, so I removed onBlur from this code. If you think it's still required then you could add it.

jdmg94 commented 4 years ago

@jdmg94 So what does the ultimate Formik component look like then? Where does formik.handleChange go in this code?

import React, { useCallback } from 'react'
import PropTypes from 'prop-types'
import PhoneInput from 'react-phone-number-input`

function FormikPhoneInput(ref, { name, onChange, formik, ...rest }) {
  const onChange_ = useCallback(value => onChange(value || ''), [onChange])
  return (
    <PhoneInput
      {...rest}
      ref={ref}
      name={name}
      onChange={onChange_}/>
  )
}

FormikPhoneInput = React.forwardRef(FormikPhoneInput)

FormikPhoneInput.propTypes = {
  name: PropTypes.string.isRequired,
  onChange: PropTypes.func.isRequired,
  formik: PropTypes.shape({
    handleChange: PropTypes.func.isRequired,
    handleBlur: PropTypes.func.isRequired
  }).isRequired
}

export default FormikPhoneInput

Also, in v3.x of this library, onBlur no longer sets event.target.value, so I removed onBlur from this code. If you think it's still required then you could add it.

remove onChange_ and just call in your jsx onChange={formik.handleChange(name)} same with onBlur if you need validation on blur

catamphetamine commented 4 years ago

@jdmg94 Ok. So, Formik users, if @jdmg94 's solution works for you then post your code.

shirazz commented 4 years ago

@catamphetamine please find a working implementation for future reference. https://codesandbox.io/s/formik-react-phone-number-input-p5jvs

boriswinner commented 3 years ago

I have modified @AndrewStratigos 's version. This version works right with Formik's onBlur, allowing you to validate the field on blur.

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,
};
catamphetamine commented 3 years ago

@boriswinner Ok, what are the main issues when using this library with formik "out of the box"?

Perhaps we could add a section in the readme on using this library with formik or other libraries.

catamphetamine commented 3 years ago

@boriswinner

Formik emits a name attribute, which has to be passed to the element. When trying to use your component, Formik logs:

name is passed through so it's not a issue of this library

The right solution will be to add a 'name' prop to the component which will correspond to the name attribute in HTML element.

That's how it currently is.

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.

What? Not clear what you meant there.

MidhaTahir commented 3 years ago

In case if anyone needs help this is my current implementation. Below approach works with both onChange and onBlur events and doesn't throw error when string is given in field (disabled). Thanks to above replies:

import "react-phone-number-input/style.css";
import PhoneInput from "react-phone-number-input";

const initialValues = {
    phoneNumber: "",
};

const validate = (values) => {
    let errors = {};
    if (!values.phoneNumber) {
      errors.phoneNumber = "⋆Required";
    }
    return errors;
  };

const onSubmit = (values, onSubmitProps) => {....}

  const formik = useFormik({
    initialValues,
    onSubmit,
    validate,
  });

<PhoneInput
          className='anonymous'
          placeholder='Phone Number'
          name='phoneNumber'
          value={formik.values.phoneNumber}
          onChange={e => formik.setFieldValue("phoneNumber", e)}
          onBlur={formik.handleBlur("phoneNumber")}
        />
        {formik.touched.phoneNumber && formik.errors.phoneNumber ? (
          <div
            className='text-danger text-right'
            style={{ marginBottom: "-13px", fontSize: "12px" }}
          >
            {formik.errors.phoneNumber}
          </div>
        ) : null}
ackalhan commented 3 years ago

This works fine for me, "react-hook-form": "^7.7.0"

<Controller
    name="contact_number"
    control={control}
    rules={{ required: true }}
    render={({ field }) => (
      <PhoneInput
        {...field}
        country="us"
      />
    )}
/>
catamphetamine commented 3 years ago

@ackalhan I've updated the README with a react-hook-form section. https://github.com/catamphetamine/react-phone-number-input#react-hook-form

jagnani73 commented 2 years ago

@jdmg94 So what does the ultimate Formik component look like then? Where does formik.handleChange go in this code?

import React, { useCallback } from 'react'
import PropTypes from 'prop-types'
import PhoneInput from 'react-phone-number-input`

function FormikPhoneInput(ref, { name, onChange, formik, ...rest }) {
  const onChange_ = useCallback(value => onChange(value || ''), [onChange])
  return (
    <PhoneInput
      {...rest}
      ref={ref}
      name={name}
      onChange={onChange_}/>
  )
}

FormikPhoneInput = React.forwardRef(FormikPhoneInput)

FormikPhoneInput.propTypes = {
  name: PropTypes.string.isRequired,
  onChange: PropTypes.func.isRequired,
  formik: PropTypes.shape({
    handleChange: PropTypes.func.isRequired,
    handleBlur: PropTypes.func.isRequired
  }).isRequired
}

export default FormikPhoneInput

Also, in v3.x of this library, onBlur no longer sets event.target.value, so I removed onBlur from this code. If you think it's still required then you could add it.

What do we need to pass in onChange prop?

jagnani73 commented 2 years ago

If it helps, here is my phoneinput/formik control

import React, { useState } from 'react';
import PhoneInput from 'react-phone-number-input';
import PropTypes from 'prop-types';
import 'react-phone-number-input/style.css';
import { getIn } from 'formik';

const PhoneInputField = (props) => {
  const {
    className,
    field: { name, value },
    form: {
      errors, handleBlur, setFieldValue, touched,
    },
    form,
    label,
    country,
    onChange,
    disabled,
  } = props;

  const [isFocused, setFocused] = useState(false);
  const isError = getIn(touched, name) && getIn(errors, name);
  const errorStyle = isError ? 'error' : '';
  const filledStyle = (isFocused || value) ? 'filled' : '';
  const disabledStyle = disabled ? 'disabled' : '';

  const handleInputBlur = (e) => {
    setFocused(false);
    handleBlur(e);
  };

  const handleInputFocus = () => setFocused(true);

  const onValueChange = (phoneNumber) => {
    setFieldValue(name, phoneNumber);

    if (onChange !== null) {
      onChange(phoneNumber);
    }
  };

  return (
    <div className={`${className} ${errorStyle} ${filledStyle} ${disabledStyle} text-input-group`}>
      <PhoneInput
        placeholder="Enter phone number"
        name={name}
        value={value}
        onChange={onValueChange}
        country={country}
      />
      <label className="transition ml-10" htmlFor={name}>
        {label}
      </label>
      <div className="flex h-5 items-end text-red-100 text-xs">
        {isError && getIn(errors, name)}
      </div>
    </div>
  );
};

PhoneInputField.propTypes = {
  className: PropTypes.string,
  form: PropTypes.any.isRequired,
  field: PropTypes.any.isRequired,
  onChange: PropTypes.func,
  label: PropTypes.string,
  country: PropTypes.string,
  disabled: PropTypes.bool,
};

PhoneInputField.defaultProps = {
  className: '',
  label: '',
  onChange: null,
  country: 'AU',
  disabled: false,
};

export default PhoneInputField;

What do we need to pass in the onChange prop?

gift56 commented 1 year ago

Thanks a lot MidhaTahir Now I have finally passed the issue from react-phone-input-2😊😊

EOEboh commented 1 year ago

In case if anyone needs help this is my current implementation. Below approach works with both onChange and onBlur events and doesn't throw error when string is given in field (disabled). Thanks to above replies:

import "react-phone-number-input/style.css";
import PhoneInput from "react-phone-number-input";

const initialValues = {
    phoneNumber: "",
};

const validate = (values) => {
    let errors = {};
    if (!values.phoneNumber) {
      errors.phoneNumber = "⋆Required";
    }
    return errors;
  };

const onSubmit = (values, onSubmitProps) => {....}

  const formik = useFormik({
    initialValues,
    onSubmit,
    validate,
  });

<PhoneInput
          className='anonymous'
          placeholder='Phone Number'
          name='phoneNumber'
          value={formik.values.phoneNumber}
          onChange={e => formik.setFieldValue("phoneNumber", e)}
          onBlur={formik.handleBlur("phoneNumber")}
        />
        {formik.touched.phoneNumber && formik.errors.phoneNumber ? (
          <div
            className='text-danger text-right'
            style={{ marginBottom: "-13px", fontSize: "12px" }}
          >
            {formik.errors.phoneNumber}
          </div>
        ) : null}

Thank you so much. This helped me. Apparently, the trick was the formik.setFieldValue method