DEV Community

Pasquale Mangialavori
Pasquale Mangialavori

Posted on • Edited on

How to pass extra params to your handlers functions with React?

So you want to render a list and pass extra data to your function with React.

Well, React does not handle this for you like Angular or Vue with some extra syntax in html would do (is this html anymore?).

<li *ngFor="let hero of heroes" (click)="onSelect(hero)">
Enter fullscreen mode Exit fullscreen mode

So how to bind data in React efficiently?

Approach 0: Do not try this at home...or office


const things = new Array(32).fill(1).map((el, index) => {
  return {
    index,
    some: 'data'
  };
});

// PLEASE DO NOT DO THIS. But if you want to, who cares ¯\_(ツ)_/¯
class Bad extends React.Component {
  constructor(props) {
    super(props);
    this.state = { message: 'Hello!' };
  }

  handle = el => {
    // WOW a function on every render for every element of the list!!
    alert(JSON.stringify(el));
  };

  render() {
    return (
      <ul>
        {things.map((el, index) => {
          return <li onClick={() => this.handle(el)}>{el.some + el.index</li>;
        })}
      </ul>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

The problem here is the follow: a function is created on every render for every element of the list!!!

So how to solve this? The perfomance for short lists will be good enogh but this approach does not scale. The list could be dynamic or could come from a service call.

Approach 1: Put data in DOM and get it in handler

const things = new Array(32).fill(1).map((el, index) => {
  return {
    index,
    some: 'data'
  };
});
class Approach1 extends React.Component {
  handleClick = evt => {
    // get item stored in dom
    // PRO: single function
    // CONS: dom payload increase, stringify on every call but this is optional
    const item = JSON.parse(evt.target.dataset.item);
    const index = parseInt(evt.target.dataset.index);
    alert(JSON.stringify(item) + index);
  };
  render() {
    return (
      <ul>
        {things.map((el, index) => {
          return (
            <li
              data-item={JSON.stringify(el)}
              data-index={el.index}
              onClick={this.handleClick}
            >
              {el.some + el.index}
            </li>
          );
        })}
      </ul>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

This approach is good, but if you have an item model too huge is not optimal.
PRO: single function for every item in list
CONS: dom payload increases if you have huge data model

Approach 2: The best(?) and verbose approach

const things = new Array(32).fill(1).map((el, index) => {
  return {
    index,
    some: 'data'
  };
});
class BestApproach extends React.Component {
  constructor(props) {
    super(props);
    this.state = { message: 'Hello!' };
    this.handlers = new Map();
  }
  getHandler = (el, index) => {
    if (!this.handlers.has(el)) {
      this.handlers.set(el, evt => {
        // do somenthing with the event
        alert(JSON.stringify(el) + index);
      });
    }
    return this.handlers.get(el);
  };

  componentWillMount() {
    this.handlers.clear();
  }
  render() {
    return (
      <ul>
        {things.map((el, index) => (<li onClick={this.getHandler(el, index)}>{el.some + el.index}</li>))}
      </ul>
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

In this way we create a function for every item but only when the first render occurs. The next time we have the cached function back from getHandler.

Approach 3: Event delegation. Probably the best one

const things = new Array(32).fill(1).map((el, index) => {
  return {
    index,
    some: 'data'
  };
});

class MyLi extends React.Component {
  _handler = (evt) => {
    const { item, onPress } = this.props;
    onPress(item, evt);
  }
  render() {
    return <li onClick={this._handler}>{this.props.children}</li>;
  }
}
class BestOfBestApproach extends React.Component {
  constructor(props) {
    super(props);
    this.state = { message: 'Hello!' };
    this.handlers = new Map();
  }
  handler = ({ index, some }) => {
    alert(some);
  };

  render() {
    return (<ul>
      {things.map((el, index) => (<MyLi onPress={this.handler}>{el.some + el.index}</MyLi>))}
    </ul>);
  }
}
Enter fullscreen mode Exit fullscreen mode

Please comment if you have some better solution/approach or if you see some errors.

Bye and happy React coding!

Top comments (4)

Collapse
 
silvestricodes profile image
Jonathan Silvestri

Is this a good case for some event delegation? Perhaps assigning a click handler to the parent which should be able to give you all the children if you click on one, and then writing appropriate guard clauses to only do things with the info of the DOM node you care about.

Collapse
 
eatsjobs profile image
Pasquale Mangialavori • Edited

You mean somenthing like this?yeah a could add it in the article. thanks

class MyLi extends PureComponent {
_onPress = (evt) => {
const { item, onPress } = this.props;
onPress(evt, item);
}
render() {
return <li onClick={this._onPress}>{this.props.children}</li>
}
}
Collapse
 
ntvinhit profile image
Nguyễn Trọng Vĩnh

I dont always use Map.

Just like:

getHandler(id) {
if (!this.getHandler[id]){this.getHandler[id] = () => ...}
return this.getHandler[id];
}

You can reduce 1 line of code.

Collapse
 
eatsjobs profile image
Pasquale Mangialavori

Yeah you can use a simple object. Maybe an Object.create(null) object is even better