React Router Link reloading page: conflitto con evento esterno

Sep 17 2020

Sto usando il componente Link di React Router per gestire la transizione della pagina nella mia app.

La mia app è inclusa in una pagina web esterna, che ha alcuni script (ovvero Google Tag Manager) che definiscono eventi sui link a livello di documento.

Ciò causa un conflitto con gli eventi di collegamento: la pagina viene sempre ricaricata quando si fa clic su un collegamento.

Ho provato a interrompere la propagazione sul mio componente Link con un callback onClick, senza fortuna: <Link onClick={(e) => { e.stopPropagation(); }} [...]>...</Link>

Vedo che gli eventi di collegamento sono definiti anche a livello di documento , quindi non so come fermare gli eventi esterni.

Qualche indizio su questo?

Grazie !

Risposte

1 MathieuRios Oct 15 2020 at 15:00

Ho affrontato un problema abbastanza simile su una delle mie app e penso che la soluzione sia evitare di usare react-router <Link /> componentse puoi.

Invece di utilizzare il <Link />componente, ho utilizzato l' withRouter()HOC fornito da react-router:

Cosa hai scritto :

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

export default MyComponent;

Può essere sostituito da:

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);

Ciò dovrebbe impedire al collegamento di rilevare altri eventi a livello di documento e ottenere lo stesso risultato del <Link />componente.

2 LionelRowe Sep 21 2020 at 14:42

Aggiornamento: sembra che non ci sia un modo semplice per farlo senza rendere l'intera app all'interno di un file iframe.

Se non ti dispiace farlo, è abbastanza facile (preso da Come impostare il contenuto dell'iframe di un componente di reazione ):

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>
    )
}

Quindi, avvolgi la tua app principale come figlia del Framecomponente. I clic all'interno di una iframebolla non verranno visualizzati nella finestra principale, come descritto in dettaglio in Cattura clic sul div che circonda un iframe .

Ecco una demo di CodeSandbox che mostra questo approccio.


Risposta originale (non funzionante) di seguito

Sul tuo Appo Routercomponente:

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)
})