axe-core-npm icon indicating copy to clipboard operation
axe-core-npm copied to clipboard

react: add delay to run

Open straker opened this issue 3 years ago • 13 comments

Currently there is no option for delaying the start of axe.run so there are problems when APIs or routes are changed but the DOM hasn't updated yet. Axe runs during an incomplete DOM step and so returns errors that aren't really errors as the DOM is still loading.

See https://github.com/dequelabs/react-axe/issues/74, https://github.com/dequelabs/react-axe/issues/134, https://github.com/dequelabs/react-axe/issues/182, https://github.com/dequelabs/react-axe/issues/183, https://github.com/dequelabs/react-axe/issues/122

straker avatar Sep 28 '20 15:09 straker

thanks for moving the issue over @straker, much appreciated.

reintroducing avatar Sep 28 '20 16:09 reintroducing

Thank you, commenting to follow.

jkliptonia avatar Sep 28 '20 17:09 jkliptonia

Reporting an error thrown when using axe with a timer. Package used: "@axe-core/react": "4.0.0"

export default function useAxe(): void {
  if (!environment.isProduction) {
    // eslint-disable-next-line react-hooks/rules-of-hooks
    useEffect(() => {
      // eslint-disable-next-line @typescript-eslint/no-var-requires, import/no-extraneous-dependencies
      const axe = require("@axe-core/react");
      // axe(React, ReactDOM, 1000) // FIXME https://github.com/dequelabs/react-axe/issues/183
      const timer = setInterval(() => {
        axe(React, ReactDOM);
      }, 1500);

      // clearing interval
      return () => clearInterval(timer);
    });
  }
}

When my top App component reloads during the user sign in process, I get this error

index.js:184 Uncaught (in promise) Axe is already running. Use await axe.run() to wait for the previous run to finish before starting a new run.

I tried to give setInterval an async function, or run an iife in useEffect with an async function, and await axe(), to not avail. Moreover, axe.run is not a function.

AdrienLemaire avatar Oct 09 '20 04:10 AdrienLemaire

Hi 👋

Since my project uses React Router, we can't use axe-core-npm 😞 (because of https://github.com/dequelabs/react-axe/issues/122). But it seems so useful 🤩 Any news about the progress of this fix?

Thank you so much for this tool 🙏

mlegait avatar Feb 20 '21 06:02 mlegait

It's pretty crucial to support (react route) rerendering. Any workarounds?

Also it doesn't seem to pick up any rerenders, reduced use case here: https://codesandbox.io/s/axe-corereact-not-picking-up-on-rerenders-cib66

timbakkum avatar Mar 26 '21 14:03 timbakkum

Hi there 😄

Any news regarding this issue?

Thanks a lot 🙏

mlegait avatar Jun 11 '21 08:06 mlegait

I too am interested in this issue. We were previously using react-axe, which works with react-router, but after upgrading to axe-core-npm, the tool no longer works when switching routes. This is critical, so for now, we will need to use the deprecated package.

michael-lynch avatar Oct 13 '21 14:10 michael-lynch

Hi, great package. But actually with React Router v6, lazy routes and React 18 it's not triggering on page navigation and also not on initial load. For example, it gives an error of no h1 element, but the actual element exists inside a react router page and axe is not recognising it.

hazzo avatar Apr 07 '22 09:04 hazzo

hello guys, I'm waiting for some update of this topic. :package:

devedux avatar Jul 25 '22 20:07 devedux

Just added @axe-core/react and facing the same problems with react-router 6.

Initially it will throw a missing h1 and upon hard refresh in the browser, it seems like it's working normally, but it does not trigger on rerenders, HMR nor on route changes.

We use React 18 and React-Router 6

optiguy avatar Sep 27 '22 11:09 optiguy

@michael-siek what's the status of it? This issue is >2 years old. Any ideas what can be the issue? How we can help you guys?

karpiuMG avatar Dec 20 '22 17:12 karpiuMG

@karpiuMG We're currently investigating getting our package to work with React 18, so we don't have a timeline for when this will get done.

straker avatar Jan 10 '23 16:01 straker

I solved it this way:

import React, { useEffect, useRef, useState } from 'react'
import ReactDOM from 'react-dom'

const runAxe = () => {
    let axeRunning = false

    return () => {
        if (!axeRunning) {
            axeRunning = true

            import('@axe-core/react').then(axe =>
                axe.default(React, ReactDOM, 0).then(() => {
                    axeRunning = false
                })
            )
        }
    }
}

const Axe = () => {
    const [mutationCount, setMutationCount] = useState(0) // State variable to track DOM mutations
    const axeRunner = useRef(runAxe())

    useEffect(() => {
        // Create a MutationObserver and observe the entire document for DOM mutations
        const observer = new MutationObserver(() => {
            setMutationCount(count => count + 1)
        })

        observer.observe(document, { subtree: true, childList: true })

        return () => {
            axeRunner.current = runAxe()
            observer.disconnect()
        }
    }, []) // Empty dependency array to run the effect only once

    useEffect(() => {
        const runAxeFunction = axeRunner.current
        runAxeFunction() // Run axe immediately on page load, route changes, and DOM mutations
    }, [mutationCount])

    return null
}

export default Axe

And import it in the App like this:

{!import.meta.env.PROD && <Axe />}

Stefwint avatar Dec 12 '23 14:12 Stefwint