Skip to content

Commit

Permalink
Revert "feat(PhoneNumberField): Custom formatter support + lazy load …
Browse files Browse the repository at this point in the history
…libphone…"

This reverts commit 2ee88e9.
  • Loading branch information
pladaria authored Oct 3, 2024
1 parent 4cf7db9 commit 64b7fe9
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 160 deletions.
81 changes: 3 additions & 78 deletions src/__tests__/phone-number-field-test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as React from 'react';
import {Form, PhoneNumberField, ThemeContextProvider} from '..';
import {render, screen, waitFor} from '@testing-library/react';
import {PhoneNumberField} from '..';
import {render, screen} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import ThemeContextProvider from '../theme-context-provider';
import {makeTheme} from './test-utils';

test.each`
Expand Down Expand Up @@ -46,79 +47,3 @@ test.each`
expect(onChangeValueSpy).toHaveBeenLastCalledWith(expectedValue, expectedValueRaw);
}
);

test.each`
contextRegionCode | prefix | typed | expectedValue | expectedValueRaw
${'ES'} | ${undefined} | ${'+123123123'} | ${'123123123'} | ${'1-2-3-1-2-3-1-2-3'}
${'ES'} | ${'+49'} | ${'69654321'} | ${'69654321'} | ${'6-9-6-5-4-3-2-1'}
${'DE'} | ${undefined} | ${'069654321'} | ${'069654321'} | ${'0-6-9-6-5-4-3-2-1'}
`(
`PhoneNumberField with custom formatter ($contextRegionCode, $prefix, $typed, $expectedValue, $expectedValueRaw)`,
async ({contextRegionCode, prefix, typed, expectedValue, expectedValueRaw}) => {
const onChangeValueSpy = jest.fn();
const theme = makeTheme();

render(
<ThemeContextProvider
theme={{
...theme,
i18n: {...theme.i18n, phoneNumberFormattingRegionCode: contextRegionCode},
}}
>
<PhoneNumberField
prefix={prefix}
label="Enter Phone"
name="phone"
onChangeValue={onChangeValueSpy}
format={(number) => {
// dumb formatter that just adds a dash between each digit
return number.replace(/[^\d]/g, '').split('').join('-');
}}
/>
</ThemeContextProvider>
);

await userEvent.type(screen.getByLabelText('Enter Phone'), typed);

expect(onChangeValueSpy).toHaveBeenLastCalledWith(expectedValue, expectedValueRaw);
}
);

test('PhoneNumberField gets formatted when libphonenumber loads', async () => {
const onChangeValueSpy = jest.fn();
const theme = makeTheme();

const TestComponent = () => {
const [isLib, setIsLib] = React.useState(false);
return (
<ThemeContextProvider
theme={{
...theme,
i18n: {...theme.i18n, phoneNumberFormattingRegionCode: 'ES'},
}}
>
<Form onSubmit={() => {}}>
<PhoneNumberField
label="Enter Phone"
name="phone"
onChangeValue={onChangeValueSpy}
format={isLib ? undefined : (number) => number}
/>
<button onClick={() => setIsLib(true)}>enable libphonenumber</button>
</Form>
</ThemeContextProvider>
);
};

render(<TestComponent />);

await userEvent.type(screen.getByLabelText('Enter Phone'), '654834455');

expect(onChangeValueSpy).toHaveBeenLastCalledWith('654834455', '654834455');

await userEvent.click(screen.getByText('enable libphonenumber'));

await waitFor(() => {
expect(onChangeValueSpy).toHaveBeenLastCalledWith('654834455', '654 83 44 55');
});
});
93 changes: 17 additions & 76 deletions src/phone-number-field.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client';
import * as React from 'react';
import {useRifm} from 'rifm';
import {formatAsYouType, formatToE164, parse, getRegionCodeForCountryCode} from '@telefonica/libphonenumber';
import {useFieldProps} from './form-context';
import {TextFieldBaseAutosuggest} from './text-field-base';
import {useTheme} from './hooks';
Expand All @@ -10,14 +11,8 @@ import {combineRefs} from './utils/common';
import type {CommonFormFieldProps} from './text-field-base';
import type {RegionCode} from './utils/region-code';

let libphonenumber: typeof import('@telefonica/libphonenumber');

type NumberFormatter = (number: string, regionCode: RegionCode) => string;

const formatPhoneDummy: NumberFormatter = (number) => number;

const formatPhoneUsingLibphonenumber: NumberFormatter = (number, regionCode) =>
libphonenumber.formatAsYouType(number.replace(/[^\d+*#]/g, ''), regionCode);
const formatPhone = (regionCode: RegionCode, number: string): string =>
formatAsYouType(number.replace(/[^\d+*#]/g, ''), regionCode);

type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'onInput'> & {
inputRef?: React.Ref<HTMLInputElement>;
Expand All @@ -26,58 +21,29 @@ type InputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'value' | 'o
onInput?: (event: React.FormEvent<HTMLInputElement>) => void;
prefix?: string;
e164?: boolean;
format?: NumberFormatter;
};

const isValidPrefix = (prefix: string): boolean => !!prefix.match(/^\+\d+$/);

const PhoneInput = ({
inputRef,
value,
defaultValue,
onChange: onChangeFromProps,
prefix,
e164,
format: formatFromProps,
...other
}: InputProps) => {
const PhoneInput = ({inputRef, value, defaultValue, onChange, prefix, e164, ...other}: InputProps) => {
const [selfValue, setSelfValue] = React.useState(defaultValue ?? '');
const ref = React.useRef<HTMLInputElement | null>(null);
const {i18n} = useTheme();
const formatRef = React.useRef<NumberFormatter>(formatFromProps || formatPhoneDummy);
/** this state is used to force a re-render when libphonenumber is loaded */
const [isLibphonenumberLoaded, setIsLibphonenumberloaded] = React.useState(false);

const regionCode = i18n.phoneNumberFormattingRegionCode;
const isControlledByParent = typeof value !== 'undefined';
const controlledValue = (isControlledByParent ? value : selfValue) as string;
const onChangeRef = React.useRef(onChangeFromProps);

React.useEffect(() => {
onChangeRef.current = onChangeFromProps;
}, [onChangeFromProps]);

React.useEffect(() => {
if (formatFromProps) {
formatRef.current = formatFromProps;
} else {
import('@telefonica/libphonenumber' /* webpackChunkName: "libphonenumber" */).then((lib) => {
libphonenumber = lib;
formatRef.current = formatPhoneUsingLibphonenumber;
setIsLibphonenumberloaded(true);
});
}
}, [formatFromProps]);

const handleChangeValue = React.useCallback(
(newFormattedValue: string) => {
if (!isControlledByParent) {
setSelfValue(newFormattedValue);
}
if (ref.current) {
onChangeRef.current?.(createChangeEvent(ref.current, newFormattedValue));
onChange?.(createChangeEvent(ref.current, newFormattedValue));
}
},
[isControlledByParent]
[isControlledByParent, onChange]
);

const format = React.useCallback(
Expand All @@ -91,15 +57,15 @@ const PhoneInput = ({
// then remove the prefix from the result
if (prefix && isValidPrefix(prefix)) {
const prefixedValue = prefix + value;
result = formatRef.current(prefixedValue, regionCode);
result = formatPhone(regionCode, prefixedValue);
if (result.startsWith(prefix)) {
result = result.slice(prefix.length).trim();
} else {
// fallback to regular formatting
result = formatRef.current(value, regionCode);
result = formatPhone(regionCode, value);
}
} else {
result = formatRef.current(value, regionCode);
result = formatPhone(regionCode, value);
}
return result.replace(/-/g, '@');
},
Expand All @@ -109,26 +75,18 @@ const PhoneInput = ({
const rifm = useRifm({
format,
value: controlledValue,
// Instead of calling `handleChangeValue` here, we call it in `useEffect` below.
// When the formatter changes (libphonenumber is lazy loaded), rifm should call `onChange`
// with the new formatted value but it doesn't, so we need to call it manually.
onChange: () => {},
onChange: handleChangeValue,
accept: /[\d\-+#*]+/g,
replace: (s) => s.replace(/@/g, '-'),
});

React.useEffect(() => {
handleChangeValue(rifm.value);
}, [rifm.value, handleChangeValue]);

return (
<input
{...other}
value={rifm.value}
onChange={rifm.onChange}
type="tel" // shows telephone keypad in Android and iOS
ref={combineRefs(inputRef, ref)}
data-using-libphonenumber={isLibphonenumberLoaded}
/>
);
};
Expand All @@ -138,7 +96,6 @@ export interface PhoneNumberFieldProps extends CommonFormFieldProps {
prefix?: string;
getSuggestions?: (value: string) => Array<string>;
e164?: boolean;
format?: NumberFormatter;
}

const PhoneNumberField = ({
Expand All @@ -154,34 +111,21 @@ const PhoneNumberField = ({
onBlur,
value,
defaultValue,
dataAttributes,
/**
* By default this component will use google's libphonenumber library to format numbers.
* The component will load libphonenumber on demand, so it won't impact the initial load time.
* You can opt-out of using libphonenumber by providing a custom formatter.
*/
format,
/** enabling e164 is incompatible with custom formatters because this requires libphonenumber */
e164,
dataAttributes,
...rest
}: PhoneNumberFieldProps): JSX.Element => {
const {i18n} = useTheme();

if (process.env.NODE_ENV !== 'production') {
if (e164 && format) {
console.error('[PhoneNumberField] enabling e164 is incompatible with custom formatters');
}
}

const processValue = (value: string) => {
if (e164 && libphonenumber && !format) {
if (e164) {
try {
const numericPrefix = (rest.prefix ?? '').replace(/[^\d]/g, '');
let regionCode = libphonenumber.getRegionCodeForCountryCode(numericPrefix);
let regionCode = getRegionCodeForCountryCode(numericPrefix);
if (!regionCode || regionCode === 'ZZ') {
regionCode = i18n.phoneNumberFormattingRegionCode;
}
return libphonenumber.formatToE164(libphonenumber.parse(value, regionCode));
return formatToE164(parse(value, regionCode));
} catch (e) {
return '';
}
Expand Down Expand Up @@ -212,12 +156,9 @@ const PhoneNumberField = ({
{...rest}
{...fieldProps}
type="phone"
inputProps={{prefix: rest.prefix, format}}
inputProps={{prefix: rest.prefix}}
inputComponent={PhoneInput}
dataAttributes={{
'component-name': 'PhoneNumberField',
...dataAttributes,
}}
dataAttributes={{'component-name': 'PhoneNumberField', ...dataAttributes}}
/>
);
};
Expand Down
5 changes: 0 additions & 5 deletions src/test-utils/ssr.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,6 @@ export const createServer = (): http.Server => {
return;
}

if (moduleName.includes('telefonica_libphonenumber')) {
serveFileInPath(path.join(__dirname, '..', '..', 'public', 'ssr', `${moduleName}`));
return;
}

let Component;
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
Expand Down
2 changes: 1 addition & 1 deletion src/text-field-base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ interface TextFieldBaseProps {
onBlur?: React.FocusEventHandler;
onFocus?: React.FocusEventHandler;
onKeyDown?: (event: React.KeyboardEvent) => void;
inputProps?: {[name: string]: unknown};
inputProps?: {[name: string]: string | number | undefined};
inputComponent?: React.ComponentType<any>;
shrinkLabel?: boolean;
focus?: boolean;
Expand Down

0 comments on commit 64b7fe9

Please sign in to comment.