aghidini
aghidini

Reputation: 3010

Force a child component to update when the parent state changes

I have a MyList component that fetches items, allows filtering and sorting. This component is already used in other parts of the apps and it works well. It uses render props to render the items so it accepts a renderItem prop of type function.

Now I'm building a simple list to allow item selection using the aforementioned component and I'm checking for the selected state in the render prop renderItem method. The problem is that when I change the state of MySelectableList the MyList component doesn't update because its props does not change (it's always the same bound function renderProp). For now I forced the rendering of the child with this.renderItem = this.renderItem.bind(this); but I don't like it, I know that I can update the child component with ref but I don't like it either.

Is there a better method to force the child component to render when the parent state changes? Am I doing something wrong?

Full code of MySelectableList:

class MySelectableList extend Component {
    constructor (props) {
        super(props);

        this.state = {
            selectedItems: [],
        };
        this.renderItem = this.renderItem.bind(this);
        this.toggle = this.toggle.bind(this);
        this.isSelected = this.isSelected.bind(this);
    }

    toggle (item) {
        const newItems = this.state.selectedItems.slice(0);
        const index = newItems.indexOf(item.uuid);
        if (index === -1) {
            newItems.push(item.uuid);
        } else {
            newItems.splice(index, 1);
        }
        this.setState({ selectedItems: newItems });
        // Force MyList to re-render by tricking react that it's different
        this.renderItem = this.renderItem.bind(this);
    }

    isSelected (item) {
        return this.state.selectedItems.includes(item.uuid);
    }

    renderItem (item) {
        return (<MySelectableItem
            key={ item.uuid }
            item={ item }
            toggle={ this.toggle }
            selected={ this.isSelected(item) } />);
    }

    render () {
        return (
            <div>
                ...
                <MyList renderItem={ this.renderItem } />
                ...
            </div>
        );
    }
}

Thanks in advance.

EDIT

The MyList component is connected to redux store using connect. I discovered that connect is the cause of the MyList component missing rendering, using only "vanilla" react component it works correctly.

I reproduced the problem in this codesandbox: https://codesandbox.io/s/0mov14nmmp

Upvotes: 0

Views: 936

Answers (2)

Yongzhi
Yongzhi

Reputation: 1070

There is nothing wrong with the way you implement MyList. React Native FlatList has the same pattern. But why dont you also pass items as a property to the MyList, so it will be like

<MyList items={this.state. selectedItems} renderItem={this.renderItem} />

This way MyList will re-render because items property changes. items property is needed as well because I assume that in your MyList component you need to do items.map function right? otherwise how do you know how many items in total you need to render?

Upvotes: 0

nitte93
nitte93

Reputation: 1840

Since you asked about how to do it more react friendly way

The nicer way to do this would be:

render () {
        return (
            <div>
                <MyList {...whateeverExtraPropsyouWantToPass}>
                   <MySelectableItem
                       key={ item.uuid }
                       item={ item }
                       toggle={ this.toggle }
                       selected={ this.isSelected(item) } />
                </MyList>
            </div>
        );

Then your MyList will look something like this:

render () {
        return (
            <div>
               ...//your other MyList code
               ...
               {this.props.children}
            </div>
        );

This looks more readable, more maintainable and easily debuggble. But I'm sure this is all obvious to you. Since, you asked about a react friendly way, this is the most react friendly way you can do.

I would not suggest unnecessary, explicitly trying to render any component. Until and unless it is the only way, which is not the case in your component.

Upvotes: 1

Related Questions