Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

react/jsx-no-bind: how to pass args from callbacks? #659

Closed
alex35mil opened this issue Jan 6, 2016 · 76 comments
Closed

react/jsx-no-bind: how to pass args from callbacks? #659

alex35mil opened this issue Jan 6, 2016 · 76 comments

Comments

@alex35mil
Copy link

I have a question about react/jsx-no-bind rule in 3.0.0: how can I pass args to handler from callback if I can't use bind and arrow functions?

doSomething(thing) { ... }

render() {
  const { items } = this.props;

  return (
    <ul>
    {
      items.map(item => (
        <li onClick={this.doSomething.bind(this, item)} /> // <--- JSX props should not use .bind()
        <li onClick={() => this.doSomething(item)} />      // <--- JSX props should not use arrow functions
      ))
    }
    </ul>
  );
}
@ljharb
Copy link
Collaborator

ljharb commented Jan 6, 2016

With this rule, you'd not be able to do either. In this case, however, I'd think you'd want to use a single onClick on the <ul> though, and use the event's target (ie, event delegation) so that you have 1 handler instead of N - which also neatly bypasses this issue.

I suspect that in all cases where this rule seems problematic, there will be a more ideal approach that obviates the problem.

@alex35mil
Copy link
Author

So in most of the cases suggestion is to save the data to the DOM and fetch it from there via e.target, is this correct?

@ljharb
Copy link
Collaborator

ljharb commented Jan 6, 2016

Ah, that's a fair point. you may not want to do that either. You could use the index of the <li> to look it up in this.props.items, but that seems a bit janky.

Can you be a bit more specific about your use case? I'm curious what prompts this to come up.

@geekjuice
Copy link

There are some tips in the eslint-plugin-react#jsx-no-bind documentation.

tl;dr, you can create another React component for the list items where you pass in the item and the click handler and pass the item as a param.

@ljharb
Copy link
Collaborator

ljharb commented Jan 6, 2016

@geekjuice thanks, that's very helpful!

@geekjuice
Copy link

If you still want to do it inline, it's possible to use something like to _.partial or

function partial(fn, ...args) {
  return fn.bind(fn, ...args);
}

React.createClass({
  handler(item) {
    // do something
  },

  render() {
    const {items} = this.props;
    return items.map(item => {
      return <div onClick={partial(this.handler, item)} />;
    });
  }
});

This will pass through the linter, but will still have the issues listed in eslint-plugin-react#jsx-no-bind documentation.

@ljharb
Copy link
Collaborator

ljharb commented Jan 6, 2016

Right - at that point you might as well disable the rule.

@alex35mil
Copy link
Author

I guess another React component is the best solution with this rule enabled. Thanks, guys, closing this.

@Kerumen
Copy link

Kerumen commented Feb 23, 2016

Just came up with this issue for the "new" way of passing ref. Instead of a string you should now use arrow functions: https://facebook.github.io/react/docs/more-about-refs.html

A recent commit in the plugin introduces flexibility with the rule and allow arrow functions in ref attributes.

Can you add this?

@ljharb
Copy link
Collaborator

ljharb commented Feb 23, 2016

That link says "Also note that when writing refs with inline function expressions as in the examples here, React sees a different function object each time so on every update, ref will be called with null immediately before it's called with the component instance." - it still seems to me that it's an antipattern to create functions in the render path.

@Kerumen
Copy link

Kerumen commented Feb 24, 2016

Okey then how should I create refs? Like this?

mapRef(c) {
  this._input = c;
}

render() {
  <input ref={::this.mapRef} />
}

@ljharb
Copy link
Collaborator

ljharb commented Feb 24, 2016

:: is just the same as this.mapRef.bind(this) so no, you'd do this.mapRef = this.mapRef.bind(this) in the constructor, and then just ref={this.mapRef} in render.

@Kerumen
Copy link

Kerumen commented Feb 24, 2016

I wanted to keep it short and avoid the constructor declaration.
Does this affect performance to create functions in render or it's only for design pattern reasons?

@ljharb
Copy link
Collaborator

ljharb commented Feb 24, 2016

If you're using this then you can't/shouldn't avoid the constructor.

It affects performance significantly, because react's DOM diffing can't use === to compare prop values when you create a new function on every render.

@Kerumen
Copy link

Kerumen commented Feb 24, 2016

Okey I understand. And what about es7 class properties ?

mapRef = (c) => {
  this._input = c;
};

It'll bind this without the constructor.

@ljharb
Copy link
Collaborator

ljharb commented Feb 24, 2016

Although those aren't standard yet, those will likely work as if they were run in the constructor - so that would work just fine!

@Kerumen
Copy link

Kerumen commented Feb 24, 2016

Okey! Thanks for your help, gladly appreciated! :)

@alexprice1
Copy link

What do you think of this approach? Each function is created once in the constructor and that is it.

class Form extends React.Component {
  constructor() {
    const fields = this.props.fields;

    this.changeField = {};
    this.fields.forEach((field) => {
      this.changeField[field] = this.onChange.bind(this, field.name); 
    });
  }

  onChange(fieldName, event) {
    this.props.onChange(fieldName, event.target.value);
  }

  render() {
    const fields = this.props.fields.map((field) => {
      return <input value={field.value} onChange={this.changeField[field.name]}/>
    });

    return (
      <div>
        {fields}
      </div>
    );
  }
}

@ljharb
Copy link
Collaborator

ljharb commented Feb 25, 2016

s/changeField/this.changeField inside the forEach and sure, that works great!

@alexprice1
Copy link

Cool, thanks!

@Dindaleon
Copy link

Dindaleon commented May 6, 2016

Hello there, sorry if my question is a bit off topic but I would like to take the opportunity to ask whether I need a constructor or not for declaring the state if I am using es6 and es7.

Currently, in my code, i doing this:

state = { ... }

someFunction = () => { ... }

Instead of:

constructor(props) {
  super(props)
  this.state = { ... }
}

What would the difference be? Do I always need the constructor?

@ljharb
Copy link
Collaborator

ljharb commented May 6, 2016

@Dindaleon the former is the "class properties" proposal, which is not yet standard (still stage 1 or 2, I think). The latter is the only way to do it currently.

@supnate
Copy link

supnate commented May 13, 2016

I created a simple utility memobind to address this problem, any comment is welcome. https://github.com/supnate/memobind

@benjick
Copy link

benjick commented May 30, 2016

What about something like this?

foo = bar => () => {
  this.props.myFunc(bar);
}

@ljharb
Copy link
Collaborator

ljharb commented May 30, 2016

@benjick then this.foo() would return a new arrow function on every call, which is no better than just making the arrow function inline (and arguably worse, since it's both less clear, and also incurs the overhead of one extra function call)

@alexprice1
Copy link

alexprice1 commented May 30, 2016

Not true. You would not be recreating foo each time it renders (that's the issue).

@ljharb
Copy link
Collaborator

ljharb commented May 30, 2016

@alexprice91 ah, i see what you mean. How is your example better than just foo = () => { … } and using this.foo in the render?

@benjick
Copy link

benjick commented May 30, 2016

So what's the real solution do this problem? I need to pass variables

@scamden
Copy link

scamden commented Jun 7, 2017

interesting, i would be curious to know what that distinction is. i was actually just using the term because it was in the related linting rule (jsx-no-lambda). but i've always essentially thought of them as the same, what makes them not lambas?

@scamden
Copy link

scamden commented Jun 7, 2017

a quick look on wikipedia makes them sound the same:

In several programming languages, anonymous functions are introduced using 
the keyword lambda, and anonymous functions are 
often referred to as lambdas or lambda abstractions.

but i am genuinely curious to learn it, if there is a distinction

@ljharb
Copy link
Collaborator

ljharb commented Jun 7, 2017

I guess I'd never really thought about it; https://gist.github.com/ericelliott/414be9be82128443f6df has interesting discussion. In general to me it evokes lambda calculus meanings, but I suppose it's fine describing an arrow function ¯\_(ツ)_/¯

As to the topic at hand; it's a fair counterpoint that bindings aren't state. However, in React, because any custom components you render might be pure, the rule I'd probably follow is "whenever you pass a function as a prop on a custom component, be sure it's the same (===) across renders when possible".

@scamden
Copy link

scamden commented Jun 7, 2017

That's fair. It def seems like a good practice to avoid creating anonymous functions unnecessarily. just seemed extreme to straight up ban it, when there are some really nice use cases for it. But i suppose you can always ignore or extract them out of jsx as you said above. thanks for the discussion.

@skosch
Copy link

skosch commented Oct 23, 2017

I wonder whether the canonical solution – to create a new component with item passed as a prop – is actually any better, performance-wise. Sure: use arrow functions, and they'll be re-bound on every render. But create a new component, and you'll have the overhead of calling extra components and diffing their item props on every re-render as well. So I wouldn't be surprised if in practice, the occasional

items.map(item => (
  <li onClick={() => this.doSomething(item)} />      // <--- JSX props should not use arrow functions
))

isn't actually as bad as it's made out to be. Plus, it makes for such beautifully readable code.

Are there benchmarks on this anywhere?

@ljharb
Copy link
Collaborator

ljharb commented Oct 23, 2017

It allows react to do the diffing, which it’s capable of doing - if you create a new function, that must always be !== from the previous render.

Separately, in your example, 1) a constructor-bound instance method solves it without creating a new component, and 2) the perf recommendation actually doesn’t apply to functions passed to DOM elements, but the rule doesn’t yet differentiate.

@scamden
Copy link

scamden commented Oct 23, 2017 via email

@skosch
Copy link

skosch commented Oct 23, 2017

@scamden thanks a ton for that link. It articulates my hunch so much better than I could hope to.

I think I'll be turning this eslint rule off – it has taught me not to abuse binds and arrow functions, which I'm grateful for, but beyond that all the warnings are just too much noise. If re-rendering performance ever becomes an issue I'll have to run benchmarks anyway to be sure.

@scamden
Copy link

scamden commented Oct 23, 2017 via email

@ljharb
Copy link
Collaborator

ljharb commented Oct 23, 2017

It's been an issue on airbnb.com many times; and it's often very hard to debug where these issues are occurring.

I do agree that the jsx-no-bind rule (again, the term "lambda" isn't actually appropriate anywhere) is overzealous; it shouldn't warn on arrow functions used on HTML elements, only on custom components, and only in the render path.

jsx-eslint/eslint-plugin-react#1459 will reduce the noise of this rule soon; I'd recommend not turning it off.

@skosch
Copy link

skosch commented Oct 23, 2017

Ahh ok @ljharb that PR would indeed solve most (maybe all) of my issues.

@scamden
Copy link

scamden commented Oct 23, 2017 via email

@ljharb
Copy link
Collaborator

ljharb commented Oct 23, 2017

No, I don't have that data available; but also the profiling data was useless in locating the issue. We instead found places where this rule was overridden, and fixed them, and things got faster.

Functions can only be compared with ===; PureComponents are not allowed to skip a render if any of their function props aren't strictly equal. It's 100% a no-brainer win here.

@scamden
Copy link

scamden commented Oct 23, 2017 via email

@duleep-noetic
Copy link

duleep-noetic commented Jan 2, 2018

you could do something like this.

class Example extends React.PureComponent {
    constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
    }

    handleClick(id) {
        return (event) => this.props.doSomething(id);
    }

    render() {
        <ul>
            {this.props.listOfSomeKind.map(thing =>
                <li onClick={this.handleClick(thing.id)}>
                    {thing.name}
                </li>
            )}
        </ul>
    }
Use bind inside the constructor rather than inside the render so that it doesn't have to redefine functions every time when it renders.

@ljharb
Copy link
Collaborator

ljharb commented Jan 2, 2018

Absolutely do not do that - you’re calling handleClick, which doesn’t exist, and you don’t want to be creating a new function in every render.

@duleep-noetic
Copy link

@ljharb im binding the callback in the constructor.constructor is only called once, not on every render..i think this is the recommended way in react docs. https://reactjs.org/docs/faq-functions.html#how-do-i-bind-a-function-to-a-component-instance.(es2015). and hence this.handleClick exist. if you define bind inside render method then it will be creating a new function every time it renders.

@ljharb
Copy link
Collaborator

ljharb commented Jan 2, 2018

@duleep-noetic i agree with what you’re saying, but your code example has onClick={this.handleClick(blah)} which invokes the function every single render, and passes its return value as a prop.

@duleep-noetic
Copy link

@ljharb i think calling the function instead of passing the function to onClick will invoke it in every render..in JS, funcName(...) is always an immediate function-call. i.e. onClick: handleClick() sets onClick to the return value of handleClick(). but whereas in bind the return value is a new function, when called boundFunction() actually calls handleClick().Is it or did i miss something?

@ljharb
Copy link
Collaborator

ljharb commented Jan 2, 2018

@duleep-noetic yes, i'm saying that is the problem. onClick should be a reference to a function, and should not change across renders.

The proper way to do what you're suggesting is something more like this:

class Example extends React.PureComponent {
    constructor(props) {
        super(props);
        this.handleListClick = this.handleListClick.bind(this);
        this.createThingOnClicks(this.props.listOfSomeKind);
    }

    componentWillReceiveProps(nextProps) {
      this.createThingOnClicks(nextProps.listOfSomeKind);
    }

    createThingOnClicks(things) {
      this.thingClicks = things.reduce((acc, thing) => ({
        ...acc,
        [thing.id]: (event) => { /* do something with `thing` */ },
      }), {});
    }

    handleListClick(e) {
      // do something
    }

    render() {
      return (
        <ul onClick={this.handleListClick}>
            {this.props.listOfSomeKind.map(thing => (
                <li onClick={this.thingClicks[thing.id]}>
                    {thing.name}
                </li>
            ))}
        </ul>
      );
    }
}

@geoguide
Copy link

Okay, I understand conceptually, breaking the item out into it's own component, but if I have a pagination component and I break Link out into it's own component, it's still going to create a function for each Link anyway, am I really saving anything?

@ljharb
Copy link
Collaborator

ljharb commented Apr 18, 2018

@geoguide yes, it would save on rerenders of Link, potentially.

@ElvenMonky
Copy link

switching from functional components to class components just for event handlers sounds a bit silly

@ljharb
Copy link
Collaborator

ljharb commented Oct 10, 2018

@ElvenMonky why? They’re stateless functional components, and event handlers are state.

@ElvenMonky
Copy link

ElvenMonky commented Oct 10, 2018

Event handlers have nothing to do with state. They are just functions.

Another note that jsx-no-bind doesn't make much sense for bindings to events in Rect Elements, because they go to Synthetic Event System, which doesn't update DOM when handler instance changes.

@ljharb
Copy link
Collaborator

ljharb commented Oct 10, 2018

@ElvenMonky to the component that receives them they are indeed props; but to the component that creates them, they are state.

I agree that the rule doesn't make much sense when applied to event handlers to DOM elements only.

@ElvenMonky
Copy link

ElvenMonky commented Oct 10, 2018

Sorry, for editing my comment. My point is that event handlers are simple functions that can come from everywhere (defined in module scope, or on/in function itself, be imported from other modules). They are not necessarily come from state, neither they have to deal with state in any way (like calling setState).

More over state is specific property of each component, that almost never includes any functions, unless they can change dynamically,

@ljharb
Copy link
Collaborator

ljharb commented Oct 10, 2018

They are conceptually state, whether they come from component state or not. Instance properties are also state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests