Passing an arrow function in render will cause React to create a new function each time the component renders. But you don't need to worry because the impact on performance is very minuscule. It's very unlikely that your users will experience a noticeable lag just because of arrow functions in render.
Arrow functions don't redefine the value of this within their function body. This makes it a lot easier to predict their behavior when passed as callbacks, and prevents bugs caused by use of this within callbacks.
The warning "Props spreading is forbidden" is caused when we use the spread syntax to unpack a props object when passing props to a component. To get around the warning, disable the eslint rule. Here is an example of how the warning is caused. Copied!
Not only can JSX elements be passed as props to components, but we can also pass other components as props.
Why you shouldn't use inline arrow functions in JSX props
Using arrow functions or binding in JSX is a bad practice that hurts performance, because the function is recreated on each render.
Whenever a function is created, the previous function is garbage collected. Rerendering many elements might create jank in animations.
Using an inline arrow function will cause PureComponent
s, and components that use shallowCompare
in the shouldComponentUpdate
method to rerender anyway. Since the arrow function prop is recreated each time, the shallow compare will identify it as a change to a prop, and the component will rerender.
As you can see in the following 2 examples - when we use inline arrow function, the <Button>
component is rerendered each time (the console shows the 'render button' text).
Example 1 - PureComponent without inline handler
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log('render button');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
onClick = () => this.setState((prevState) => ({
counter: prevState.counter + 1
}));
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ this.onClick } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Example 2 - PureComponent with inline handler
class Button extends React.PureComponent {
render() {
const { onClick } = this.props;
console.log('render button');
return (
<button onClick={ onClick }>Click</button>
);
}
}
class Parent extends React.Component {
state = {
counter: 0
}
render() {
const { counter } = this.state;
return (
<div>
<Button onClick={ () => this.setState((prevState) => ({
counter: prevState.counter + 1
})) } />
<div>{ counter }</div>
</div>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Binding methods to this
without inlining arrow functions
Binding the method manually in the constructor:
class Button extends React.Component {
constructor(props, context) {
super(props, context);
this.cb = this.cb.bind(this);
}
cb() {
}
render() {
return (
<button onClick={ this.cb }>Click</button>
);
}
}
Binding a method using the proposal-class-fields with an arrow function. As this is a stage 3 proposal, you'll need to add the Stage 3 preset or the Class properties transform to your babel configuration.
class Button extends React.Component {
cb = () => { // the class property is initialized with an arrow function that binds this to the class
}
render() {
return (
<button onClick={ this.cb }>Click</button>
);
}
}
When we create an inner function (event handler for example) inside a function component, the function will be recreated every time the component is rendered. If the function is passed as props (or via context) to a child component (Button
in this case), that child will re-render as well.
Example 1 - Function Component with an inner callback:
const { memo, useState } = React;
const Button = memo(({ onClick }) => console.log('render button') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = () => setCounter(counter => counter + 1); // the function is recreated all the time
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
To solve this problem, we can wrap the callback with the useCallback()
hook, and set the dependencies to an empty array.
Note: the useState
generated function accepts an updater function, that provides the current state. In this way, we don't need to set the current state a dependency of useCallback
.
Example 2 - Function Component with an inner callback wrapped with useCallback:
const { memo, useState, useCallback } = React;
const Button = memo(({ onClick }) => console.log('render button') || (
<button onClick={onClick}>Click</button>
));
const Parent = () => {
const [counter, setCounter] = useState(0);
const increment = useCallback(() => setCounter(counter => counter + 1), []);
return (
<div>
<Button onClick={increment} />
<div>{counter}</div>
</div>
);
}
ReactDOM.render(
<Parent />,
document.getElementById('root')
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="root"></div>
This is because an arrow function apparently will create a new instance of the function on each render if used in a JSX property. This might create a huge strain on the garbage collector and will also hinder the browser from optimizing any "hot paths" since functions will be thrown away instead of reused.
You can see the whole explanation and some more info at https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
Using inline functions like this is perfectly fine. The linting rule is outdated.
This rule is from a time when arrow functions were not as common and people used .bind(this), which used to be slow. The performance issue has been fixed in Chrome 49.
Do pay attention that you do not pass inline functions as props to a child component.
Ryan Florence, the author of React Router, has written a great piece about this:
https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578
Mostly, because inline functions can break memoization of optimized components:
Traditionally, performance concerns around inline functions in React have been related to how passing new callbacks on each render breaks
shouldComponentUpdate
optimizations in child components. (docs)
It is less about additional function creation cost:
Performance issues with
Function.prototype.bind
got fixed here and arrow functions are either a native thing or are transpiled by babel to plain functions; in both cases we can assume it’s not slow. (React Training)I believe people claiming function creation is expensive have always been misinformed (React team never said this). (Tweet)
react/jsx-no-bind
rule useful?You want to ensure, that memoized components work as intended:
React.memo
(for function components)PureComponent
or custom shouldComponentUpdate
(for class components)By obeying to this rule, stable function object references are passed. So above components can optimize performance by preventing re-renders, when previous props have not changed.
Classes: Define the handler as method, or class property for this
binding.
Hooks: Use useCallback
.
In many cases, inline functions are very convenient to use and absolutely fine in terms of performance requirements. Unfortunately, this rule cannot be limited to only memoized component types. If you still want to use it across-the-board, you could e.g. disable it for simple DOM nodes:
rules: {
"react/jsx-no-bind": [ "error", { ignoreDOMComponents: true } ],
}
const Comp = () => <span onClick={() => console.log("Hello!")} />; // no warning
To avoid creating new functions with the same arguments, you could memoize the function bind result, here is a simple utility named memobind
to do it: https://github.com/supnate/memobind
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With