Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Redux: Can't figure out why ownProps gets out of sync with state

Tags:

reactjs

redux

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">
like image 218
JamesPlayer Avatar asked Jul 05 '18 23:07

JamesPlayer


1 Answers

Problem

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).

Solution

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);

Edit 417121z17

like image 104
Roy Wang Avatar answered Nov 05 '22 07:11

Roy Wang