1

I know solutions like redux thunk exist when you want to dispatch action asynchronously. However, lately I had following situation:

import {store} from "./store";

const initialState = {
  todos: []
}
​
function todoApp(state = initialState, action) {    
​  
  if(action.type == "ACTION_A"){
    // 1. do smth with state
    // 2. do smth with state, and then... schedule a dispatch say using setTimeout:
    setTimeout(()=>store.dispatch({type:"ACTION_B", payload:1}), 2000);  
    return state;
  }// check other actions e.g. ACTION_B etc.

  return state;     
}

You can see ACTION_B isn't an action I would like to dispatch from somewhere else say as an async action (so that I could use redux thunk say), rather it is part of the logic in ACTION_A.

My question is: how are such situations handled in redux?

PS. This answer, says it is fine to schedule a dispatch in reducer (my situation above) and even gives some solution using middleware. However, I followed that solution to a blog post (see comments on that answer) and saw comments by Mark Erikson (maintainer of Redux) on blog, that that is still not the right way to do it. He seems to suggest redux-loop for such situation.

My question is what are the right ways to handle such situations in redux? Are there other solutions also apart from redux-loop?

Or can we still solve this situation using redux thunk?

Giorgi Moniava
  • 27,046
  • 9
  • 53
  • 90

1 Answers1

2

This is a good case for a thunk:

const actionA = () = ({ dispatch, getState }) => {
   dispatch(actionA1) // dispatch another action that will change the state

   setTimeout(()=> {
       const { data } = getState();
       dispatch({type:"ACTION_B", payload: data });
   }, 2000);
}

or a custom middleware, that will schedule the timeout, but will allow actionA to continue to the reducer, and change the state (this will happen before the timeout, because it's synchronous):

const middleware = ({ dispatch, getState }) = next => action => {
   if(action.type == "ACTION_A"){ //
       setTimeout(()=> {
           const { data } = getState();
           dispatch({type:"ACTION_B", payload: data });
       }, 2000);
   }

   next(action);
}

In general reducers should be pure functions, ie no side effects like scheduling or dispatching actions. If an action needs to do something other than changing the state, it should use a middleware (thunk for example) to do so.

Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • you can see in my example I used `payload:1`- say that value 1, is something I computed from steps 1 and 2 (see comments) in reducer. What would you do in such case? I think your first suggestion wouldn't apply so easily anymore? – Giorgi Moniava Jul 28 '18 at 22:09
  • It actually will. The state will be updated before the setTimeout, because standard actions are synchronous. – Ori Drori Jul 28 '18 at 22:10
  • Anyway, I would prefer the custom middleware in this case. This is a side effect of actionA, and the middleware reflect this - trigger setTimeout, and continue. – Ori Drori Jul 28 '18 at 22:11
  • So I would have to call getState in actionA in order to read value 1? Or even imagine I should do setTimeout only if some condition is satisfied in state. (PS. Not familiar with middlewares yet :() – Giorgi Moniava Jul 28 '18 at 22:13
  • You can use `getState()` inside the timeout's callback in both solutions. I'll update the answer. – Ori Drori Jul 28 '18 at 22:13
  • The updated state (after actionA) will only be available in the timeout's callback. You can do whatever you like with the state, not dispatching if stateX, for example. – Ori Drori Jul 28 '18 at 22:17
  • Don't remember exactly but my situation was like in ACTION_A if user didn't match two cards, I had to close those two cards after 2 seconds. In that case I suppose I will have to set flag MISMATCH in state using actionA1 and if that's true close cards in setTimeout, is that something you suggest? – Giorgi Moniava Jul 28 '18 at 22:19
  • You can do something like that. Another option is that the reducers would react to actionA, and change whatever needs to changed without you dispatching another action. Why do you need action B at all? – Ori Drori Jul 28 '18 at 22:23
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/176952/discussion-between-giorgi-moniava-and-ori-drori). – Giorgi Moniava Jul 28 '18 at 22:24