Как интегрировать response-intl-tel-input с formik?

Я использую Formik для обработки форм в своем приложении ReactJ, я хотел бы использовать response-intl-tel-input для обработки телефонного номера, однако я не могу интегрировать handleChange, handleBlur и проверки с Formik. Прямо сейчас я использую состояние моей формы, чтобы сохранить номер телефона и его статус проверки, но это создает проблему с Formik путем повторного рендеринга моих других полей.

Вот мой компонент номера телефона:

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

Какой правильный способ сделать это? Я имею в виду использовать пользовательский компонент, но иметь возможность использовать handleChange, handleBlur и схему проверки Formik?

Заранее спасибо...

4 ответа

В случае, если кто-то хочет интегрироваться в функциональный компонент (а не в компонент на основе классов), это может сэкономить вам время!:)

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;

Это не оптимальное решение, но связывает обратно IntlTelInput с setFieldTouched и setFieldValue от formik.

используйте валидатор, например validate.js, чтобы убедиться, что номер телефона не является "invalid_phone_number"

// @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>
          );
        }}
      />
    );
  }
}

Если вы используете хук useFormik

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


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

Мне удалось сделать все, используяref

Вот мой код для вдохновения

      "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
Другие вопросы по тегам