Search code examples
javascriptreduxlit-element

Redux: Execute reducers asynchronously?


I'm working on a bowling scoring system and I am quite confused as to why the following is not working as expected.

My state could look like this:

players: [
  {
    "id": "f44t0uagdyg",
    "name": "test-player",
    "currentFrame": 1,
    "active": false,
    "rolls": {
      "0": [2,3],
      "1": [1,5],
      "2": [],
      "3": [],
      "4": [],
      "5": [],
      "6": [],
      "7": [],
      "8": [],
      "9": [],
      "10": []
    },
    "totalWins": 0
  },
  {
    "id": "5gbxlu94zpr",
    "name": "test-player-2",
    "currentFrame": 1,
    "active": false,
    "rolls": {
      "1": [1,6],
      "2": [],
      "3": [],
      "4": [],
      "5": [],
      "6": [],
      "7": [],
      "8": [],
      "9": [],
      "10": []
    },
    "totalWins": 0
  }
]

Now, whenever the user inputs a new roll, I want to add it to the store with these reducers (they work as intended): To add the roll to the state I do the following:

    case ADD_ROLL:
      return {
      ...state,
      players: state.players.map((player) => {
        if (player.id === action.id) {
          return {
            ...player,
            rolls: {
              ...player.rolls,
              [action.frame]: [
                ...player.rolls[action.frame],
                action.roll
              ]
            }
          }
        }
        return player;
      })
    };

To advance to the next frame (e.g. after the user input 2 rolls or rolled a strike) I do this:

       case NEXT_FRAME:
          return {
          ...state,
          players: state.players.map((player) => {
            if (player.id === action.id) {
              return {
                ...player,
                currentFrame: player.currentFrame + 1,
              }
            }
            return player;
          })
        }

After the user clicks to confirm his input this advanceGame function is called to asses whether the game should advance to the next frame (if the user rolled twice already):

export const advanceGame = (players, roll) => {
  let currentPlayer = 0;
  const { currentFrame } = players[currentPlayer];

  if (players[currentPlayer].rolls[currentFrame].length === 2) {
    store.dispatch(nextFrame(players[currentPlayer].id));
    store.dispatch(addRoll(players[currentPlayer].currentFrame, roll, players[currentPlayer].id));
  } else {
    store.dispatch(addRoll(players[currentPlayer].currentFrame, roll, players[currentPlayer].id));
  }
};

What I would like to happen is that that if the user rolled two times already , store.dispatch(nextFrame) updates the currentFrame to the next one and then adds the score to the appropriate frame. However, currently the frame only advance after 3 rolls have been made and 3 items are in the a roll Array.

How could I ensure that ADD_ROLL is only executed after NEXT_FRAME has 'finished'?

I have been staring at this for a really long time and I apologize if my question is to verbose. Happy to clear it up and appreciate any help!


Solution

  • I don't know if it's exactly what your looking for but i think that reducer is ALWAYS synchrone.

    Maybe you can try to make action async instead. Redux-thunk was made for that https://github.com/reduxjs/redux-thunk

    For example you can dispatch an action which do an api call, and the second action is only call if the api has returned his response.

    You should find code that take time to execute and deplace it in the thunk, and only call the reducer to update your state.