0

I am in the early development stage of a React+Redux game and have followed Redux best practices: pure reducer, presentational/container component separation, using getState() only in Reducer (as opposed to in action creator) etc. The app seems to be working as expected but when I try to reverse an action using Time Travel, even though the state property map[][] and it's computed connected component prop change as expected, the result doesn't get reflected on the UI properly (specifically the player position on the map doesn't follow what state dictates). When I inspect the state changes I can see that all necessary changes are correctly taking place between different states. Here is my reducer:

const gridReducer = (state, action) => {

  if (typeof state === 'undefined'){
    let dungeon = new Dungeon();
    dungeon.generate();
    return {
      boardWidth: Math.floor(((70/100) * window.innerWidth) / 20),
      boardHeight: Math.floor(((70/100) * window.innerHeight) / 20),
      map: dungeon.map,
      position: dungeon.playerPosition
    }
  }
  switch (action.type) {
    case 'GRID_RESIZE': 
      return {...state, 
              boardWidth: action.newBoardWidth,
              boardHeight: action.newBoardHeight
      }
    //This is where I have the issue, map correctly changes both when interacting with the game and when reversing using time travel however the UI fails to update (only in reverse)!
    case 'MOVE':
      let dungeonObj = new Dungeon(state.map.slice(), {...state.position});
      if (dungeonObj.movePlayer(action.direction)) {
        return {...state,
                position: dungeonObj.playerPosition,
                map: dungeonObj.map
               }
      } else return state;
    default:
      return state;
  }
}

Here is the complete code if you want to take a look! The app currently only supports moving the player in the dungeon by pressing arrow keys and the view is supposed to always be centeral based on the position of the player (player fails to move back when using time travel) http://s.codepen.io/sabahang/debug/GjrPNQ

PS: Dungeon.generate does use Math.Random but I'm only using this function in initialState and for dispatched actions I'm only making a shallow copy of the generated map by sending the current state to Dungeon constructor and use its other methods (eg. movePlayer)

Saba Ahang
  • 578
  • 9
  • 24
  • I'm trying your code right now and it does not seem to have a problem with the Time Travel feature in my browser at least. (It seems that you left a `debugger` in line 87, btw). – Daniel Zendejas Oct 03 '16 at 02:53
  • I press the arrow buttons and that triggers the `MOVE` action, logging it in the sidebar. If I reset the state to a previous one with the debug tool it looks fine. – Daniel Zendejas Oct 03 '16 at 02:54
  • @DanielZendejas thanks for reminding the debugger, I removed it. Don't reset the state rather make a few moves and then use the time traver slider to move the player back in the path you originally moved it (or toggle some of the intermediate `MOVE` actions. It messes up the position of the player on the map (the blue dot doesn't move at all or replicates itself) – Saba Ahang Oct 03 '16 at 03:03
  • I see now what you are saying, I'll look further into it and will let you know if I can find something helpful. – Daniel Zendejas Oct 03 '16 at 03:06
  • This is absurd! I can't understand why for the life of me! Do you have any suggestions? – Saba Ahang Oct 04 '16 at 20:18
  • Possible duplicate of [React-redux store updates but React does not](http://stackoverflow.com/questions/33140008/react-redux-store-updates-but-react-does-not) – Saba Ahang Oct 05 '16 at 04:34

1 Answers1

1

Found the culprit. It's not Redux's fault at all, it's about the way React works! If you are new to React and you haven't fallen into this trap yet wait for it! It has to do with the fact that most of the conventional ways of copying a deeply nested object which is needed in Redux to implement a pure Reducer is in fact making a shallow copy of the objects and properties' memory references are still pointing to the original State. React updates the UI based on a deep comparison of the old state and the new one and when some of the references are the same it fails to update the UI properly. Here I have a 2 dimensional array map[][] which is an object and although I'm using ES6 spread operator to avoid modifying the original state because a shadow copy is being made, deeply nested indexes of the original map[][] are being modified. One solution would be to use `Array.map()' to create a completely new object but I ended up using immutablejs and it fixed my problem with the time travel slider.

This is a highly recommended reference if you don't want to spend weeks chasing similar bugs in complicated apps: http://redux.js.org/docs/recipes/reducers/ImmutableUpdatePatterns.html

and there are tons of immutability helpers to help based on your specific need: https://github.com/markerikson/redux-ecosystem-links/blob/master/immutable-data.md#immutable-update-utilities

This one also looks interesting for Redux only: https://github.com/indexiatech/redux-immutablejs

This question is potentially a duplicate of the following: React-redux store updates but React does not

Community
  • 1
  • 1
Saba Ahang
  • 578
  • 9
  • 24