In the chapter on Designing the State Shape, the docs suggest to keep your state in an object keyed by ID:
Keep every entity in an object stored with
1) Is the simplicity of the reducer the motivation for going with the object-keyed-by-id approach? Are there other advantages to that state shape?
The main reason you want to keep keep entities in objects stored with IDs as keys (also called normalized), is that it's really cumbersome to work with deeply nested objects (which is what you typically get from REST APIs in a more complex app) — both for your components and your reducers.
It's a bit hard to illustrate the benefits of a normalized state with your current example (as you don't have a deeply nested structure). But let's say that the options (in your example) also had a title, and were created by users in your system. That would make the response look something like this instead:
[{
id: 1,
name: 'View',
open: false,
options: [
{
id: 10,
title: 'Option 10',
created_by: {
id: 1,
username: 'thierry'
}
},
{
id: 11,
title: 'Option 11',
created_by: {
id: 2,
username: 'dennis'
}
},
...
],
selectedOption: ['10'],
parent: null,
},
...
]
Now let's say you wanted to create a component that shows a list of all users that have created options. To do that, you'd first have to request all the items, then iterate over each of their options, and lastly get the created_by.username.
A better solution would be to normalize the response into:
results: [1],
entities: {
filterItems: {
1: {
id: 1,
name: 'View',
open: false,
options: [10, 11],
selectedOption: [10],
parent: null
}
},
options: {
10: {
id: 10,
title: 'Option 10',
created_by: 1
},
11: {
id: 11,
title: 'Option 11',
created_by: 2
}
},
optionCreators: {
1: {
id: 1,
username: 'thierry',
},
2: {
id: 2,
username: 'dennis'
}
}
}
With this structure, it's much easier, and more efficient, to list all users that have created options (we have them isolated in entities.optionCreators, so we just have to loop through that list).
It's also quite simple to show e.g. the usernames of those that have created options for the filter item with ID 1:
entities
.filterItems[1].options
.map(id => entities.options[id])
.map(option => entities.optionCreators[option.created_by].username)
2) It seems like the object-keyed-by-id approach makes it harder to deal with standard JSON in/out for an API. (That's why I went with the array of objects in the first place.) So if you go with that approach, do you just use a function to transform it back and forth between JSON format and state shape format? That seems clunky. (Though if you advocate that approach, is part of your reasoning that that's less clunky than the array-of-objects reducer above?)
A JSON-response can be normalized using e.g. normalizr.
3) I know Dan Abramov designed redux to theoretically be state-data-structure agnostic (as suggested by "By convention, the top-level state is an object or some other key-value collection like a Map, but technically it can be any type," emphasis mine). But given the above, is it just "recommended" to keep it an object keyed by ID, or are there other unforeseen pain points I'm going to run into by using an array of objects that make it such that I should just abort that plan and try to stick with an object keyed by ID?
It's probably a recommendation for more complex apps with lots of deeply nested API responses. In your particular example though, it doesn't really matter that much.