Skip to content

Instantly share code, notes, and snippets.

@dmeehan1968
Last active February 12, 2018 10:10
Show Gist options
  • Save dmeehan1968/20230085f295f85b96ae6e4183d9796e to your computer and use it in GitHub Desktop.
Save dmeehan1968/20230085f295f85b96ae6e4183d9796e to your computer and use it in GitHub Desktop.
react/jsx-no-bind

ESLINT rule: react/jsx-no-bind

The problem

Arrow functions create a new function on every render, decreasing performance as a result of:

  • Addtional garbage collection
  • Passing changed props to components causing render to travel further into the graph

This is particularly evident when iterating over an array to generate components, which also need function props to handle state change as a result of user interaction, such as onClick/onPress.

Example using Stateless Functional Component:

const List = ({data, onItemPress}) => (
  <View>
  {data.map(item => (
    <View key={item.id} onPress={() => onItemPress(item)}>
      <Text>{item.text}</View>
    </View>
  )}
  </View>
)

List.propTypes = {
  data: PropTypes.arrayOf(
    PropTypes.shapeOf({
      id: PropTypes.number.isRequired,
      text: PropTypes.string.isRequired,
    ).isRequired,
  onItemPress: PropTypes.func.isRequired,
 }

Remove the arrow function

Converting the arrow function to a regular function doesn't solve the problem, you are still creating a new function on every render. Extracting the function requires use of Function.bind.

const listItemPress = (item, handler) => {
  handler(item)
}

const List = ({data, onItemPress}) => (
  <View>
  {data.map(item => (
    <View key={item.id} onPress={listItemPress.bind(null, item, onItemPress)}>
      <Text>{item.text}</View>
    </View>
  )}
  </View>
)

List.propTypes = {
  data: PropTypes.arrayOf(
    PropTypes.shapeOf({
      id: PropTypes.number.isRequired
    ).isRequired,
  onItemPress: PropTypes.func.isRequired,
 }

This causes the same problem, Function.bind still creates a new function, bound with the passed arguments.

Use React.PureComponents, Extract the repetition into a subcomponent

Eliminating the bind during repetition prevents the passed prop from changing on each render.

class List extends React.PureComponent {
  
  static defaultProps = {
    data: PropTypes.arrayOf(
      PropTypes.shapeOf({
        id: PropTypes.number.isRequired
      ).isRequired,
    onItemPress: PropTypes.func.isRequired,
  }
  
  render() {
    <View>
      {this.props.data.map(item => (
        <ListItem key={item.id} onPress={this.props.onItemPress} {...item} />
      )}
    </View>
  }
}

class ListItem extends React.PureComponent {
  
  static propTypes = {
    id: PropTypes.number.isRequired,
    text: PropTypes.string.isRequired,
    onPress: PropTypes.func.isRequired,
  }
  
  constructor(props, context) {
    super(props, context)
    // Bind onPress to the instance, once, here in the constructor
    // NB: this is an instance method, do not store it in React state (via setState)
    this.onPress = this.onPress.bind(this)
  }
  
  onPress() {
    // call the passed prop, with item id as the argument as expected
    this.props.onPress(this.props.id)
  }
  
  render() {
    return (
      <View onPress={this.onPress}>
        <Text>{this.props.text}</Text>
      </View>
    )
  }
}

Remove intermediate function and bind directly on the passed prop

We can partly simplify the subcomponent implementation by removing the handler class method and dynamically creating a property that is the result of binding.

The downside to this is that its conceivable that the parent component may chose to change the passed prop implementation depending on state, which means we will have to rebind if the props change during the lifecycle of the instance. componentWillReceiveProps will allow us to handle this case.

class ListItem extends React.PureComponent {
  
  static propTypes = {
    id: PropTypes.number.isRequired,
    text: PropTypes.string.isRequired,
    onPress: PropTypes.func.isRequired,
  }
  
  constructor(props, context) {
    super(props, context)
    this.onPress = this.props.onPress.bind(null, this.props.id)
  }
  
  componentWillReceiveProps(newProps) {
    this.onPress = this.newProps.onPress.bind(null, this.props.id)
  }
  
  render() {
    return (
      <View onPress={this.onPress}>
        <Text>{this.props.text}</Text>
      </View>
    )
  }
}

Transform Class Properties

babel-plugin-transform-class-properties

Class properties are a proposal for ES that can be enabled via the above Babel transform (although the transform may already be included in a plugin/preset that you are using).

If using ESLint, because class properties are a proposal (Stage 2) they are not supported by ESLint core. There is a separate plugin available for eslint or you can use eslint-plugin-babel to transform via Babel before linting.

The class field syntax creates a function which is implicitly bound to this on each instance, saving the need for a manual bind.

npm install eslint-plugin-babel --save-dev

Add the following to .eslintrc:

{
  "plugins": ["babel"],
  "parser": "babel-eslint",
  "rules": {
    "no-invalid-this": "off",
    "babel/no-invalid-this": "error",
    }
}

Change the code to use the class fields syntax:

class ListItem extends React.PureComponent {

  static propTypes = {
    id: PropTypes.number.isRequired,
    text: PropTypes.string.isRequired,
    onPress: PropTypes.func.isRequired,
  }
  
  onPress = () => this.props.onPress(this.props.id)
  
  render() {
    return (
      <View onPress={this.onPress}>
        <Text>{this.props.text}</Text>
      </View>
    )
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment