Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: "Reducers may not dispatch actions" #2753

Closed
cjpete opened this issue Dec 8, 2017 · 16 comments
Closed

Question: "Reducers may not dispatch actions" #2753

cjpete opened this issue Dec 8, 2017 · 16 comments

Comments

@cjpete
Copy link

cjpete commented Dec 8, 2017

I haven't been able to replicate this in an isolated environment, but it's an error which keeps getting picked up from our error reporter.

We have two websockets which asynchronously push data to the client. Upon receiving messages on these websockets, redux store actions are dispatched.

What appears to be happening based on our error stack trace is that mid-way between one state update cycle occurring (perhaps in between reducer function calls) an asynchronous message is being received on one of the sockets, and this somehow is prioritised above the current redux state update cycle - thus when it gets to the end of the cycle it sees that an action has been dispatched in that time, and throws the error.

Stack trace from error capture:
image

As you can see there are two calls to "src/api/asyncListeners -> listener" - these are both reactions to asynchronous events, and are not related / triggered by each other.

Below is an example fiddle which doesn't show the problem. Clearly this code waits for the first state update cycle to run before processing the next action:
https://jsfiddle.net/pojzf7vx/2/

TL;DR;

  • When the "Reducers may not dispatch actions" error is thrown, is this because something is likely to have gone wrong in the redux store? Or is this a best practice warning to prevent, say, infinite loops?

  • Will redux start a secondary state update cycle if it receives an action mid-update?

  • Is there any way to force redux to process full state updates for actions one at a time?

Appreciate any help/advice!

@timdorr
Copy link
Member

timdorr commented Dec 8, 2017

Is one of your reducers asynchronous in some way? If so, that is considered a side effect. You should push any side effects to be handled in your middleware, not your reducer functions. They should be pure (no global variable access, including setTimeout).

@timdorr timdorr closed this as completed Dec 8, 2017
@cjpete
Copy link
Author

cjpete commented Dec 8, 2017

@timdorr the reducers are all pure synchronous functions

@William-Owen
Copy link

@timdorr This question seems to have been closed prematurely.

@timdorr
Copy link
Member

timdorr commented Dec 8, 2017

This is a bug tracker, not a support system. For usage questions, please use Stack Overflow or Reactiflux where there are a lot more people ready to help you out. Thanks!

@vinnymac
Copy link

@cjpete interestingly I just found the same exact error pop up in my sentry logs. I use redux-saga to manage all asynchronous control flows. In my case I too have a websocket that is dispatching an action. Later I came to a similar conclusion that it must somehow be prioritizing the newly dispatched action from the websocket push over the currentReducer.

The reducers are small, straightforward, and include no asynchronous code in them such as setTimeout or requestAnimationFrame. This is an extremely rare occurrence, and even though this code has been running in production for 6 months by a massive number of users, we've only received this error the one time for a single user.

They were using Chrome 63.0.3239 on Windows 8.1. If I figure out a way to reproduce it I will surely post back here. But I thought you'd be happy to know you are not the only one.

@tappleby
Copy link
Contributor

Also started seeing some of these errors show up in sentry (IE11, Windows).

We have a custom promise based request middleware w/ redux-thunk, all reducers are pure functions. I wonder if there is any connection with reduxjs/redux-thunk#122.

@markerikson
Copy link
Contributor

This admittedly seems to be "impossible". I would really appreciate it if someone could manage to come up with a repro of some kind.

@Jiert
Copy link

Jiert commented Feb 21, 2018

I'm also seeing this Reducers may not dispatch actions error pop up in Sentry logs, and I'm also not (yet) able to reproduce it. However, for what it's worth, in my case exactly 100% of these errors are coming from some version of Internet Explorer, mostly IE 11.

@quadsurf
Copy link

quadsurf commented May 3, 2018

it's happening to me too (in react native), and it's crashing the app... and likewise, none of my reducers are dispatching actions, and all are synchronous

"dependencies": {
    "react": "16.2.0",
    "react-native": "0.52",
    "react-redux": "^5.0.6",
    "redux": "^3.7.2",
    "redux-thunk": "^2.2.0"
  }

@bpdarlyn
Copy link

bpdarlyn commented Jul 24, 2018

some news about this ? I have the same problem.

export function fetchDashboard() {
  return (dispatch) => {
    dispatch(isFetching()); // Error
  };
}
"react": "16.2.0",
"react-native": "0.52.0",
"react-redux": "5.0.5",
"redux": "3.7.2",
"redux-thunk": "2.2.0"

For now this is the solution:
reduxjs/redux-thunk#122

@reduxjs reduxjs deleted a comment from benjiwheeler Oct 2, 2018
@relaxomatic
Copy link

relaxomatic commented Jul 4, 2019

I agree this should be impossible. However I managed to recreate this using Chrome 75.0.3770.100. I'm trying to get this to happen again but I haven't managed to get it to happen again. But I would be interested if someone else can get it to happen again. I burrowed the prime number functions from https://www.npmjs.com/package/prime-number

/**
 * Benchmark a primality algorythm
 *
 * @param {Function} isPrime
 *
 * @returns {Function} checkPrimality (from, to)
 */
function primeBenchMark(isPrime) {
  /**
   * Primality check on given range
   *
   * @param {Number} from
   * @param {Number} to
   */
  return function checkPrimality(from, to) {
    console.time('primality benchmark');

    var countPrimes = 0;

    for (var i = from; i <= to; i++) if (isPrime(i)) countPrimes++;

    console.log(`Found ${countPrimes} primes`);

    console.timeEnd('primality benchmark');
  };
}

/**
 * Check if a number is prime
 *
 * @param {Number} n
 *
 * @returns {Boolean} isPrime
 */
function primeNumber(n) {
  if (n === 2) return true; // 2 is a special case
  if (n % 2 === 0) return false;
  for (var i = 3; i <= Math.sqrt(n); i = i + 2) {
    if (!primeNumber(i)) continue; // <-- recursion here
    if (n % i === 0) return false;
  }
  return true;
}

let lock = false;
let count = 0;

/**
 * A function that checks javascript is single threaded, by setting a lock, and computing a prime numbers, given js is
 * single threaded each iteration should finish executing before the next one begins
 *
 * @return {void}
 */
function lockedFunction() {
  count++;
  console.info(`Started ${count}`);

  if (lock) {
    throw new Error(`Not single threaded ${lock} hasn't finished executing before ${count} executed`);
  }

  try {
    lock = count;
    primeBenchMark(primeNumber)(10000, 500000);
  } finally {
    lock = false;
  }

  console.info(`Ended ${count}`);
};


/**
 * Runs a given function many times
 *
 * @param {function} func the function to run
 * @param {number} times to run the calculation
 * @param {number} delay ms delay time between executions
 *
 * @return {void}
 */
function runMany(func, times, delay) {
  let count = 0;

  function runCycle() {
    func();
    if (count < times) {
      count++;
      setTimeout(runCycle, delay);
    }
  };

  runCycle();
};

/**
 * Runs a function that computes prime numbers many times
 *
 * @param {number} times to run the calculation
 * @param {number} delay ms delay time between executions
 *
 * @return {void}
 */
function lockTest(times = 100, delay = 50) {
  runMany(lockedFunction, times, delay);
};

Running lockTest(); a few times in the console I got this to finally happen

Started 325
index.js?m=1562193618:155900 Started 326
index.js?m=1562193618:155900 Uncaught Error: Not single threaded 325 hasn't finished executing before 326 executed
    at lockedFunction (index.js?m=1562193618:155900)
    at runCycle (index.js?m=1562193618:155907)
lockedFunction @ index.js?m=1562193618:155900
runCycle @ index.js?m=1562193618:155907
setTimeout (async)
runCycle @ index.js?m=1562193618:155907
setTimeout (async)
runCycle @ index.js?m=1562193618:155907
setTimeout (async)
runCycle @ index.js?m=1562193618:155907
setTimeout (async)
runCycle @ index.js?m=1562193618:155907
runMany @ index.js?m=1562193618:155907
lockTest @ index.js?m=1562193618:155914
(anonymous) @ VM293424:1
index.js?m=1562193618:155900 Started 327

@relaxomatic
Copy link

relaxomatic commented Jul 4, 2019

But this has been driving me nuts. If the redux store ever get into this state it cannot recover. Contemplating forking off my own version to unset isDispatching when this error occurs, which I don't think we should have to do but it's better then the app breaking because of it.

@markerikson
Copy link
Contributor

To be blunt: if there's an issue here, it's not the fault of Redux. The logic is sound - there's nothing we can do about environments that might be somehow doing something bizarre.

@relaxomatic
Copy link

I know that, firefox is fine as far as I have tested it, and this should be impossible with javascript running as a single thread. But letting people know should they randomly experience this issue, as I can't find much about this issue online. Also confirming that this is not a redux issue, but this issue will break it when it occurs.

@vinnymac
Copy link

vinnymac commented Jul 4, 2019

Yea, I mean this issue has been closed since it was created. I never really expected anything to be done about this. I don’t believe the issue is in the redux source or our source at all.

It is good to collect what information we do have on the subject to see if we could find the true cause, but it is a difficult one.

In the case above, if you are able to reproduce a problem with JS execution order perhaps this discussion should be moved to a ticket here?

Perhaps they will tell us this is a possibility due to some extreme circumstance we haven’t thought up.

@relaxomatic
Copy link

Yeah my search still goes on to isolate the exact cause, it is really driving me nuts. Thinking about this some more, it seems the current execution just completely stops silently, and so it never progresses to the finally statement, if it did it would eventually recover, but it does not.

Thanks for the link, I'll see if anyone has reported this issue, if not I'll continue trying to isolate it to reproducible steps and report it myself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants