Reputation: 3010
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
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
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