megapctr megapctr - 2 months ago 14
React JSX Question

Removing a key from state causes problems with rendering

Let's say I'm writing a Todo app that allows to create groups of todos.

My state is structured like so:

state = {

groups: {
groupName: [todoId],
},

todos: {
ids: [todoId],
byId: {
todoId: todoObj,
},
},
}


I'm implementing a
removeGroup
operation. The
groups
reducer would pick all
todoId
s from the given group name, move them to a default group and remove the old group. I'm not mutating the state.

I have three React components,
TodoGroups
,
Group
and
Todo
.


  • TodoGroups
    renders a list of
    Group
    s, based on
    groupNames
    prop from
    getGroupNames(state)
    selector.

  • Group
    renders a list of
    Todo
    s, based on
    todoIds
    prop from
    getGroupTodoIds(state, groupName)
    selector



The
getGroupNames
just runs
Object.keys(state)
and orders it alphabetically.




Now, here is my problem:

When I trigger
removeGroup
, I get a PropType warning that
todoIds
prop is undefined. That warning comes from the
Group
I just removed.

What happens is that
Group
is re-rendered before
TodoGroups
receives new props. Because of that, it still uses the old name and
getGroupTodoIds
returns
undefined
since the old group is not there anymore.

What would be a proper solution to this problem? I could just fall back to an empty array either in
getGroupTodoIds
,
mapStateToProps
or even
Group.defaultProps
, but all of this seems like going in the wrong direction.

Answer

The right solution is probably to not connect the Group component but instead pass down the necessary information from the connected TodoGroups component.

The issue is scheduling: connect will alert all connected components when state changes, but the existence of some of these components is itself dependent on state. How does react know not to update the child until the parent completes rendering? You can read more about this and find some other solutions here, though I'm not totally up to date with it.

Also fyi, you're probably not getting anything out of using the getGroupTodoIds(state, groupName) selector: selectors are basically just memoized functions that get re-computed every time an argument changes. By calling it with multiple different groupName values on every render, it's being re-computed every single time.