Ropez
Ropez

Reputation: 3514

How to handle events in pure functional React components

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

Answers (2)

sjc42002
sjc42002

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

Ropez
Ropez

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

Related Questions