Skip to content

Fix: Enhance loadCoreSdkScript#845

Open
EvanReinstein wants to merge 7 commits intomainfrom
feat/enhance-load-core-sdk-script
Open

Fix: Enhance loadCoreSdkScript#845
EvanReinstein wants to merge 7 commits intomainfrom
feat/enhance-load-core-sdk-script

Conversation

@EvanReinstein
Copy link
Contributor

loadCoreSdkScript has a condition that prevents duplicate script loads, however there is an edge-case in the current logic. If loadCoreSdkScript is called twice (like by React StrictMode for example) the core script is added to the DOM and is in the process of loading, i.e. window.paypal is still undefined, when the function runs for the second time. The current condition evaluates to false, resulting in another script added to the DOM.

This change updates loadCoreSdkScript to prevent the double load.

@EvanReinstein EvanReinstein requested a review from a team as a code owner February 27, 2026 18:16
@changeset-bot
Copy link

changeset-bot bot commented Feb 27, 2026

🦋 Changeset detected

Latest commit: 6093419

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@paypal/paypal-js Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@EvanReinstein EvanReinstein changed the title Feat: Enhance loadCoreSdkScript Fix: Enhance loadCoreSdkScript Feb 27, 2026
Copy link
Contributor

@nityasp nityasp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!!

}

// Script tag exists but hasn't finished loading yet (e.g., React StrictMode double-invoke)
if (currentScript) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to add some tests for this situation? since this impacts the load logic 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea great point, I will push some 👍


if (window.paypal?.version.startsWith("6") && currentScript) {
// Script already loaded and namespace is available — return immediately
if (window.paypal?.version?.startsWith("6") && currentScript) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to take into account the custom namespace option?

Suggested change
if (window.paypal?.version?.startsWith("6") && currentScript) {
const windowNamespace = options.dataNamespace ?? "paypal";
if (window[windowNamespace]?.version?.startsWith("6") && currentScript) {

Copy link
Contributor

@HackTheW2d HackTheW2d Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great catch! Same doubt here. But don't we always load at window.paypal internally? 🤔 Correct me if I am wrong :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In V6 we use window__paypal_sdk__ as the internal namespace. Here's an example when using data-namespace="paypalV6":

<script async="" src="https://www.sandbox.paypal.com/web-sdk/v6/core" data-namespace="paypalV6"></script>

Ex:

Image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the clarification! TIL

if (window.paypal?.version.startsWith("6") && currentScript) {
// Script already loaded and namespace is available — return immediately
if (window.paypal?.version?.startsWith("6") && currentScript) {
return Promise.resolve(window.paypal as unknown as PayPalV6Namespace);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here with what gets returned.

Suggested change
return Promise.resolve(window.paypal as unknown as PayPalV6Namespace);
return Promise.resolve(window[windowNamespace] as unknown as PayPalV6Namespace);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2026-02-27 at 3 14 10 PM

Confirmed that this update works with the custom namespace as well as window.paypal

currentScript.addEventListener(
"error",
() => {
reject(new Error(`The PayPal SDK script failed to load.`));
Copy link
Contributor

@HackTheW2d HackTheW2d Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message seems a bit vague to me. Can we make it more specific? :)

Suggested change
reject(new Error(`The PayPal SDK script failed to load.`));
reject(new Error(
`The script "${currentScript.src}" failed to load.
Check the HTTP status code and response body in DevTools to
learn more.`
));

if (currentScript) {
return new Promise<PayPalV6Namespace>((resolve, reject) => {
const namespace = options.dataNamespace ?? "paypal";
currentScript.addEventListener(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we go this route, we should look into sharing these callbacks with what is passed into insertScriptElement with onSuccess and onError. That function uses these same event listeners under the hood

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok i'll add this to my near-term ToDos

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants