5

I'm using React Router's Link component to handle page transition in my app.

My app is included in an external web page, which has some scripts (namely Google Tag Manager) that define events on links on the document level.

This causes a conflict with Link events : page is always reloading when clicking on a link.

I tried to stop propagation on my Link component with an onClick callback, with no luck : <Link onClick={(e) => { e.stopPropagation(); }} [...]>...</Link>

I see that Link events are also defined on the document level, so I don't know how to stop the external events.

Any clue on this one ?

Thank you !

2 Answers2

2

Update: It seems that there's no simple way to do this without rendering the whole app inside an iframe.

If you don't mind doing that though, it's easy enough (taken from How to set iframe content of a react component):

const Frame = ({ children }) => {
    const [ref, setRef] = useState(null)
    const mountNode = ref?.contentWindow.document.body

    return (
        <iframe title="iframe" ref={setRef}>
            {mountNode && ReactDOM.createPortal(children, mountNode)}
        </iframe>
    )
}

Then, wrap your main app as a child of the Frame component. Clicks inside an iframe will not bubble up to the parent window, as detailed in Capture click on div surrounding an iframe.

Here's a CodeSandbox demo showing this approach.


Original (non-functioning) answer below

On your App or Router component:

const blockClicks = useCallback((e) => {
    const { target } = e
    const { nodeName, href } = target
    if (
        target.closest('#root-id') &&
        nodeName === 'A' &&
        href &&
        (/^https?:\/\//.test(!href) ||
        href.startsWith(window.location.origin)
    )) {
        // amend logic specific to your use case
        // - we want to avoid blocking irrelevant clicks
        e.stopPropagation()
    }
})

useEffect(() => {
    document.addEventListener('click', blockClicks, true)

    return () =>
        document.removeEventListener('click', blockClicks, true)
})
Lionel Rowe
  • 5,164
  • 1
  • 14
  • 27
  • 1
    Thanks Lionel for your answer. React router defines events at the document level. So if I stop propagation for my click, it won't be intercepted by my router. Does your answer take that into account ? Is there a way to make my router define events on the app root level and not on the document level ? – Henry Boisgibault Sep 21 '20 at 14:53
  • 1
    Apparently not, upon testing it. I'm leaving this here for the time being, will try tweaking the approach and delete later if it's unsalvageable. – Lionel Rowe Sep 21 '20 at 15:02
  • 1
    Will give you some feedback. – Henry Boisgibault Sep 22 '20 at 08:03
  • 1
    This did not work. As I explained, it seems that react router events and GTM events are at the same level (document). So stopping propagation has no effect. Is there a way to define router events at the app root level and not at the document level ? – Henry Boisgibault Sep 22 '20 at 12:36
  • 1
    Doesn't seem to be. I assume it's controlled by ReactDOM, not React Router, but I can't find anywhere it's documented. – Lionel Rowe Sep 22 '20 at 12:38
  • @jreid Added a working answer that uses `iframe`s. You may not want to render the whole app inside an iframe, but as of now it's the only thing I've found that worked. – Lionel Rowe Sep 29 '20 at 14:46
1

I faced quite the similar issue on one of my apps and I think that the solution is to avoid using the react-router <Link /> component if you can.

Instead of using the <Link /> component, I used the withRouter() HOC provided by react-router :

What you wrote :

Class MyComponent extends React.Component{
  <Link onClick={(e) => { e.stopPropagation(); }} [...]>
    ...
  </Link>
}

export default MyComponent;

Can be replaced by :

Class MyComponent extends React.Component{

  navigate = (event, location) => {
    event.preventDefault();
    this.props.history.push(location);
  }

  <a onClick={(e, href) => { this.navigate(e, href) }} [...]>
    ...
  </a>
}

export default withRouter(MyComponent);

This should prevent your link to catch others event at the document level and achieve the same result as <Link /> component.

Mathieu Rios
  • 350
  • 1
  • 17