dequelabs / axe-core-npm

Mozilla Public License 2.0
592 stars 67 forks source link

react: add delay to run #92

Open straker opened 3 years ago

straker commented 3 years ago

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

reintroducing commented 3 years ago

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

jkliptonia commented 3 years ago

Thank you, commenting to follow.

AdrienLemaire commented 3 years ago

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.

mlegait commented 3 years ago

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 🙏

timbakkum commented 3 years ago

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

mlegait commented 3 years ago

Hi there 😄

Any news regarding this issue?

Thanks a lot 🙏

michael-lynch commented 2 years ago

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.

hazzo commented 2 years ago

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.

devedux commented 2 years ago

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

optiguy commented 1 year ago

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

karpiuMG commented 1 year ago

@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?

straker commented 1 year ago

@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.

Stefwint commented 8 months ago

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