From c8100591ced9c21ac86560df2f510cb93ce56ca8 Mon Sep 17 00:00:00 2001 From: Varun Date: Sat, 25 Apr 2026 00:01:39 +0530 Subject: [PATCH 1/2] Warn when NaN is passed as an aria-* attribute value aria-* attributes bypass the existing NaN check in ReactDOMUnknownPropertyHook because they return early before that check is reached. When a NaN number reaches the DOM it gets stringified to "NaN", which is invalid for every ARIA attribute type and is almost always a developer mistake (e.g. a missing guard around a computed numeric value). This adds the same NaN check directly inside ReactDOMInvalidARIAHook so that any valid aria-* attribute receiving a NaN value produces a clear warning in development, matching the message already used for non-aria attributes. --- .../src/shared/ReactDOMInvalidARIAHook.js | 15 ++++++++++-- .../__tests__/ReactDOMInvalidARIAHook-test.js | 24 +++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/react-dom-bindings/src/shared/ReactDOMInvalidARIAHook.js b/packages/react-dom-bindings/src/shared/ReactDOMInvalidARIAHook.js index 9cce3b5807aa..e753a17fe819 100644 --- a/packages/react-dom-bindings/src/shared/ReactDOMInvalidARIAHook.js +++ b/packages/react-dom-bindings/src/shared/ReactDOMInvalidARIAHook.js @@ -13,7 +13,7 @@ const warnedProperties = {}; const rARIA = new RegExp('^(aria)-[' + ATTRIBUTE_NAME_CHAR + ']*$'); const rARIACamel = new RegExp('^(aria)[A-Z][' + ATTRIBUTE_NAME_CHAR + ']*$'); -function validateProperty(tagName, name) { +function validateProperty(tagName, name, value) { if (__DEV__) { if (hasOwnProperty.call(warnedProperties, name) && warnedProperties[name]) { return true; @@ -69,6 +69,17 @@ function validateProperty(tagName, name) { warnedProperties[name] = true; return true; } + // NaN is never a valid aria attribute value. The DOM will stringify it + // to "NaN" which violates the ARIA spec for every attribute type. + if (typeof value === 'number' && isNaN(value)) { + console.error( + 'Received NaN for the `%s` attribute. If this is expected, cast ' + + 'the value to a string.', + name, + ); + warnedProperties[name] = true; + return true; + } } } @@ -80,7 +91,7 @@ export function validateProperties(type, props) { const invalidProps = []; for (const key in props) { - const isValid = validateProperty(type, key); + const isValid = validateProperty(type, key, props[key]); if (!isValid) { invalidProps.push(key); } diff --git a/packages/react-dom/src/__tests__/ReactDOMInvalidARIAHook-test.js b/packages/react-dom/src/__tests__/ReactDOMInvalidARIAHook-test.js index a5fd14e95c96..43531452dfd2 100644 --- a/packages/react-dom/src/__tests__/ReactDOMInvalidARIAHook-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMInvalidARIAHook-test.js @@ -106,5 +106,29 @@ describe('ReactDOMInvalidARIAHook', () => { ' in div (at **)', ]); }); + + it('should warn when a valid aria-* attribute receives a NaN value', async () => { + await mountComponent({'aria-valuenow': NaN}); + assertConsoleErrorDev([ + 'Received NaN for the `aria-valuenow` attribute. If this is expected, cast ' + + 'the value to a string.\n' + + ' in div (at **)', + ]); + }); + + it('should warn when a string-type aria-* attribute receives a NaN value', async () => { + await mountComponent({'aria-label': NaN}); + assertConsoleErrorDev([ + 'Received NaN for the `aria-label` attribute. If this is expected, cast ' + + 'the value to a string.\n' + + ' in div (at **)', + ]); + }); + + it('should not warn for valid numeric values in aria-* attributes', async () => { + await mountComponent({'aria-valuenow': 42}); + await mountComponent({'aria-level': 3}); + await mountComponent({'aria-colcount': -1}); + }); }); }); From ecd08ac709a78e2a37e40fcff4acb7b0cf9d76f9 Mon Sep 17 00:00:00 2001 From: Varun Date: Sun, 26 Apr 2026 19:13:08 +0530 Subject: [PATCH 2/2] Also warn for Infinity/-Infinity in aria-* attribute values Extends the non-finite check from isNaN to !isFinite so that Infinity and -Infinity are caught alongside NaN. All three stringify to invalid DOM values ("NaN", "Infinity", "-Infinity") that violate the ARIA spec. The error message now prints the actual received value so the developer knows exactly what came through. --- .../src/shared/ReactDOMInvalidARIAHook.js | 10 +++++---- .../__tests__/ReactDOMInvalidARIAHook-test.js | 22 +++++++++++++++++-- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/packages/react-dom-bindings/src/shared/ReactDOMInvalidARIAHook.js b/packages/react-dom-bindings/src/shared/ReactDOMInvalidARIAHook.js index e753a17fe819..139bfd7dea59 100644 --- a/packages/react-dom-bindings/src/shared/ReactDOMInvalidARIAHook.js +++ b/packages/react-dom-bindings/src/shared/ReactDOMInvalidARIAHook.js @@ -69,12 +69,14 @@ function validateProperty(tagName, name, value) { warnedProperties[name] = true; return true; } - // NaN is never a valid aria attribute value. The DOM will stringify it - // to "NaN" which violates the ARIA spec for every attribute type. - if (typeof value === 'number' && isNaN(value)) { + // NaN and Infinity are never valid aria attribute values. The DOM will + // stringify them (e.g. "NaN", "Infinity") which violates the ARIA spec + // for every attribute type. + if (typeof value === 'number' && !isFinite(value)) { console.error( - 'Received NaN for the `%s` attribute. If this is expected, cast ' + + 'Received `%s` for the `%s` attribute. If this is expected, cast ' + 'the value to a string.', + value, name, ); warnedProperties[name] = true; diff --git a/packages/react-dom/src/__tests__/ReactDOMInvalidARIAHook-test.js b/packages/react-dom/src/__tests__/ReactDOMInvalidARIAHook-test.js index 43531452dfd2..538a98c59dea 100644 --- a/packages/react-dom/src/__tests__/ReactDOMInvalidARIAHook-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMInvalidARIAHook-test.js @@ -110,7 +110,7 @@ describe('ReactDOMInvalidARIAHook', () => { it('should warn when a valid aria-* attribute receives a NaN value', async () => { await mountComponent({'aria-valuenow': NaN}); assertConsoleErrorDev([ - 'Received NaN for the `aria-valuenow` attribute. If this is expected, cast ' + + 'Received `NaN` for the `aria-valuenow` attribute. If this is expected, cast ' + 'the value to a string.\n' + ' in div (at **)', ]); @@ -119,7 +119,25 @@ describe('ReactDOMInvalidARIAHook', () => { it('should warn when a string-type aria-* attribute receives a NaN value', async () => { await mountComponent({'aria-label': NaN}); assertConsoleErrorDev([ - 'Received NaN for the `aria-label` attribute. If this is expected, cast ' + + 'Received `NaN` for the `aria-label` attribute. If this is expected, cast ' + + 'the value to a string.\n' + + ' in div (at **)', + ]); + }); + + it('should warn when a valid aria-* attribute receives an Infinity value', async () => { + await mountComponent({'aria-valuenow': Infinity}); + assertConsoleErrorDev([ + 'Received `Infinity` for the `aria-valuenow` attribute. If this is expected, cast ' + + 'the value to a string.\n' + + ' in div (at **)', + ]); + }); + + it('should warn when a valid aria-* attribute receives a -Infinity value', async () => { + await mountComponent({'aria-valuemin': -Infinity}); + assertConsoleErrorDev([ + 'Received `-Infinity` for the `aria-valuemin` attribute. If this is expected, cast ' + 'the value to a string.\n' + ' in div (at **)', ]);