I have hierarchical, recursive data structure. It's a list of named items and every item can have its own sub-list of items. It looks like this:
const items = [
{
name: 'item1',
items: [
{name: 'item2'},
{name: 'item3'},
{name: 'item4', items: [
{
name: 'item5'
}
]},
]
},
{
name: 'item6'
}
]
I want to display it in unordered list with nested sub-lists. Also I need the ability to mark some items as highlighted and to dehighlight all items. Because of the second requirement information about highlighted items must live in top level component.
My naive implementation looks like this:
class App extends Component {
constructor() {
super(...arguments)
this.state = {
items: items,
highlightedItemsNames: {},
}
this.handleHighligtItem = (item) => {
this.setState({highlightedItemsNames: {
...this.state.highlightedItemsNames, [item.name]: true
}})
}
this.handleDehighlightAllItems = () => {
this.setState({highlightedItemsNames: {}})
}
}
render() {
return <div>
<button onClick={this.handleDehighlightAllItems}>dehighlight</button>
<List
items={this.state.items}
highlightedItemsNames={this.state.highlightedItemsNames}
onHighlight={this.handleHighligtItem}
/>
</div>
}
}
function List(props) {
return <ul>
{props.items.map(item => <li key={item.name}>
<Item
item={item}
highlighted={props.highlightedItemsNames[item.name]}
highlightedItemsNames={props.highlightedItemsNames}
onHighlight={props.onHighlight}
/>
</li>)}
</ul>
}
function Item(props) {
let className = "item"
if (props.highlighted) {
className += '-highlighted'
}
console.log('renders item', props.item.name)
return <div>
<span className={className}>{props.item.name}</span>
<button onClick={() => props.onHighlight(props.item)}>highlight</button>
{props.item.items ? <List
items={props.item.items}
highlightedItemsNames={props.highlightedItemsNames}
onHighlight={props.onHighlight}
/> : null}
</div>
}
But now when I highlight an item highlightedItemsNames changes and every Item re-renders. To display data correctly only the highlighted item and its parents need to re-render. So how can I avoid all of those unnecessary re-renders?
In my application list of items can be a bit big and re-rendering all of it causes app to noticeably freeze on click.
I could implement shouldComponentUpdate in Item that checks if any sub-item should be re-rendered but does this have an actual chance to improve performance over virtual-dom?
I'm not using Redux or any other state management library and I'd rather not do this at this point.