1

Hi guys would like to know how to fix this issue after updating to React 18 I have a duplicated button due to the useEffect while in StrictMode. Any guide how to unsubscribe to this API call or maybe fix it using a proper cleanup. Thanks in advance.

const paypalbutton = useRef();

useEffect(() => {

    const addPayPalScript = async () => {

        const { data } = await axios('/api/config/paypal');
        script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = `https://www.paypal.com/sdk/js?client-id=${data}`;
        script.async = true;
        script.onload = async () => { setSdkReady(true) };

        paypalbutton.current.appendChild(script)
    };

  // inside render()
        <li ref={paypalbutton} id="paypal-button" className="row center">
     
          </li>
noOne
  • 11
  • 2

2 Answers2

0

React strict mode deliberately runs the useEffect callback twice to catch misuse of useEffect, which is what's happening here.

You'll need to do two things:

  1. Make your asynchronous side effect cancellable; and
  2. Return a cleanup function that will
    • cancel the side-effect if it's in progress; or
    • undo the side effect if it completed.
useEffect(() => {
    const controller = new AbortController();
    let script

    const addPayPalScript = async () => {
        const { data } = await axios.get(
            '/api/config/paypal',
            { signal: controller.signal }
        )

        script = document.createElement('script')
        script.type = 'text/javascript'
        script.src = `https://www.paypal.com/sdk/js?client-id=${data}`
        script.async = true
        script.onload = () => {
            setSdkReady(true)
        }

        paypalbutton.current.appendChild(script)
        completed = true
    }

    let completed = false

    addPayPalScript().then(() => {
        completed = true
    })

    return () => {
        controller.abort()

        if (completed) {
            paypalbutton.current.removeChild(script)
        }
    }
}, [/* not sure what goes here... */])
Steve
  • 8,066
  • 11
  • 70
  • 112
  • Hi Steve thanks a lot for your reply.I have tried implementing the changes but unfortunately the paypal button flashes then disappears. I have checked the console and some error occured like findDOMNode is deprecated along with some error about some VM elements getting destroyed. After this I have tried setting the local variables like the completed variable inside the useEffect to be useRef variables to persist the value of the initial render but still having some issues this time an Uncaught TypeError: Cannot read properties of undefined (reading 'removeChild'). I'll try more &update you. – noOne Jul 09 '22 at 23:32
  • Hi Steve with exact implementation as you have shared I am getting ff. errors 1.) react-dom.development.js:86 Warning: findDOMNode is deprecated in StrictMode. findDOMNode was passed an instance of e which is inside StrictMode. Instead, add a ref directly to the element you want to reference. Learn more about using refs safely here: https://reactjs.org/link/strict-mode-find-node at div at e 2.) OrderDetails.js:66 Uncaught (in promise) Cancel {message: 'canceled'} addPayPalScript 3. VM19 js:2 unhandled_error {err: 'Error: zoid destroyed all components 4. VM19Uncaught Error: zoid destroyed all – noOne Jul 09 '22 at 23:41
  • 1
    I will try also implementing another way of implementing the buttons using these. Hopefully it works. import { PayPalScriptProvider, PayPalButtons, usePayPalScriptReducer } from "@paypal/react-paypal-js"; – noOne Jul 09 '22 at 23:44
  • Hi Steve finally got the code working. I was actually referencing the wrong parent element containing the inserted buttons. Anyway Thanks a lot for your help! – noOne Jul 11 '22 at 03:20
  • Hi Steve during some fiddling with different solutions I was able to show the buttons once in the initial render of the page where the buttons are loaded but then if I unmount that page/screen go to another screen/page then come back to it I would actually see two buttons again. The code may actually be working but the problem could be that the initial subscription is lurking somewhere even when the unmounting happened and is not actually removed. Not really sure how to clean this up properly in the return of useEffect. – noOne Jul 16 '22 at 11:25
0

I fixed it by downgrading to React 17.02

npm uninstall react react-dom

npm install react@17.0.2 react-dom@17.0.2