Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Getting "Error: zoid destroyed all" in the console and only 1 button on the page renders. #30

Open
markoliverbrawn opened this issue Sep 16, 2019 · 23 comments

Comments

@markoliverbrawn
Copy link

markoliverbrawn commented Sep 16, 2019

Have you seen this? Is there a fix? My implementation is:

<PayPalButton
	amount="0.01"
	onSuccess={ (details, data) => {
		console.log("Transaction completed", details);
	} }
	options={ {
		clientId: 'AVYn2RUPC.....',
		currency: 'NZD',
		merchantId: '3T6K....',
	} }
	style = { {
		color: 'black',
		layout: 'horizontal'
	} }
/>
@Osperye
Copy link

Osperye commented Sep 19, 2019

I have the same problem, I am trying to implement two buttons in the same modal: one for subscriptions and one for one-time payments. Even though I pass different props to them etc. when the page is rendered I get the same error and only one button is rendered.

@markoliverbrawn
Copy link
Author

I bailed in the end, and implemented a redux cart and payment page with just a single button on.

@ovxrfl0w
Copy link

ovxrfl0w commented Nov 3, 2019

I have the same problem. I can't render multiple buttons on a single page.
As @Luehang said he tried rendering multiple buttons a single page, and it worked, then I'd like to request an example of how to do that. I always get the "zoid destroyed all" error.

@RebecaRey
Copy link

I had the same error, it seems to have been caused by loading the PayPal script more than once. I fixed it by loading the script just once in the head, then loading the two buttons where they should be.

@Dara-To
Copy link

Dara-To commented Mar 2, 2020

I have the same issue. To try to hide the PayPal client ID and make it dynamic depending on the environment, I removed the script tag and added a variable in the options props. Only 1 button is displayed in my pricing card (I have 2 for different pricing plan) and the error is Error: zoid destroyed all.

options={{
        clientId: PAYPAL_CLIENT_ID,
}}

@so2liu
Copy link

so2liu commented May 10, 2020

I had the same error, it seems to have been caused by loading the PayPal script more than once. I fixed it by loading the script just once in the head, then loading the two buttons where they should be.

I am using the official sdk with React and met the same problem. Following RebecaRey, I set a more detailed loading state to differ three period: prepare to load, loading, loaded.

Normal boolean loaded state will (I guess) let sdk load again when loading. Here is my code:

// with bug
  const [loaded, setLoaded] = useState(false);
  useEffect(() => {
    if (!loaded) {
      const script = document.createElement("script");
      script.src = `https://www.paypal.com/sdk/js?client-id=${
        isDeveloperDev ? testID : productID
      }&currency=EUR&disable-funding=credit,card,giropay,sepa,sofort`;
      script.addEventListener("load", () => setLoaded(true));
      document.body.appendChild(script);
      console.log("append script");
    }
// ...
}), [loaded, ...]

The code above will log multiple "append script". The code below logs only once.

// without bug
  const [loadState, setLoadState] = useState({ loading: false, loaded: false });
  useEffect(() => {
    if (!loadState.loading && !loadState.loaded) {
      setLoadState({ loading: true, loaded: false });
      const script = document.createElement("script");
      script.src = `https://www.paypal.com/sdk/js?client-id=${
        isDeveloperDev ? testID : productID
      }&currency=EUR&disable-funding=credit,card,giropay,sepa,sofort`;
      script.addEventListener("load", () =>
        setLoadState({ loading: false, loaded: true })
      );
      document.body.appendChild(script);
      console.log("append script");
    }
// ...
}), [loadState, ...]

I guess it's because of deps of useEffect.

  1. Start load script.
  2. Before loaded, some deps like amount / description comes, re-run useEffect.
  3. At this time, loaded is still false. Then load the script again.

@NicholasG04
Copy link

I tried adding the script tag to the head and removing options={{}} but it now intermittently has the same error. If I refresh the page it works fine, yet if I use a Link to it it has the error. For reference, I'm using NextJS

@NicholasG04
Copy link

This should be fixable by just checking if a script already exists including the word 'paypal', however there's a large problem with the way this library works in this regard. It allows different options for each button, and obviously the PayPal script tag has to have only one set of options. You'd have to declare in the documentation 'all buttons must have the same set of options' for my fix to work, but it would work I believe save that caveat.

@alfredsgenkins
Copy link

I managed to resolve that, by adding:

componentWillUnmount() {
    // resetting all pay-pal related properties
    Object.keys(window).forEach((key) => {
        if (/paypal|zoid|post_robot/.test(key)) {
            // eslint-disable-next-line fp/no-delete
            delete window[key];
        }
    });
}

Not really a fix, but it helped me to stop receiving this error message.

@alfredsgenkins
Copy link

We are actually not using this library, just found the issue to be same. Looks like the error of PayPal itself.

@NicholasG04
Copy link

I believe it occurs when multiple instances of the SDK are loaded in and have suggested a fix as well as the caveats but have not had time to attempt a fix.

@mtshin
Copy link

mtshin commented Jun 21, 2020

I managed to resolve that, by adding:

componentWillUnmount() {
    // resetting all pay-pal related properties
    Object.keys(window).forEach((key) => {
        if (/paypal|zoid|post_robot/.test(key)) {
            // eslint-disable-next-line fp/no-delete
            delete window[key];
        }
    });
}

Not really a fix, but it helped me to stop receiving this error message.

This and/or a combination of loading or unloading the paypal script on component lifecycle events seem to be valid workarounds for this issue 👍

@imekinox
Copy link

There is an easier way to solve this, if you look at the code, there is a onButtonReady prop, and also this component checks that the sdk is already present in window.paypal.

So, add this state variable:

const [paypalLoaded, setPaypalLoaded] = useState(false);

Then set to only one component ( the first one, doesn't matter which one ) the prop onButtonReady to set the paypalLoaded state to true like so:

                <PayPalButton
                  onButtonReady={() => {
                    setPaypalLoaded(true);
                  }}

The rest of the components should check against this state variable to be rendered only after the first one loaded the SDK, so SDK is not loaded multiple times:

                { paypalLoaded
                  ? (
                    <PayPalButton
                      ... // No onButtonReady needed here...
                    />
                  ) : null }

Note: this also works on Next.js.

@huggler
Copy link

huggler commented Jun 27, 2020

In your paypalButton component use this

useEffect(()=> {

let paypalScript = document.getElementById('paypal-script');

new Promise(resolve => {

  if(paypalScript){
    resolve(true)
  }else{
    const target = document.body;
    const tag = document.createElement('script');
    tag.async = false;
    tag.id = 'paypal-script';
    tag.src = 'https://www.paypal.com/sdk/js?client-..........';
    target.appendChild(tag);
    tag.addEventListener('load', resolve, {
      once: true
    });
  }
}).then(resp => {

@raulcontrerasrubio
Copy link

I have solved it with a mix of mtshin and imekinox solutions:

I have two different pages. In the first one, I only have subscriptions. In the second one, simple checkouts.
On both pages, I have a useEffect hook to remove the Paypal SDK when the page unmounts. The reason to use it is that subscriptions and simple checkouts use different configuration of the SDK:

useEffect(() => {
    return () => {
      Object.keys(window).forEach(key => {
        if (/paypal|zoid|post_robot/.test(key)) {
          delete window[key];
        }
      });

      document.querySelectorAll('script[src*="www.paypal.com/sdk"]').forEach(node => node.remove());
    };
  }, []);

Also, on both pages I have the loading state of Paypal:

const [paypalSDKReady, setPaypalSDKReady] = useState(false);

Then I only render one button. The other ones are rendered when paypalSDKReady is true:

<PayPalButton
          amount={price}
          createOrder={createOrder}
          onApprove={onApprove}
          onError={onError}
          options={{clientId: process.env.REACT_APP_PAYPAL_CLIENT_ID, currency: 'EUR', vault: false, intent: 'capture'}}
          onButtonReady={() => setPaypalSDKReady(true)}
        />
{
paypalSDKReady ? (
<>
<PayPalButton
          amount={price}
          createOrder={createOrder}
          onApprove={onApprove}
          onError={onError}
          options={{clientId: process.env.REACT_APP_PAYPAL_CLIENT_ID, currency: 'EUR', vault: false, intent: 'capture'}}
        />
// Other Paypal buttons
</>
) : null}

The options parameter is required in this case. Maybe on the buttons rendered after the paypalSDKReady is true, the options parameter is not needed, but I have not tested it.

@rautNitesh
Copy link

I too faced similar kind of error and I just tried to add the script tag at highest level of hierarchy i.e. on the page element and voila it worked. Hope all of you guys get your issues solved.

@DevLucem
Copy link

I had the same error, it seems to have been caused by loading the PayPal script more than once. I fixed it by loading the script just once in the head, then loading the two buttons where they should be.

Quick Note: if you want to know the script is not loaded, Check if !window.paypal

@cuongdevjs
Copy link

If you embed code create button, this is my solution

function loadAsync(url: string, callback: () => void) {
  const s = document.createElement('script');
  s.setAttribute('src', url);
  s.onload = callback;
  document.head.insertBefore(s, document.head.firstElementChild);
}

export const loadScriptPaypal = (html: string) => {
  const regex = /<script\b[^>]*>([\s\S]*?)<\/script>/gm;
  const scripts = html.match(regex);
  let pureHtml = html;
  const src = scripts[0].match(/src="(.*?)"/)[1];
  const func = scripts[1].replace(/<\/script>|<script>/g, '');
  loadAsync(src, () => {
    eval(func);
  });
  scripts.forEach(script => {
    pureHtml = pureHtml.replace(script, '');
  });
  return pureHtml;
};

@dorianmariecom
Copy link

dorianmariecom commented Jan 2, 2022

With turbo/turbolinks it was reloading the script so I removed it from the DOM:

    script.onload = () => {
      script.remove()
    }

Full code:

  initialize() {
    if (!window.paypal && !this.loadingValue) {
      this.loadingValue = true
      console.log("LOADING", this.element)
      this.loadScript()
    }
  }

  loadScript() {
    const cspNonce = document.querySelector("meta[name=csp-nonce]")?.content
    const script = document.createElement("script")
    script.setAttribute("src", this.data.get("script-url"))
    script.setAttribute("data-csp-nonce", cspNonce)
    script.onload = () => {
      script.remove()
    }
    document.body.appendChild(script)
  }

(It's in a stimulus controller)

@danwetherald
Copy link

Why not just add a check to see if paypal has already been loaded here and setButtonReady(true)?

https://github.com/Luehang/react-paypal-button-v2/blob/master/src/index.tsx#L234

@wassb92
Copy link

wassb92 commented Aug 19, 2022

I had same issue, i put my "client-id" in <script> (index.html) and in PayPalScriptProvider options, just removed from PayPalScriptProvider and it's work

@HenKun
Copy link

HenKun commented Oct 6, 2022

There is a line in the JS SDK debug script that is the cause of the error and it states:

"Attempted to load sdk version 5.0.335 on page, but window." + namespace + " at version " + (existingNamespace && existingNamespace.version) + ' already loaded.\n\nTo load this sdk alongside the existing version, please specify a different namespace in the script tag, e.g. <script src="https://www.paypal.com/sdk/js?client-id=CLIENT_ID" data-namespace="paypal_sdk"><\/script>, then use the paypal_sdk namespace in place of paypal in your code.

It tests on existing namespace

var existingNamespace = window[namespace];

and destroys the old script if already present.

So it should work, if the new script (which might be necessary due to other options) is loaded in a new namespace.

In our case we needed one script with commit=true and one with commit=false. It is arguable if these options should be specified in the script url at all.

@THEmmanuel
Copy link

I had the same error, it seems to have been caused by loading the PayPal script more than once. I fixed it by loading the script just once in the head, then loading the two buttons where they should be.

bless you. been on this for a few hrs now. turns out i was still loading the initial i used to test in the index.html file while having another one going in a react component.

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

No branches or pull requests