I want to display a selectable list of objects with react. I've got the list component, which owns several groups, which own several elements. Obviously, the list data itself should go into a store, but what about the UI state (e.g., which element is selected)? It's ostensively a property of the root list element, but to pass it down to the elements, each element along the chain (well, just 1 in this case- the group) would need to pass it along even if they don't care about it. And if I want to add another property I need to pass it down the chain rather verbosely.
Also, encapsulation. How do I make a component that might be instanced multiple times with different data?
In other languages I would just pass the list pointer down the chain, so that child elements could whatever they want from it, so everything stays encapsulated. Would the flux way be to pass the store down to child elements? Would you have a separate store for UI state vs the persistent data?
Let's design this from the bottom up. So, at the bottom we have a ListItem.
What does the list item need in order to render? Let's say it's a title, a body, and if it's selected or not.
Also what events make sense of the list item? Probably just onClick.
Does it have any state? No, unless we need to handle things like it being hovered, or other state specific to the ListItem, but not relevant to its parent.
var ListItem = React.createClass({
render(){
var classNames = ["list-item"];
if (this.props.selected) classNames.push("selected");
return (
<li className={classNames.join} onClick={this.props.onClick}>
<div className="title">{this.props.title}</div>
<div className="body">{this.props.body}</div>
</li>
);
}
});
The ListGroup is a bit less obvious.
For props we'll ask for a list of items, the selected index (if any), and an onSelectionChange callback, which is called with (nextIndex, lastIndex)
.
This component also has no state. For extra points, we'll allow specifying a custom renderer, making this more reusable. You can pass renderer={component}
where component is something that implements the ListItem interface described above.
var ListGroup = React.createClass({
getDefaultProps: function(){
return {renderer: ListItem, onSelectionChange: function(){}}
},
render(){
return (
<div>{this.props.items.map(this.renderItem)}</div>
);
},
renderItem(data, index){
var selectedIndex = this.props.selectedIndex;
return (
<this.props.renderer
selected={selectedIndex === index}
key={i}
onClick={() => this.props.onSelectionChange(index, selectedIndex)}
{...data} />
}
});
And then we could render ListGroup like this:
<ListGroup
items={[{title: "foo", body: "bar"}, {title: "baz", body: "quux"]}
selectedIndex={this.state.i}
onSelectionChange={(index) => this.setState({i: index})} />
The data is passed down the tree, but only the essence of the data required to render each component. ListItem doesn't need the full list of items, nor the selected index. It doesn't care if multiple selections are allowed, or just one. All it knows is that it's selected when this.props.selected
is truthy, and otherwise it's not.