Change:
Do you want to request a feature or report a bug? Report a bug What is the current behavior? There are cases where actions dispatched by 's dispatch function are reduced twice (in concurrent mode). As far as I can tell from my first quick read of the code of react-dom (so I might be totally wrong!), this happens, when a higher priority action is to a queue that already contains lower priority actions. On the first run, that is caused by for example an onClick handler (higher priority), the dispatched action is appended to the queue. On processing the queue the lower priority actions (for example from setInterval events) are ignored and the "onClick action" is reduced, but stays on the end of the queue, so that on the next time the queue is processed, that action gets reduced a second time. See a testcase at: Pressing the button increments the button (most of the time not always, depending if 'TICK' actions are in the queue) by two. What is the expected behavior? I'd expect a dispatched action to be only reduced once. Which versions of React, and which browser / OS are affected by this issue? Did this work in previous versions of React? Tested with React 16.7.0-alpha.2 on Chrome (70.0.3538.110) and Safari (12.0.1) both on macOs
This does look like a bug. Want to dig into it?
I'd love to, but I am not sure I understand the implications on messing with the action queue, there are quit a lot of special cases and as I wrote that was my first read of any react implementation code :) Also it might take me a week or so, as I am pretty busy at the moment.
Actually I was wrong, the higher priority action should be reduced twice, the error is in where the memoizedState (action applied) overwrites the baseState (which should stay the last state before updates were skipped). I have a test that reproduced my bug and is fixed by correcting (while the other tests are still passing). I'll have that cleaned up tomorrow or Monday, so I'd take this issue.
Nice find :-)

Discussion:
return { memoizedState: hook.memoizedState, <del> baseState: hook.memoizedState, </del> <ins> baseState: hook.baseState, </ins> queue: hook.queue, baseUpdate: hook.baseUpdate,

ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 8')]); }); <ins> // Regression test for https://github.com/facebook/react/issues/14360 it('handles dispatches with mixed priorities', () => { const INCREMENT = 'INCREMENT'; function reducer(state, action) { return action === INCREMENT ? state + 1 : state; } function Counter(props, ref) { const [count, dispatch] = useReducer(reducer, 0); useImperativeMethods(ref, () => ({dispatch})); return <Text text={'Count: ' + count} />; } Counter = forwardRef(Counter); const counter = React.createRef(null); ReactNoop.render(<Counter ref={counter} />); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); counter.current.dispatch(INCREMENT); counter.current.dispatch(INCREMENT); counter.current.dispatch(INCREMENT); ReactNoop.flushSync(() => { counter.current.dispatch(INCREMENT); }); expect(ReactNoop.getChildren()).toEqual([span('Count: 1')]); ReactNoop.flush(); expect(ReactNoop.getChildren()).toEqual([span('Count: 4')]); }); </ins> }); describe('useEffect', () => {