0

I have a chat feature in my app and very infrequently the chat box loads twice when reloading the page. Probably something do to with some weird react re-rendering. I would like for something to check if the script has been executed already and if so don't do it again.

Here is my relevant code

 import { useEffect, useState, useRef } from 'react';
 import { useDebouncedCallback } from 'use-debounce';

const debounceLoading = useDebouncedCallback((e) => {
        const firstLoad = useRef(true);
        loadScript();
        setup();
    }, 2000);

    useEffect(() => {
        if (firstLoad.current){
            firstLoad.current = false;
            return;
        }
        debounceLoading();
    }, []);

    useEffect(() => {
        if (open) {
            setWrapperClass('open');
            setButtonIcon('/images/.svg');
        } else {
            setWrapperClass('closed');
            setButtonIcon('/images/.svg');
        }
        }, [open]);
    
        const loadScript = () => {...........}

return (
        <div id="wrapper" className={wrapperClass}>
            <div onClick={toggleChat} className="button">
                <img src={buttonIcon} alt="Chat icon" />
                Chat
            </div>
            <div id="box-container"></div>
        </div>
    );
}

I have looked at Verify External Script Is Loaded and https://medium.com/anna-coding/the-way-to-check-if-its-the-first-time-for-useeffect-function-is-being-run-in-react-hooks-170520554067. I also understand that this will not be a reproducible question and I'm well aware of that but any help would be appreciated.

My current code is giving me errors because the useRef hook can't be called inside a callback. Does anyone know the best way to fix my issue?

Steve
  • 197
  • 7
  • 17
  • You could use `useRef` in the component and pass it to the `useDebouncedCallback`, but as it is already wrapped inside `useEffect` with zero deps, more likely the parent component of this component is causing it to remount – iunfixit Aug 30 '21 at 14:11
  • Hooks are supposed to be on the top level. Your useRef is not. Can you move it to the same level as useEffect? – Sanish Joseph Aug 30 '21 at 14:14
  • You can see in your developer tools profiler, if you record the page load, why a component re-renders. Check how to see the performance of a react app for guidance on how you can see the rendering pattern and how to enable the option what caused the rerender. – Sanish Joseph Aug 30 '21 at 14:16

2 Answers2

0

I do something similar with an external script. I use the global variable that the external script exposes to check if the script is already loaded to prevent loading the library twice. While loading, a loadingRef is set to true until the script has been loaded.

This doesn't work when using this component in multiple places though.

import { useEffect, useState, useRef } from 'react';
import { useDebouncedCallback } from 'use-debounce';

function loadScript() {
  // keeps this outside your render function if it does not use props or state.
}

export const ChatBox = () => {
  const loadingRef = useRef(false);
  
  const debounceLoading = useDebouncedCallback(async (e) => {
    loadingRef.current = true;
    await loadScript();
    loadingRef.current = false;
    setup();
  }, 2000);

  useEffect(() => {
    if (!loadingRef.current && !window.chatScript) {
      debounceLoading();
    }
  }, []);
};

Ps. the useDebouncedCallback is probably redundant since the useEffect is only called once.

Chris
  • 6,331
  • 1
  • 21
  • 25
0

If the script should load and never load again under any circumstance, and you don't have a cleanup function to remove it, you could use window on its load call

const loadScript = () => {
  if (!window.isScriptLoaded) {
      // ...
      window.isScriptLoaded= true;
  }
}

If the script has a cleanup function, then you can call it on the return function of useEffect

useEffect(() => {
        loadScript();
        return () => { removeMyScript() }
  }, []);

Ref also works, but if your script is very little related to the component, you will have to wrap it in a custom hook to make sure it loads for every component, or just check for window as the method above

const useMyScript () => {
   const isLoaded = useRef(false)
   
   useEffect(() => {
      if (!isLoaded.current) {
        loadScript();
        isLoaded.current = true;
      }
   }, [])
}

But again, if the component remounts, it will be loaded again, stick with the window if it is the case, you cannot be 100% sure that your component won't mount again

iunfixit
  • 953
  • 3
  • 15