4

Say you have the following TSX code:

render() {
    const someParameter = 5;

    return (
        <div onClick={() => this.doSomething(someParameter)}></div>
    );
}

Now, we know that we shouldn't pass fat arrow functions like this to the onClick handler because it will create a new function every time, forcing a rerender (this is especially bad when we're passing this callback to a deep component, not just a div).

So then the solution, if there were no parameters, would be to bind doSomething to this in the constructor of our class, like this.doSomething = this.doSomething.bind(this) and then pass the bound function as the callback. However, this doesn't cut it in our case, because we want to pass a parameter to the function - someParameter. Imagine that someParameter wasn't just a stupid constant like it is above, but instead an element in an array that we get in a map() over an array in our render method. How do we handle this situation? That is, how do we pass a function that is not created from scratch every time, thereby breaking our ability to smartly rerender only when necessary?

Thanks!

3
  • We really should benchmark our applications before determining "that we shouldn't pass fat arrow functions" in such contexts. Sure, it is suboptimal if render is called many times, but otherwise it is likely fine. It also reads very clearly and will be type-checked. Commented Jul 19, 2017 at 17:49
  • I agree with Aluan. If you're not having performance problems, you may not need to worry about this. Unless you're causing a massive cascade of re-rendering, you're probably fine in terms of computational cost, and you can worry about clarity and brevity instead. Commented Jul 19, 2017 at 18:49
  • 1
    It destroys a big part of the benefit of using Redux, though, especially if you're passing the callback at a high level. Also, my question wasn't "discuss the benefits of this performance optimization" - it was "how do we do this?" Commented Jul 19, 2017 at 20:21

2 Answers 2

4

bind allows to bind not only context but also parameters. So you can do something like this in constructor:

this.doSomething = this.doSomething.bind(this, someArgument)

Above solution will work only if someArgument doesn't change during component lifecycle.
If this argument is going to be dynamic the only solution is to use arrow function like in your example - but please note that might casue additional re-rendering only if this function is passed as a prop to child components (not HTML elements). According to react docs:

In most cases, this is fine. However, if this callback is passed as a prop to lower components, those components might do an extra re-rendering.

If you don't pass it to React child component it shouldn't be a problem and will not cause any additional re-rendering. Please note that there is a difference between React components and HTML tags. If you use arrow function as event callback on HTML element (e.g. on div generated in map) it will not trigger additional rendering of this div because it's not a component and doesn't have render method. React Virtual DOM algorithm will decide if actuall DOM element corresponding to this div should be updated and in our case existing DOM element will not be updated (you can check it using dev tools).

class Hello extends React.Component{
  constructor() {
    super();
    this.state = {test: 0};
  }
  componentDidMount() {
    var i =0;
    this.timer = setInterval(() => {
      i++;
    	this.setState({test: i})
    }, 1000)
  }
  componentWillUnmount() {
    clearInterval(this.timer)
  }
  render() {
    return (<div>{this.state.test}
      {
        [1, 4, 6].map((v) => {
          return (<div key={v} onClick={() => { console.log(this.state.test)}}>test</div>)
        })
      }
    </div>);
  }
};

ReactDOM.render(
  <Hello name="World" />,
  document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
    <!-- This element's contents will be replaced with your component. -->
</div>

You can inspect outoput generated by above code snippet to see that div elements are not updated in DOM although they have arrow function defined as event callback and parent component it being re-rendred.

In case you need to pass this function to child component you can separetely pass prop with function and prop with argument value to avoid extre re-rendering (of course you also have to implement shouldComponentUpdate):

<SomeComponent myClick={this.doSomething} myArgument={someArgument} />

and somewhere in SomeComponent:

//JSX in SomeComponent render method
<div onClick={() => this.props.myClick(this.props.myArgument)} >...</div>

You can also create additional method in SomeComponent instead of using an arrow function:

handleMyClick() {
   this.props.myClick(this.props.myArgument);
}

and then use it JSX:

<div onClick={this.handleMyClick} >...</div>

So to sum up: if you use arrow function as event callback on HTML tags it shouldn't cause performance problems and it will not trigger any additional re-rendering - it will not break ability to smartly rerender only when necessary. It may cause additional re-rendering only if arrow function is passed down as prop to child components and in such case you can use soulution suggested by me.

Sign up to request clarification or add additional context in comments.

14 Comments

Yes, but bind is called only once in constructor not on every re-render. As I mentioned bind should be used in constructor
Ah, I see, excellent point. In that case, I would still prefer this.doSomething = something => {...}; for readability but I suppose that is suggestive. Bind may be more readable depending on your background
@MichaelTontchev I think you've misunderstood me. You don't need to wrap it in its own component. It's just an example to show how you can pass callback to already existing child component to avoid triggerring additional re-renders of this child component. If you want to add event callback to HTML element (not React component) you can use bind or arrow function and it shouldn't cause any problems and you don't have to wrap it in component. Using arrow function as prop on HTML element doesn't cause any additional re-renders - it may be problem only if you pass it to child React component
@Michael Tontchev If you want to pass a event callback to div (so to HTML element not to React component) inside a map you can use arrow function because as I mentioned it will not cause additional re-rendering of those divs. Only React components (div is not React component) may be re-rendered when new function is passed as prop. Futhermore, creating new arrow function on each parent component render shouldn't cause any performance problems. So to sum up: you can pass arrow function to divs generated in map and you should avoid it if you pass it to React component.
@MichaelTontchev have you checked my code snippet using dev-tools? Dev-tools highlights every DOM element that has been updated but as you can see 'div' elements in the code snippet are not being updated although they receive new function as event callback every second. This is thanks to Virtual DOM optimizations - DOM elements are updated only when there's 'visible' change. Changing event callback doesn't require DOM tree to be updated because it doesn't make visual changes in DOM. Of course new event handler is added in 'background' but it can be done without re-rendering actual DOM elements
|
0

Can you please check solution proposed here in "Protips" section? https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md

Looks as if this might work:

    var List = createReactClass({
              render() {
        return (
          <ul>
            {this.props.items.map(item =>
              <ListItem key={item.id} item={item} onItemClick={this.props.onItemClick} />
            )}
          </ul>
        );
      }
    });

    var ListItem = createReactClass({
      render() {
        return (
          <li onClick={this._onClick}>
            ...
          </li>
        );
      },
      _onClick() {
        this.props.onItemClick(this.props.item.id);
      }
    });

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.