2

I am running into an issue trying to integrate a third party product tour (Intercom) with a react application. There is no way to programmatically end a tour that I have found.

Basically, I need a prop that can change inside the react app whenever a certain non-react DOM element exists or not. I need to be able to tell in a hook or in componentDidUpdate whether or not a certain non-React element exists in the DOM.

I am not sure what to do because obviously when this tour opens and closes there is no change to state or props as far as react is concerned.

Is there a way I can wrap a component with the result of something like document.getElementById("Id-of-the-product-tour-overlay") as a prop? Is there a way I can watch for it with a hook?

Ideally something like

componentDidUpdate(){
   if(elementExists){
      //Do stuff that needs to happen while tour is on
   }
   if(!elementExists){
       //do app stuff to end the tour
   }
}

//OR

useEffect(()=>{
   //do stuff conditional on element's existence
},[elementExists])

clayton groth
  • 199
  • 2
  • 16

2 Answers2

2

The easy way of doing so is to prepare a funcion that receives an HTML element and returns a function that receives a callback as an argument (function that returns other function - currying for purity). The result of the returned function is a new MutationObserver with the callback set.

const observeTarget = target => callback => {
  const mutationObserver = new MutationObserver(callback);
  mutationObserver.observe(target, { childList: true });
}

In non-react file you can feed this function with an HTML element that is a container of 3rd party element which you want to investigate.

Then export the function and you can use it in a react component.

export const observeProductTourOverlay = observeTarget(containerOfProductTourOverlay);

Then in a React component, you can use useEffect hook and use the function

const checkIFMyCompExists = () => !!document.querySelector("#my-component");

export const FromOutside = () => {
  const [elementExists, setElementExist] = useState(checkIFMyCompExists());
  const [indicator, setIndicator] = useState(3);
  useEffect(() => {
    observeProductTourOverlay((mutationRecord, observer) => {
      const doesExist = checkIFMyCompExists();
      setElementExist(doesExist);
      // this will fire every time something inside container changes
      // (i.e. a child is added or removed)
    });

    // garbage collector should take care of mutationObserver in a way there are no memory leaks, so no need to disconnect it on compoment unmouting.
  }, []);

  useEffect(() => {
    setIndicator(elementExists);
    //do stuff when elementExistance changes
  }, [elementExists]);
  return (
    <div>
      <div>{"my component has been added: " + indicator}</div>
    </div>
  );
};

Find the working demo here: https://codesandbox.io/s/intelligent-morning-v1ndx

grzim
  • 534
  • 3
  • 10
  • Ok. This looks like its responding to the changes in the child list just fine. But it throws this error after a few seconds ` TypeError: Cannot read property 'getElementByID' of undefined` for this line ```const doesExist = !!mutationRecord.target.getElementByID("Id-of-the-product-tour-overlay");``` I am using `document.body` for `observeTarget` parameter as that is the only parent of the tour. – clayton groth May 03 '20 at 22:28
  • Solution works for parents & children that are not `` or direct children of `body`. The third party product tour is a direct child of `document.body`. – clayton groth May 03 '20 at 23:04
  • Also tried `document.body || document.documentElement`, this results in no changes being observed. I get the initial `elementExists: false` but no subsequent changes. – clayton groth May 03 '20 at 23:22
  • try const doesExist = !!document.getElementByID("Id-of-the-product-tour-overlay"); I will implement the entire solution later and will paste here – grzim May 04 '20 at 13:42
0

Could you use a while loop?

useEffect(()=>{
   while (document.getElementById('theTour') !== null) {
     // do stuff
   }
   // do cleanup
})
JoshN
  • 1