Search code examples
reactjsformikintl-tel-input

How to integrate react-intl-tel-input with formik?


I'm using Formik to handle forms in my ReactJs app, I would like to use react-intl-tel-input to handle a phone number, however I'm unable to integrate the handleChange, handleBlur and validations with Formik. Right now I'm using my form's state to save the phone number and its validation status, but this generate a problem with Formik by re-render my other fields.

Here is my phone number component:

<IntlTelInput
  fieldId="userPhoneNumber"
  fieldName="userPhoneNumber"
  value={values.userPhoneNumber}
  preferredCountries={preferredMobileCountries}
  css={['intl-tel-input', `form-control ${(!validPhoneNumber) ? 'is-invalid' : ''}`]}
  style={{display: 'block',width: '100%'}}
  format
  onPhoneNumberChange={this.handlePhoneChange}
/>
{!validPhoneNumber && <div className="invalid-feedback">Invalid phone number</div>}

Which is the correct way to accomplish this? I mean use a custom component but be able to use the handleChange, handleBlur and validation schema of Formik?

Thanks in advance...


Solution

  • This is not the optimal solution, but links back IntlTelInput to formik's setFieldTouched and setFieldValue.

    // @flow
    
    import React, {Component, Fragment} from 'react';
    import {ErrorMessage, Field} from 'formik';
    import IntlTelInput from 'react-intl-tel-input';
    
    export default class MobileField extends Component {
      formatPhoneNumberOutput(
        isValid: boolean,
        newNumber: string,
        countryData: Object,
        fullNumber: string,
        isExtension: boolean
      ) {
        if (isValid && fullNumber) {
          return fullNumber.replace(/(\s|-)/g, '');
        }
        return 'invalid_phone_number'; // caught by validator
      }
    
      render() {
        return (
          <Field
            name={name}
            render={({field, form: {errors, isSubmitting, touched, setFieldTouched, setFieldValue}}) => {
              return (
                <Fragment>
                  <IntlTelInput
                    defaultCountry="fr"
                    defaultValue={field.value}
                    disabled={isSubmitting}
                    fieldId={name}
                    fieldName={name}
                    onPhoneNumberBlur={() => {
                      setFieldTouched(name, true);
                    }}
                    onPhoneNumberChange={(...args) => {
                      setFieldValue(name, this.formatPhoneNumberOutput(...args));
                    }}
                    preferredCountries={['fr', 'gb', 'es', 'be', 'de']}
                  />
                  <ErrorMessage name={name} render={msg => <p>{msg}</p>} />
                </Fragment>
              );
            }}
          />
        );
      }
    }
    

    use a validator such as validate.js to check that phone number is not "invalid_phone_number"

    // @flow
    
    import _mapValues from 'lodash/mapValues';
    import validate from 'validate.js';
    
    export type Values = {
        mobile: string,
        landline: string
    };
    
    export default (values: Values) => {
        const options = {
            fullMessages: false
        };
      const validation: {[key: string]: string[]} = validate(
          values,
          {
              mobile: {
                  presence: {message: 'Please add a mobile phone number'},
                  format: {
                      pattern: '^((?!invalid_phone_number).)*$', // is not invalid_phone_number
                      message: 'This phone number looks like being invalid'
                  }
            },
            landline: {}
        },
        options
        );
        return _mapValues(validation, messages => messages[0]);
    };