Summary
Under React StrictMode (i.e. any development build wrapped in <StrictMode>), the
accessibility tree never renders — document.querySelectorAll('[role="gridcell"]').length
stays 0 for the lifetime of the grid. Screen readers get an unlabelled <canvas>, and
tooling that reads the a11y tree (e2e tests, audits) sees nothing. Production builds without
StrictMode are unaffected.
Cause
useDebouncedMemo in dist/esm/common/utils.js (6.0.3):
export function useDebouncedMemo(factory, deps, time) {
const [state, setState] = React.useState(factory);
const mountedRef = React.useRef(true);
React.useEffect(() => () => {
mountedRef.current = false; // ← only ever set to false
}, []);
const debouncedSetState = React.useRef(debounce(x => {
if (mountedRef.current) { // ← gate
setState(x);
}
}, time));
React.useLayoutEffect(() => {
if (mountedRef.current) { // ← gate
debouncedSetState.current(() => factory());
}
}, deps);
return state;
}
StrictMode mounts, unmounts and remounts each component in development. The cleanup sets
mountedRef.current = false; the remount never sets it back, because the ref is only
initialised on first render. Both guards are then permanently false, so no dependency change
ever produces a new value and the memo keeps whatever factory() returned at first render.
The only consumer is accessibilityTree in internal/data-grid/data-grid.js, whose factory
begins:
if (width < 50 || experimental?.disableAccessibilityTree === true) return null;
At first render the grid is unmeasured (width === 0), so that first value is null — and it
is never replaced.
Evidence
Instrumented 6.0.3 with React 19.2 in Chromium, logging inside the memo and inside
onVisibleRegionChangedImpl:
| condition |
values seen by the memo |
[role=gridcell] |
| StrictMode on (as shipped) |
width: 0, accessibilityHeight: 1 on every run |
0 |
StrictMode on, DataEditor given numeric width/height |
still width: 0 |
0 |
StrictMode on, onVisibleRegionChanged logged |
clientWidth: 1078, clientHeight: 395 — the measurement does arrive and setClientSize runs |
0 |
| StrictMode removed, nothing else changed |
width: 1022 → 842, accessibilityHeight: 12 |
36 |
The third row is the decisive one: width genuinely goes 0 → 1078 via clientSize, and the
memo still never re-runs, even though width is the first entry in its dependency array.
Reproduction
Render any <DataEditor> inside <StrictMode> and, once cells are painted, evaluate
document.querySelectorAll('[role="gridcell"]').length → 0. Remove <StrictMode> and the
same page returns one node per visible cell, with the expected accessible text.
Suggested fix
Re-arm the ref on mount:
React.useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;
};
}, []);
Alternatively drop the mountedRef guard entirely — since React 18, calling a state setter
after unmount is a no-op rather than a warning, so the guard no longer earns its keep.
Happy to open a PR if that would help. We are carrying the four-line change as a local patch
in the meantime.
Summary
Under React StrictMode (i.e. any development build wrapped in
<StrictMode>), theaccessibility tree never renders —
document.querySelectorAll('[role="gridcell"]').lengthstays
0for the lifetime of the grid. Screen readers get an unlabelled<canvas>, andtooling that reads the a11y tree (e2e tests, audits) sees nothing. Production builds without
StrictMode are unaffected.
Cause
useDebouncedMemoindist/esm/common/utils.js(6.0.3):StrictMode mounts, unmounts and remounts each component in development. The cleanup sets
mountedRef.current = false; the remount never sets it back, because the ref is onlyinitialised on first render. Both guards are then permanently false, so no dependency change
ever produces a new value and the memo keeps whatever
factory()returned at first render.The only consumer is
accessibilityTreeininternal/data-grid/data-grid.js, whose factorybegins:
At first render the grid is unmeasured (
width === 0), so that first value isnull— and itis never replaced.
Evidence
Instrumented 6.0.3 with React 19.2 in Chromium, logging inside the memo and inside
onVisibleRegionChangedImpl:[role=gridcell]width: 0, accessibilityHeight: 1on every runDataEditorgiven numericwidth/heightwidth: 0onVisibleRegionChangedloggedclientWidth: 1078, clientHeight: 395— the measurement does arrive andsetClientSizerunswidth: 1022 → 842, accessibilityHeight: 12The third row is the decisive one:
widthgenuinely goes 0 → 1078 viaclientSize, and thememo still never re-runs, even though
widthis the first entry in its dependency array.Reproduction
Render any
<DataEditor>inside<StrictMode>and, once cells are painted, evaluatedocument.querySelectorAll('[role="gridcell"]').length→0. Remove<StrictMode>and thesame page returns one node per visible cell, with the expected accessible text.
Suggested fix
Re-arm the ref on mount:
Alternatively drop the
mountedRefguard entirely — since React 18, calling a state setterafter unmount is a no-op rather than a warning, so the guard no longer earns its keep.
Happy to open a PR if that would help. We are carrying the four-line change as a local patch
in the meantime.