Skip to content

Commit

Permalink
chore: move registered fields state to registry hook
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebastian Fernandez committed Apr 16, 2024
1 parent d4db167 commit f817e41
Show file tree
Hide file tree
Showing 15 changed files with 101 additions and 90 deletions.
5 changes: 3 additions & 2 deletions src/components/cardFields/PayPalCVVField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { mock } from "jest-mock-extended";
import { PayPalCVVField } from "./PayPalCVVField";
import { PayPalScriptProvider } from "../PayPalScriptProvider";
import { PayPalCardFieldsProvider } from "./PayPalCardFieldsProvider";
import { PayPalCardFieldsComponent } from "../../types";
import { CARD_FIELDS_CONTEXT_ERROR } from "../../constants";

import type { PayPalCardFieldsComponent } from "../../types";
import type { ReactNode } from "react";

const onError = jest.fn();
Expand Down Expand Up @@ -217,7 +218,7 @@ describe("PayPalCVVField", () => {

await waitFor(() => expect(onError).toBeCalledTimes(1));
expect(onError.mock.calls[0][0].message).toBe(
"Individual CardFields must be rendered inside the PayPalCardFieldsProvider"
CARD_FIELDS_CONTEXT_ERROR
);
spyConsoleError.mockRestore();
});
Expand Down
6 changes: 3 additions & 3 deletions src/components/cardFields/PayPalCVVField.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React from "react";

import { usePayPalCardFields } from "./hooks";
// import { usePayPalCardFields } from "./hooks";
import { PayPalCardFieldsIndividualFieldOptions } from "../../types";
import { PayPalCardField } from "./PayPalCardField";

export const PayPalCVVField: React.FC<
PayPalCardFieldsIndividualFieldOptions
> = (options) => {
const { cvvField } = usePayPalCardFields();
// const { cvvField } = usePayPalCardFields();

return (
<PayPalCardField
fieldRef={cvvField}
// fieldRef={cvvField}
fieldName="CVVField"
{...options}
/>
Expand Down
33 changes: 17 additions & 16 deletions src/components/cardFields/PayPalCardField.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import React, { useEffect, useRef, useState } from "react";

import { usePayPalCardFields } from "./hooks";
import { hasChildren, ignore } from "./utils";
import {
PayPalCardFieldsIndividualField,
import { hasChildren } from "./utils";
import { CARD_FIELDS_CONTEXT_ERROR } from "../../constants";

import type {
FieldComponentName,
PayPalCardFieldsIndividualFieldOptions,
} from "../../types";

export const PayPalCardField: React.FC<
PayPalCardFieldsIndividualFieldOptions & {
fieldRef: React.MutableRefObject<PayPalCardFieldsIndividualField | null>;
fieldName: "NameField" | "NumberField" | "CVVField" | "ExpiryField";
fieldName: FieldComponentName;
}
> = ({ className, fieldRef, fieldName, ...options }) => {
const { cardFields, registerField, unregisterField } =
> = ({ className, fieldName, ...options }) => {
const { cardFieldsForm, registerField, unregisterField } =
usePayPalCardFields();

const containerRef = useRef<HTMLDivElement>(null);
Expand All @@ -22,27 +23,27 @@ export const PayPalCardField: React.FC<
const [, setError] = useState(null);

function closeComponent() {
unregisterField(`PayPal${fieldName}`);
fieldRef.current?.close().catch(ignore);
unregisterField(fieldName);
}

useEffect(() => {
if (!cardFields) {
if (!cardFieldsForm) {
setError(() => {
throw new Error(
"Individual CardFields must be rendered inside the PayPalCardFieldsProvider"
);
throw new Error(CARD_FIELDS_CONTEXT_ERROR);
});
return closeComponent;
}
if (!containerRef.current) {
return closeComponent;
}

registerField(`PayPal${fieldName}`);
fieldRef.current = cardFields[fieldName](options);
const registeredField = registerField(
fieldName,
options,
cardFieldsForm
);

fieldRef.current.render(containerRef.current).catch((err) => {
registeredField?.render(containerRef.current).catch((err) => {
if (!hasChildren(containerRef)) {
// Component no longer in the DOM, we can safely ignore the error
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { render, waitFor } from "@testing-library/react";
import { ErrorBoundary } from "react-error-boundary";
import { PayPalNamespace, loadScript } from "@paypal/paypal-js";
import { mock } from "jest-mock-extended";
import { PayPalCardFieldsComponent } from "@paypal/paypal-js/types/components/card-fields";

import { PayPalScriptProvider } from "../PayPalScriptProvider";
import { PayPalCardFieldsProvider } from "./PayPalCardFieldsProvider";
Expand All @@ -15,6 +14,7 @@ import { PayPalNumberField } from "./PayPalNumberField";
import { PayPalCVVField } from "./PayPalCVVField";
import { PayPalExpiryField } from "./PayPalExpiryField";

import type { PayPalCardFieldsComponent } from "@paypal/paypal-js/types/components/card-fields";
import type { ReactNode } from "react";

const MOCK_ELEMENT_ID = "mock-element";
Expand Down
24 changes: 7 additions & 17 deletions src/components/cardFields/PayPalCardFieldsProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { usePayPalCardFieldsRegistry } from "./hooks";
import type {
PayPalCardFieldsComponentOptions,
PayPalCardFieldsComponent,
PayPalCardFieldsIndividualField,
} from "@paypal/paypal-js/types/components/card-fields";

type CardFieldsProviderProps = PayPalCardFieldsComponentOptions & {
Expand All @@ -23,23 +22,18 @@ export const PayPalCardFieldsProvider = ({
...props
}: CardFieldsProviderProps): JSX.Element => {
const [{ options, loadingStatus }] = useScriptProviderContext();
const { registerField, unregisterField } = usePayPalCardFieldsRegistry();
const { fields, registerField, unregisterField } =
usePayPalCardFieldsRegistry();

const [cardFields, setCardFields] =
const [cardFieldsForm, setCardFieldsForm] =
useState<PayPalCardFieldsComponent | null>(null);
const cardFieldsInstance = useRef<PayPalCardFieldsComponent | null>(null);

const nameField = useRef<PayPalCardFieldsIndividualField | null>(null);
const numberField = useRef<PayPalCardFieldsIndividualField | null>(null);
const cvvField = useRef<PayPalCardFieldsIndividualField | null>(null);
const expiryField = useRef<PayPalCardFieldsIndividualField | null>(null);

const [isEligible, setIsEligible] = useState(false);
// We set the error inside state so that it can be caught by React's error boundary
const [, setError] = useState(null);

useEffect(() => {
// Only render the card fields when script is loaded and cardFields is eligible
if (!(loadingStatus === SCRIPT_LOADING_STATE.RESOLVED)) {
return;
}
Expand Down Expand Up @@ -74,11 +68,10 @@ export const PayPalCardFieldsProvider = ({
}

setIsEligible(cardFieldsInstance.current.isEligible());
setCardFields(cardFieldsInstance.current);
setCardFieldsForm(cardFieldsInstance.current);

// Clean up after component unmounts
return () => {
setCardFields(null);
setCardFieldsForm(null);
cardFieldsInstance.current = null;
};
}, [loadingStatus]); // eslint-disable-line react-hooks/exhaustive-deps
Expand All @@ -92,11 +85,8 @@ export const PayPalCardFieldsProvider = ({
<div style={{ width: "100%" }}>
<PayPalCardFieldsContext.Provider
value={{
cardFields,
nameField,
numberField,
cvvField,
expiryField,
cardFieldsForm,
fields,
registerField,
unregisterField,
}}
Expand Down
5 changes: 3 additions & 2 deletions src/components/cardFields/PayPalExpiryField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { mock } from "jest-mock-extended";
import { PayPalExpiryField } from "./PayPalExpiryField";
import { PayPalScriptProvider } from "../PayPalScriptProvider";
import { PayPalCardFieldsProvider } from "./PayPalCardFieldsProvider";
import { PayPalCardFieldsComponent } from "../../types";
import { CARD_FIELDS_CONTEXT_ERROR } from "../../constants";

import type { PayPalCardFieldsComponent } from "../../types";
import type { ReactNode } from "react";

const onError = jest.fn();
Expand Down Expand Up @@ -217,7 +218,7 @@ describe("PayPalExpiryField", () => {

await waitFor(() => expect(onError).toBeCalledTimes(1));
expect(onError.mock.calls[0][0].message).toBe(
"Individual CardFields must be rendered inside the PayPalCardFieldsProvider"
CARD_FIELDS_CONTEXT_ERROR
);
spyConsoleError.mockRestore();
});
Expand Down
6 changes: 3 additions & 3 deletions src/components/cardFields/PayPalExpiryField.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React from "react";

import { usePayPalCardFields } from "./hooks";
// import { usePayPalCardFields } from "./hooks";
import { PayPalCardFieldsIndividualFieldOptions } from "../../types";
import { PayPalCardField } from "./PayPalCardField";

export const PayPalExpiryField: React.FC<
PayPalCardFieldsIndividualFieldOptions
> = (options) => {
const { expiryField } = usePayPalCardFields();
// const { expiryField } = usePayPalCardFields();

return (
<PayPalCardField
fieldRef={expiryField}
// fieldRef={expiryField}
fieldName="ExpiryField"
{...options}
/>
Expand Down
5 changes: 3 additions & 2 deletions src/components/cardFields/PayPalNameField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { mock } from "jest-mock-extended";
import { PayPalNameField } from "./PayPalNameField";
import { PayPalScriptProvider } from "../PayPalScriptProvider";
import { PayPalCardFieldsProvider } from "./PayPalCardFieldsProvider";
import { PayPalCardFieldsComponent } from "../../types";
import { CARD_FIELDS_CONTEXT_ERROR } from "../../constants";

import type { PayPalCardFieldsComponent } from "../../types";
import type { ReactNode } from "react";

const onError = jest.fn();
Expand Down Expand Up @@ -217,7 +218,7 @@ describe("PayPalNameField", () => {

await waitFor(() => expect(onError).toBeCalledTimes(1));
expect(onError.mock.calls[0][0].message).toBe(
"Individual CardFields must be rendered inside the PayPalCardFieldsProvider"
CARD_FIELDS_CONTEXT_ERROR
);
spyConsoleError.mockRestore();
});
Expand Down
6 changes: 3 additions & 3 deletions src/components/cardFields/PayPalNameField.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React from "react";

import { usePayPalCardFields } from "./hooks";
// import { usePayPalCardFields } from "./hooks";
import { PayPalCardFieldsIndividualFieldOptions } from "../../types";
import { PayPalCardField } from "./PayPalCardField";

export const PayPalNameField: React.FC<
PayPalCardFieldsIndividualFieldOptions
> = (options) => {
const { nameField } = usePayPalCardFields();
// const { nameField } = usePayPalCardFields();

return (
<PayPalCardField
fieldRef={nameField}
// fieldRef={nameField}
fieldName="NameField"
{...options}
/>
Expand Down
5 changes: 3 additions & 2 deletions src/components/cardFields/PayPalNumberField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import { mock } from "jest-mock-extended";
import { PayPalNumberField } from "./PayPalNumberField";
import { PayPalScriptProvider } from "../PayPalScriptProvider";
import { PayPalCardFieldsProvider } from "./PayPalCardFieldsProvider";
import { PayPalCardFieldsComponent } from "../../types";
import { CARD_FIELDS_CONTEXT_ERROR } from "../../constants";

import type { PayPalCardFieldsComponent } from "../../types";
import type { ReactNode } from "react";

const onError = jest.fn();
Expand Down Expand Up @@ -217,7 +218,7 @@ describe("PayPalNumberField", () => {

await waitFor(() => expect(onError).toBeCalledTimes(1));
expect(onError.mock.calls[0][0].message).toBe(
"Individual CardFields must be rendered inside the PayPalCardFieldsProvider"
CARD_FIELDS_CONTEXT_ERROR
);
spyConsoleError.mockRestore();
});
Expand Down
6 changes: 3 additions & 3 deletions src/components/cardFields/PayPalNumberField.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import React from "react";

import { usePayPalCardFields } from "./hooks";
// import { usePayPalCardFields } from "./hooks";
import { PayPalCardFieldsIndividualFieldOptions } from "../../types";
import { PayPalCardField } from "./PayPalCardField";

export const PayPalNumberField: React.FC<
PayPalCardFieldsIndividualFieldOptions
> = (options) => {
const { numberField } = usePayPalCardFields();
// const { numberField } = usePayPalCardFields();

return (
<PayPalCardField
fieldRef={numberField}
// fieldRef={numberField}
fieldName="NumberField"
{...options}
/>
Expand Down
29 changes: 9 additions & 20 deletions src/components/cardFields/context.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
import { createContext } from "react";
import {
PayPalCardFieldsComponent,
PayPalCardFieldsIndividualField,
} from "@paypal/paypal-js/types/components/card-fields";

import { ignore } from "./utils";
import { FieldComponentName } from "./hooks";
import { RegistryHookReturnType } from "./hooks";

import type { PayPalCardFieldsComponent } from "@paypal/paypal-js/types/components/card-fields";

export type PayPalCardFieldsContextType = {
cardFields: PayPalCardFieldsComponent | null;
nameField: React.MutableRefObject<PayPalCardFieldsIndividualField | null>;
numberField: React.MutableRefObject<PayPalCardFieldsIndividualField | null>;
expiryField: React.MutableRefObject<PayPalCardFieldsIndividualField | null>;
cvvField: React.MutableRefObject<PayPalCardFieldsIndividualField | null>;
registerField: (field: FieldComponentName) => void;
unregisterField: (field: FieldComponentName) => void;
};
cardFieldsForm: PayPalCardFieldsComponent | null;
} & RegistryHookReturnType;

export const PayPalCardFieldsContext =
createContext<PayPalCardFieldsContextType>({
cardFields: null,
nameField: { current: null },
numberField: { current: null },
cvvField: { current: null },
expiryField: { current: null },
registerField: ignore, // implementation is inside provider
unregisterField: ignore, // implementation is inside provider
cardFieldsForm: null,
fields: {},
registerField: ignore, // implementation is inside hook and passed through the provider
unregisterField: ignore, // implementation is inside hook and passed through the provider
});
Loading

0 comments on commit f817e41

Please sign in to comment.