🐛 Fix issue with duplicate button instances

On Block Checkout pages, there were multiple PaymentButton instances for the same context.
This commit is contained in:
Philipp Stracker 2024-08-07 22:36:35 +02:00
parent 95c7d4f7bc
commit e9c557a361
No known key found for this signature in database
3 changed files with 66 additions and 2 deletions

View file

@ -43,6 +43,35 @@ const addToDebuggingCollection = ( methodName, button ) => {
collection[ methodName ].push( button );
};
/**
* Provides a context-independent instance Map for `PaymentButton` components.
*
* This function addresses a potential issue in multi-context environments, such as pages using
* Block-components. In these scenarios, multiple React execution contexts can lead to duplicate
* `PaymentButton` instances. To prevent this, we store instances in a `Map` that is bound to the
* document's `body` (the rendering context) rather than to individual React components
* (execution contexts).
*
* The `Map` is created as a non-enumerable, non-writable, and non-configurable property of
* `document.body` to ensure its integrity and prevent accidental modifications.
*
* @return {Map<any, any>} A Map containing all `PaymentButton` instances for the current page.
*/
const getInstances = () => {
const collectionKey = '__ppcpPBInstances';
if ( ! document.body[ collectionKey ] ) {
Object.defineProperty( document.body, collectionKey, {
value: new Map(),
enumerable: false,
writable: false,
configurable: false,
} );
}
return document.body[ collectionKey ];
};
/**
* Base class for APM payment buttons, like GooglePay and ApplePay.
*
@ -132,6 +161,41 @@ export default class PaymentButton {
*/
#button = null;
/**
* Factory method to create a new PaymentButton while limiting a single instance per context.
*
* @param {string} context - Button context name.
* @param {unknown} externalHandler - Handler object.
* @param {Object} buttonConfig - Payment button specific configuration.
* @param {Object} ppcpConfig - Plugin wide configuration object.
* @param {unknown} contextHandler - Handler object.
* @return {PaymentButton} The button instance.
*/
static createButton(
context,
externalHandler,
buttonConfig,
ppcpConfig,
contextHandler
) {
const buttonInstances = getInstances();
const instanceKey = `${ this.methodId }.${ context }`;
if ( ! buttonInstances.has( instanceKey ) ) {
const button = new this(
context,
buttonConfig,
ppcpConfig,
externalHandler,
contextHandler
);
buttonInstances.set( instanceKey, button );
}
return buttonInstances.get( instanceKey );
}
/**
* Returns a list with all wrapper IDs for the implemented payment method, categorized by context.
*

View file

@ -90,9 +90,9 @@ class GooglepayButton extends PaymentButton {
constructor(
context,
externalHandler,
buttonConfig,
ppcpConfig,
externalHandler,
contextHandler
) {
super( context, buttonConfig, ppcpConfig );

View file

@ -20,7 +20,7 @@ class GooglepayManager {
bootstrap.handler
);
const button = new GooglepayButton(
const button = GooglepayButton.createButton(
bootstrap.context,
bootstrap.handler,
buttonConfig,