Reputation: 3514
According to Cam Jackson, we should use Redux, and write small, stateless, functional components. Here's an example:
const ListView = ({items}) => (
<ul>
{items.map(item => <ItemView item={item}/>)}
</ul>
)
const ItemView = ({item}) => (
<li><a>{item.name}</a></li>
)
Now, say that you want to handle mouse clicks, and trigger an action that takes the item that was clicked. I think this is a pretty nice way to do this:
const ListView = ({items, onItemClicked}) => {
const _onClick = item => event => {
event.preventDefault()
onItemClicked(item)
}
return (
<ul>
{items.map(item => <ItemView item={item} onClick={_onClick(item)}/>)}
</ul>
)
}
const ItemView = ({item, onClick}) => (
<li><a onClick={onClick}>{item.name}</a></li>
)
However, according to Esa-Matti Suuronen, this is an anti-pattern that will cause a performance penalty.
Obviously, it's possible to handle the event inside the ItemView
component, and inject item
. But, if we want to avoid creating functions inside render functions, then we need to make it a class component.
This is a very common pattern, and I want to find a simple way to handle this, with functional components, without causing performance problems. What do you suggest?
If there is no way to do this, I won't be able to use functional components at all. Because I think more often than not, at some point I will need to convert every functional component to a class.
Upvotes: 2
Views: 3516
Reputation: 464
Pass onItemClicked
function as a prop on ItemView
and use that as the onClick
function. In the implementation of onItemClicked
have it receive an event, and get the item
off of the event.
<ItemView item={item} onItemClicked={onItemClicked}/>
const ItemView = ({item, onItemClicked}) => (
<li><a item={item} onClick={onItemClicked}>{item.name}</a></li>
)
Upvotes: 0
Reputation: 3514
After doing some more thinking, I think I've found the way to do it. I realize that we need to have more Redux containers. Not only for main top-level components, but also for reusable child components.
The example above can be solved like this:
const ListView = ({items, onItemClicked}) => (
<ul>
{items.map(item => <ItemContainer item={item} onItemClicked={onItemClicked}/>)}
</ul>
)
const ItemView = ({item, onClick}) => (
<li><a onClick={onClick}>{item.name}</a></li>
)
const mapDispatch = (dispatch, {item, onItemClicked}) => ({
onClick: (event) => {
event.preventDefault()
onItemClicked(item)
}
})
const ItemContainer = connect(null, mapDispatch)(ItemView)
In many cases, we will not need to pass a callback from the outer component to the inner, because we will be able to dispatch the action directly from the ItemContainer
.
Upvotes: 2