3

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...

lisdey89
  • 222
  • 4
  • 16

4 Answers4

2

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]);
};
gasp
  • 576
  • 4
  • 10
  • Hello @gasp thanks for the response, however don't fully understand how to integrate it with validate.js? I mean where can I specify the rules to validate the phone? – lisdey89 Feb 18 '19 at 18:38
2

In case anybody is looking to integrate into a functional component (rather than a class based component) this may save you some time! :)

import React, { useState } from 'react';
import PropTypes from 'prop-types';
import IntlTelInput from 'react-intl-tel-input';
import { Field } from 'formik';
import 'react-intl-tel-input/dist/main.css';

const TelephoneInput = ({ name, ...props }) => {

  const [telephoneValid, setTelephoneValid] = useState(true);
  const setValidity = valid => {
    setTelephoneValid(valid);
  };
  // process number into string with area code for submission
  const processNumber = (isValid, phone, country) => {
    return `+${country.dialCode} ${phone}`;
  };

  return (
    <>
      <Field name={name}>
        {(
          { field: { value },
          form: { isSubmitting, setFieldTouched, setFieldValue } }) =>
            <IntlTelInput
              {...props}
              containerClassName="intl-tel-input"
              inputClassName={telephoneValid ? 'valid' : 'invalid'}
              label="telephone"
              defaultValue={value}
              disabled={isSubmitting}
              fieldId={name}
              fieldName={name}
              onPhoneNumberBlur={(isValid) => {
                setFieldTouched(name, true);
                setValidity(isValid);
              }}
              onPhoneNumberChange={(isValid, phone, country) => {
                setFieldValue(name, processNumber(isValid, phone, country));
              }}
            />
        }
      </Field>
    </>
  );
};

TelephoneInput.propTypes = {
  name: PropTypes.string.isRequired,
};
export default TelephoneInput;
Oli C
  • 1,120
  • 13
  • 36
1

If you're using the useFormik hook

import IntlTelInput from "react-intl-tel-input";


<IntlTelInput
  inputClassName="tel-number"
  fieldId="number"
  fieldName="number"
  onPhoneNumberChange={(
    isValid,
    value,
    selectedCountryData,
    fullNumber
  ) => {
    formik.handleChange("number")(fullNumber);
  }}
/>
0

I managed to do everything using ref

Here's my code for inspiration

"use client";

import React, { useState } from 'react'

import { Formik, FormikProps, Form, ErrorMessage, FormikErrors } from 'formik';
import './intl-tel-input-override.css';
import IntlTelInput from "react-intl-tel-input";
import 'react-intl-tel-input/dist/main.css';

interface FormValues {
    phone: string;
}

function SendOTPForm({
    csrfToken
}: {
    csrfToken: string | undefined
}) {

    const intlTelephoneInputRef = React.useRef<any>(null);

    return (
        <Formik
            initialValues={{
                phone: '',
            }}
            onSubmit={async (values) => {
                console.log(values)
            }
            }
            validate={values => {
                const errors: FormikErrors<FormValues> = {};
                if (!values.phone) {
                    errors.phone = 'Please enter your phone number';
                }
                if (intlTelephoneInputRef.current) {
                    const value = intlTelephoneInputRef.current.state.value;
                    const fullNumber = intlTelephoneInputRef.current.formatFullNumber(
                        value
                    )
                    const isValid = intlTelephoneInputRef.current.isValidNumber(fullNumber)
                    if (!isValid) {
                        errors.phone = 'Please enter a valid phone number'
                    }
                }
                return errors;
            }
            }
        >
            {
                ({ values, setFieldValue, setErrors, setTouched }: FormikProps<FormValues>) => (
                    <Form className="space-y-4">
                        <div>
                            <label htmlFor="phone" className="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300">
                                Continue using phone number {values.phone}
                            </label>
                            <IntlTelInput
                                fieldName="phone"
                                fieldId="phone"
                                inputClassName="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white"
                                placeholder="Enter your phone number to continue"
                                preferredCountries={[
                                    "in",
                                    "us"
                                ]}
                                defaultCountry="in"
                                autoComplete="tel"
                                geoIpLookup={(callback) => {
                                    fetch("https://ipapi.co/json")
                                        .then(function (res) { return res.json(); })
                                        .then(function (data) { callback(data.country_code); })
                                        .catch(function () { callback("us"); });
                                }}
                                ref={intlTelephoneInputRef}
                                onPhoneNumberChange={(
                                    _isValid,
                                    value,
                                    _selectedCountryData,
                                    fullNumber
                                ) => {
                                    setFieldValue('phone', fullNumber)
                                    // If length is less than 10 then do not set it as touched for 
                                    // sake of user experience
                                    if (value.length < 10) {
                                        return
                                    }
                                    setTouched({ phone: true }, true)
                                }}
                            />
                            <ErrorMessage
                                name="phone"
                                component={"div"}
                                className="text-red-500 mt-2"
                            />
                        </div>
                        <button type="submit" className="w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">
                            Request OTP
                        </button>
                    </Form>
                )
            }

        </Formik>
    )
}

export default SendOTPForm
Charanjit Singh
  • 809
  • 10
  • 23