Are Lambda in JSX Attributes an anti-pattern?

Viewed 12685

I started using a new linter today (tslint-react) and it is giving me the following warning:

"Lambdas are forbidden in JSX attributes due to their rendering performance impact"

I get that this causes the a new function to be created with each render. And that it could trigger unneeded re-renders because the child component will think it's props have changed.

But my question is this, how else can one pass parameters to an event handler inside a loop:

customers.map( c => <Btn onClick={ () => this.deleteCust(c.id) } /> );
3 Answers

Well as far as I know, it has an impact on performance even if you're not using React.PureComponent or useMemo. When you define anonymous arrow function (Lambda) in component's prop (JSX attribute), the same function is created on each render so that JS engine can't reuse it. That recreation slows don't performance because JavaScript's engine garbage collector has to collect those unneccessary functions.

There are several other approaches which behaves the same. Take a look at example below:

#1 Lamba approach
customers.map( c => <Btn onClick={ () => this.deleteCust(c.id) } /> );

#2 bind apprach
customers.map( c => <Btn onClick={ this.deleteCust.bind(this, c.id) } /> );

#3 call approach
customers.map( c => <Btn onClick={ this.deleteCust.call(this, c.id) } /> );

#4 apply approach
customers.map( c => <Btn onClick={ this.deleteCust.apply(this, [c.id]) } /> );

I would say the recommended approach is to create a separate function inside the component being mapped. Let's modify a bit your example:

const Btn = ({ clicked, customer }) => {
  const buttonClickHandler = () => {
    clicked(customer.id)
  }
  return <button onClick={buttonClickHandler}>Click me!</button> 
}

const App = () => {
  return (
    <App>
      { customers.map(c => <Btn customer={c} clicked={deleteCust} />) }
    </App>
  )
}

So now, since instead of anonymous function (which can't be reused) we're using function expression in a constant, React doesn't recreate new function every new component re render and the garbage collector can rest in piece!

I am not sure on why they are / are not allowed, but regardless; Javascript allows us to declare functions within blocks of code like so

function mapCustomersToButton(c) {
  function handleBtnClick() {
    this.deleteCust(c.id);
  }

  return <Btn onClick={handleBtnClick} />
}

return customers.map(mapCustomersToButton);

The handleBtnClick function creates a closure around the c object being passed into it from the mapCustomersToButton function; preserving the reference.

This is equivalent to the following:

return customers.map(c => <Btn onClick={() => this.deleteCust(c.id)} />);
Related