WHY? https://codepen.io/jamesplayer/project/editor/AjWvBb
I'm building a large Redux application and have managed to produce an error where a list item is being passed an id that no longer exists in the store, so when it goes to get the full object from the store it can't find it, causing an error that was apparently fixed in react-redux here
To reproduce requires a very specific sequence of components to do some mounting, dispatching, connecting and rendering so I've created an example of the issue in this codepen: https://codepen.io/jamesplayer/project/editor/AjWvBb
It seems that tweaking most things in this setup can fix the bug but I'm not exactly sure WHY this particular setup generates the issue. If someone could explain to me why this particular order of events creates the bug I'd really appreciate it, I've been trying to get to the bottom of it for a long time.
The basic structure looks like:
<Parent>
<Page1 "I dispatch an action to clear out the list items every time I mount,
then re-populate them after about 500ms">
<Page1Wrapper "I'm connected to the store. I get an update when my child
Page1Child1 mounts">
<Child1 "I dispatch an action when I mount that can affect Page1Wrapper">
<Child2 "I'm connected to the store, though in this example I don't receive any updates that would cause a
re-render">
<List "I get a list of ids from the store. I pass each id to a ListItem">
<ListItem "I'm connected to the store. I get passed an id from
List and fetch the whole listItem object">
<Page2 "I don't do much, but going from page1 to page2 and then back to page1 is
a good way to reproduce the error">
In ConnectedList
, state.listItems
was not empty, so List
renders an instance of ConnectedListItem
.
However, state.listItems
was cleared by the CLEAR_LIST_ITEMS
action right before ConnectedListItem
could get it from the store, which causes it not to find any match from the now emptied state.listItems
.
This bug requires very precise timing to reproduce, which might explain why changing things around fixes it (eg. if rendering of ConnectedListItem
completes before CLEAR_LIST_ITEMS
).
If you provide a default value to listItem
(eg. const ListItem = ({ listItem = {} })
), it will prevent the crash and ConnectedList
will correctly re-render with the updated state.listItems
.
I'd recommend not doing state.listItems.map(listItem => listItem.id)
in connect
, as it causes the component to always re-render, since a different array is constructed even if state.listItems
didn't change. Mapping should be done in component or with reselect instead.
A cleaner approach is to pass the entire item to ListItem
, eliminating the need for ListItem
to be connected to the store:
const ListItem = ({ item }) => <li>{item.text}</li>;
const List = ({ listItems }) => (
<div style={{ backgroundColor: 'lightgreen' }} className="border">
I'm List and I get a list of ids from the store. I pass each id to a ListItem.
<ul>{listItems.map(item => <ListItem key={item.id} item={item} />)}</ul>
</div>
);
const ConnectedList = connect(state => ({
listItems: state.listItems,
}))(List);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With