@samitouri / QOS-React-2 / commits / c506ded3b2

Don't discard render phase state updates with the eager reducer optimization (#14852)

* Add test cases for setState(fn) + render phase updates * Update eager state and reducer for render phase updates * Fix a newly firing warning

Dan Abramov committed Feb 19, 2019 at 18:40 UTC c506ded3b275b410517c06e0f55db8f0f70288b5
3 files changed +76 -2
packages/react-reconciler/src/ReactFiberHooks.js
+3 -1
@@ -607,7 +607,6 @@ function updateReducer<S, I, A>(
607 }
608
609 hook.memoizedState = newState;
610 -
610 // Don't persist the state accumlated from the render phase updates to
611 // the base state unless the queue is empty.
612 // TODO: Not sure if this is the desired semantics, but it's what we
@@ -616,6 +615,9 @@ function updateReducer<S, I, A>(
615 hook.baseState = newState;
616 }
617
618 + queue.eagerReducer = reducer;
619 + queue.eagerState = newState;
620 +
621 return [newState, dispatch];
622 }
623 }
packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+70
@@ -669,6 +669,76 @@ describe('ReactHooks', () => {
669 }).toThrow('is not a function');
670 });
671
672 + it('does not forget render phase useState updates inside an effect', () => {
673 + const {useState, useEffect} = React;
674 +
675 + function Counter() {
676 + const [counter, setCounter] = useState(0);
677 + if (counter === 0) {
678 + setCounter(x => x + 1);
679 + setCounter(x => x + 1);
680 + }
681 + useEffect(() => {
682 + setCounter(x => x + 1);
683 + setCounter(x => x + 1);
684 + }, []);
685 + return counter;
686 + }
687 +
688 + const root = ReactTestRenderer.create(null);
689 + ReactTestRenderer.act(() => {
690 + root.update(<Counter />);
691 + });
692 + expect(root).toMatchRenderedOutput('4');
693 + });
694 +
695 + it('does not forget render phase useReducer updates inside an effect with hoisted reducer', () => {
696 + const {useReducer, useEffect} = React;
697 +
698 + const reducer = x => x + 1;
699 + function Counter() {
700 + const [counter, increment] = useReducer(reducer, 0);
701 + if (counter === 0) {
702 + increment();
703 + increment();
704 + }
705 + useEffect(() => {
706 + increment();
707 + increment();
708 + }, []);
709 + return counter;
710 + }
711 +
712 + const root = ReactTestRenderer.create(null);
713 + ReactTestRenderer.act(() => {
714 + root.update(<Counter />);
715 + });
716 + expect(root).toMatchRenderedOutput('4');
717 + });
718 +
719 + it('does not forget render phase useReducer updates inside an effect with inline reducer', () => {
720 + const {useReducer, useEffect} = React;
721 +
722 + function Counter() {
723 + const [counter, increment] = useReducer(x => x + 1, 0);
724 + if (counter === 0) {
725 + increment();
726 + increment();
727 + }
728 + useEffect(() => {
729 + increment();
730 + increment();
731 + }, []);
732 + return counter;
733 + }
734 +
735 + const root = ReactTestRenderer.create(null);
736 + ReactTestRenderer.act(() => {
737 + root.update(<Counter />);
738 + });
739 + expect(root).toMatchRenderedOutput('4');
740 + });
741 +
742 it('warns for bad useImperativeHandle first arg', () => {
743 const {useImperativeHandle} = React;
744 function App() {
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js
+3 -1
@@ -454,7 +454,9 @@ describe('ReactHooksWithNoopRenderer', () => {
454
455 // Test that it works on update, too. This time the log is a bit different
456 // because we started with reducerB instead of reducerA.
457 - counter.current.dispatch('reset');
457 + ReactNoop.act(() => {
458 + counter.current.dispatch('reset');
459 + });
460 ReactNoop.render(<Counter ref={counter} />);
461 expect(ReactNoop.flush()).toEqual([
462 'Render: 0',