From be10b645163d4ffcba0fe472e5acecee98dbeb2e Mon Sep 17 00:00:00 2001 From: Luka Hartwig Date: Thu, 27 Mar 2025 19:52:28 -0700 Subject: [PATCH] Skip setting innerHTML when html string has not changed --- .../src/client/ReactDOMComponent.js | 5 ++- .../src/__tests__/ReactDOMComponent-test.js | 36 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponent.js b/packages/react-dom-bindings/src/client/ReactDOMComponent.js index 48b4d9472fb5d..215b164eedaa7 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMComponent.js +++ b/packages/react-dom-bindings/src/client/ReactDOMComponent.js @@ -565,7 +565,10 @@ function setProp( ); } const nextHtml: any = value.__html; - if (nextHtml != null) { + if ( + nextHtml != null && + (prevValue == null || prevValue.__html !== nextHtml) + ) { if (props.children != null) { throw new Error( 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.', diff --git a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js index 0f0986dde8e38..1297d72274080 100644 --- a/packages/react-dom/src/__tests__/ReactDOMComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMComponent-test.js @@ -1283,6 +1283,42 @@ describe('ReactDOMComponent', () => { expect(container.textContent).toEqual('bonjour'); }); + it('should set innerHTML when html changed', async () => { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(
); + }); + const node = container.firstChild; + const spyOnSetInnerHtml = jest.spyOn(node, 'innerHTML', 'set'); + await act(() => { + root.render(
); + }); + expect(spyOnSetInnerHtml).toHaveBeenCalledTimes(1); + await act(() => { + root.render(
); + }); + expect(spyOnSetInnerHtml).toHaveBeenCalledTimes(2); + }); + + it('should skip setting innerHTML when html did not change', async () => { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(
); + }); + const node = container.firstChild; + const spyOnSetInnerHTML = jest.spyOn(node, 'innerHTML', 'set'); + await act(() => { + root.render(
); + }); + expect(spyOnSetInnerHTML).toHaveBeenCalledTimes(1); + await act(() => { + root.render(
); + }); + expect(spyOnSetInnerHTML).toHaveBeenCalledTimes(1); + }); + it('should not incur unnecessary DOM mutations for attributes', async () => { const container = document.createElement('div'); const root = ReactDOMClient.createRoot(container);