3

I'm working on a game. Originally, the user was in a single dungeon, with properties:

// state

{
  health: 95,
  creatures: [ {}, {} ],
  bigBoss: {},
  lightIsOn: true,
  goldReward: 54,
  // .. you get the idea
}

Now there are many kingdoms, and many dungeons, and we may want to fetch this data asynchronously.

Is it better to represent that deeply-nested structure in the user's state, effectively caching all the other possible dungeons when they are loaded, and every time we want to update a property (e.g. action TURN_ON_LIGHT) we need to find exactly which dungeons we're talking about, or to update the top-level properties every time we move to a new dungeon?

The state below shows nesting. Most of the information is irrelevant to my presentational objects and actions, they only care about the one dungeon the user is currently in.

// state with nesting

{
  health: 95,
  kingdom: 0,
  dungeon: 1,
  kingdoms: [
    {
      dungeons: [
        {
          creatures: [ {}, {} ],
          bigBoss: {},
          lightIsOn: true,
          goldReward: 54
        }
        {
          creatures: [ {}, {}, {} ],
          bigBoss: {},
          lightIsOn: false,
          goldReward: 79
        }
        {
          //...
        }
      ]
    }
    {
      // ...
    }
  ]
}

One of the things that's holding me back is that all the clean reducers, which previously could just take an action like TURN_ON_LIGHT and update the top-level property lightIsOn, allowing for very straight-forward reducer composition, now have to reach into the state and update the correct property depending on the kingdom and dungeon that we are currently in. Is there a nice way of composing the reducers that would keep this clean?

Dave Newton
  • 158,873
  • 26
  • 254
  • 302
Sam Fen
  • 5,074
  • 5
  • 30
  • 56

1 Answers1

2

The recommended approach for dealing with nested or relational data in Redux is to normalize it, similar to how you would structure a database. Use objects with IDs as keys and the items as values to allow direct lookup by IDs, use arrays of IDs to indicate ordering, and any other part of your state that needs to reference an item should just store the ID, not the item itself. This keeps your state flatter and makes it more straightforward to update a given item.

As part of this, you can use multiple levels of connected components in your UI. One typical technique with Redux is to have a connected parent component that retrieves the IDs of multiple items, and renders <SomeConnectedChild itemID={itemID} /> for each ID. That connected child would then look up its own data using that ID, and pass the data to any presentational children below it. Actions dispatched from that subtree would reference the item's ID, and the reducers would be able to update the correct normalized item entry based on that.

The Redux FAQ has further discussion on this topic: http://redux.js.org/docs/FAQ.html#organizing-state-nested-data . Some of the articles on Redux performance at https://github.com/markerikson/react-redux-links/blob/master/react-performance.md#redux-performance describe the "pass an ID" approach, and https://medium.com/@adamrackis/querying-a-redux-store-37db8c7f3b0f is a good reference as well. Finally, I just gave an example of what a normalized state might look like over at https://github.com/reactjs/redux/issues/1824#issuecomment-228609501.

edit:

As a follow-up, I recently added a new section to the Redux docs, on the topic of "Structuring Reducers". In particular, this section includes chapters on "Normalizing State Shape" and "Updating Normalized Data".

markerikson
  • 63,178
  • 10
  • 141
  • 157
  • Even without nesting, though, my question still remains, just slightly different: Should I have all those other "kingdoms" and "dungeons" in my code when the presentational components don't care about them, and when the properties are identical between each of them, and how can I compose my reducers so that I can cleanly update a property of a single dungeon? Can I pass my current dungeon as the pseudo-root state of all the dungeon-specific reducers, maybe? – Sam Fen Jun 24 '16 at 16:42
  • Not sure what you mean by "having them in my code". Did update my answer with further information and suggestions. – markerikson Jun 24 '16 at 17:19
  • "Having them in my code" was supposed to be "having them in my state." No matter, though, I can work with this. Thanks. – Sam Fen Jun 26 '16 at 21:38
  • Sure. I also just added one more link to the answer, as I wrote up an example of what a normalized state looks like over in the Redux issues list today. – markerikson Jun 26 '16 at 22:53