catamphetamine / react-phone-number-input

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

Cannot use formatting utils on the server-side #445

Closed jakeorr closed 1 month ago

jakeorr commented 1 month ago

Hello! Thanks for the useful phone number package. We've been able to successfully rely on it for our client-side needs.

I've run into issues when I try to use parts of the package on the react server-side though. In my use case we are rendering React code on the server to convert to a PDF. The important part is that there is no client side rendering taking place, so solutions with "use client" aren't an option.

When I run the following function on the server, it generates an error

import {
    formatPhoneNumberIntl,
    formatPhoneNumber as formatPhoneNumberLocal,
    getCountryCallingCode,
    parsePhoneNumber,
    type Country,
} from 'react-phone-number-input';

function formatPhoneNumber(phone: string, iso2?: Country) {
    const parsed = parsePhoneNumber(phone);
    const countryCallingCode = parsed?.countryCallingCode;
    const localCountryCallingCode = tryGetCountryCallingCode(iso2);

    if (countryCallingCode !== localCountryCallingCode) {
        return formatPhoneNumberIntl(phone);
    } else {
        return formatPhoneNumberLocal(phone);
    }
}

Here's the error:

ReactServerComponentsError:

You're importing a component that needs useRef. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default.
Learn more: https://nextjs.org/docs/getting-started/react-essentials

    ╭─[/.../node_modules/input-format/modules/react/Input.js:7:1]
  7 │ function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
  8 │
  9 │ // This is just `./ReactInput.js` rewritten in Hooks.
 10 │ import React, { useCallback, useRef } from 'react';
    ·                              ──────
 11 │ import PropTypes from 'prop-types';
 12 │ import { onChange as onInputChange, onKeyDown as onInputKeyDown } from '../inputControl.js'; // Usage:
 13 │ //
    ╰────

The error was caused by importing 'react-phone-number-input/min/index.js' in './src/utils/phone-number.ts'.

Maybe one of these should be marked as a client entry with "use client":
  ./src/utils/phone-number.ts

It looks like react-phone-number-input uses a single barrel file which includes all the imports, and it is a known issue that tree-shaking doesn't work with typescript barrel files. I suspect that is why I'm seeing this issue. I think what would solve this would be to also provide utility exports separately in another spot that didn't also pull in react code. Something like import { ...utils... } from 'react-phone-number-input/core'.

Env

Next.js 13 React 18

jakeorr commented 1 month ago

I was able to resolve the issue by using the utils from libphonenumber-js directly:

import { type CountryCode, default as parsePhoneNumber, getCountryCallingCode } from 'libphonenumber-js';

For the format functions I used the parsed number's functions:

const parsed = parsePhoneNumber(phone);

return parsed.formatInternational();
// or return parsed.formatNational();

Sort of a workaround, but likely how the packages are expected to be used. This will work for me.