From 88decefe65dfba77ab5e3e87470cd4fb19ddcff8 Mon Sep 17 00:00:00 2001 From: james_robinson Date: Thu, 30 Mar 2023 10:01:01 -0700 Subject: [PATCH 1/2] Handle NaN comparison in useEffect dependency array --- hooks/src/index.js | 12 +++++++++++- hooks/test/browser/useEffect.test.js | 22 ++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/hooks/src/index.js b/hooks/src/index.js index 56f1aa11d3..f532cb7a63 100644 --- a/hooks/src/index.js +++ b/hooks/src/index.js @@ -493,6 +493,16 @@ function invokeEffect(hook) { currentComponent = comp; } +/** + * Check if two values are the same value + * @param {*} x + * @param {*} y + * @returns {boolean} + */ +function is(x, y) { + return (x === y && (x !== 0 || 1 / x === 1 / y)) || (x !== x && y !== y); +} + /** * @param {any[]} oldArgs * @param {any[]} newArgs @@ -501,7 +511,7 @@ function argsChanged(oldArgs, newArgs) { return ( !oldArgs || oldArgs.length !== newArgs.length || - newArgs.some((arg, index) => arg !== oldArgs[index]) + newArgs.some((arg, index) => !is(arg, oldArgs[index])) ); } diff --git a/hooks/test/browser/useEffect.test.js b/hooks/test/browser/useEffect.test.js index 2075e5f2a1..073f0ccb4f 100644 --- a/hooks/test/browser/useEffect.test.js +++ b/hooks/test/browser/useEffect.test.js @@ -591,4 +591,26 @@ describe('useEffect', () => { expect(calls.length).to.equal(1); expect(calls).to.deep.equal(['doing effecthi']); }); + + it('should not rerun when receiving NaN on subsequent renders', () => { + const calls = []; + const Component = ({ value }) => { + const [count, setCount] = useState(0); + useEffect(() => { + calls.push('doing effect' + count); + setCount(count + 1); + return () => { + calls.push('cleaning up' + count); + }; + }, [value]); + return

{count}

; + }; + const App = () => ; + + act(() => { + render(, scratch); + }); + expect(calls.length).to.equal(1); + expect(calls).to.deep.equal(['doing effect0']); + }); }); From 7f124d140fe37c89c99a55517a36bcd36a668bee Mon Sep 17 00:00:00 2001 From: james_robinson Date: Thu, 30 Mar 2023 10:05:35 -0700 Subject: [PATCH 2/2] Handle NaN comparison in component state --- hooks/src/index.js | 2 +- hooks/test/browser/useState.test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/hooks/src/index.js b/hooks/src/index.js index f532cb7a63..3e81a24f11 100644 --- a/hooks/src/index.js +++ b/hooks/src/index.js @@ -236,7 +236,7 @@ export function useReducer(reducer, initialState, init) { const currentValue = hookItem._value[0]; hookItem._value = hookItem._nextValue; hookItem._nextValue = undefined; - if (currentValue !== hookItem._value[0]) shouldUpdate = true; + if (!is(currentValue, hookItem._value[0])) shouldUpdate = true; } }); diff --git a/hooks/test/browser/useState.test.js b/hooks/test/browser/useState.test.js index 58b152afc8..bcd30840a1 100644 --- a/hooks/test/browser/useState.test.js +++ b/hooks/test/browser/useState.test.js @@ -371,4 +371,30 @@ describe('useState', () => { rerender(); expect(scratch.innerHTML).to.equal('

hello world!!!

'); }); + + it('should limit rerenders when setting state to NaN', () => { + const calls = []; + const App = ({ i }) => { + calls.push('rendering' + i); + const [greeting, setGreeting] = useState(0); + + if (i === 2) { + setGreeting(NaN); + } + + return

{greeting}

; + }; + + act(() => { + render(, scratch); + }); + expect(calls.length).to.equal(1); + expect(calls).to.deep.equal(['rendering1']); + + act(() => { + render(, scratch); + }); + expect(calls.length).to.equal(26); + expect(calls.slice(1).every(c => c === 'rendering2')).to.equal(true); + }); });