GithubHelp home page GithubHelp logo

Comments (51)

mohsen1 avatar mohsen1 commented on June 30, 2024 90

You can do onClick={this.foo.bind(this, 23)} but I would question why this is necessary. 23 should be either a prop or part of state.

from tslint-react.

johnsimons avatar johnsimons commented on June 30, 2024 57

In ES6 you should be able to use currying, eg:

<Foo onClick={ this.foo(23) }/>

foo = (event) => (id) => {

}

from tslint-react.

jeff3dx avatar jeff3dx commented on June 30, 2024 54

Ignore "jsx-no-lambda" and disable it in your entire project by adding this to your tslint.json:

"rules": {
    "jsx-no-lambda": [ false ]
}

None of the alternatives in this thread actually avoid the performance issue this rule tries to avoid, but only wack-a-mole, or even make it worse with unnecessary complexity. Unless you're coding a ray-tracer this is just not a problem. Don't be afraid of lambdas in JSX, or just quit your React job and go drive Uber.

from tslint-react.

cytrowski avatar cytrowski commented on June 30, 2024 41

Hi guys :) I believe this rule is a big overkill if we consider most rendering cases. I understand making it forbidden to use lambda in iteration since making function in a loop is a bad practice anyway but assuming every user interface is a target of constant updates and rendering of huge lists is too much :)

However - if we really want to be purists then why not use data- attributes and make it event more performant in terms of memory usage:

class List extends Component {
  state = {
    items: [ { id: 1, title: 'Fizz' }, { id: 2, title: 'Buzz' } ]
  }
  handleClick = event => {
    // dataset elements are strings - that's why we parseInt here - consider it a compromise :)
    const itemId = parseInt(event.target.dataset.itemId, 10)
    this.setState({ items: this.state.items.filter(item => item.id !== itemId })
  }
  render() {
    return (
      <ul>
        { 
          this.state.items.map(
            item => (
              <li 
                key={item.id} 
                data-item-id={item.id} 
                onClick={this.handleClick}
              >
                {item.title}
              </li>
           ) 
        }
      </ul>
    )
  }
}

from tslint-react.

mohsen1 avatar mohsen1 commented on June 30, 2024 36

What I usually do is creating another component that accepts customer as prop.

function DeleteCostomerButton({customer, deleteCust}) {
  return <Btn onClick={() => deleteCust(customer)} />
}

that's more reacty

from tslint-react.

adidahiya avatar adidahiya commented on June 30, 2024 31

@johnsimons that's simply syntactic sugar. you are still creating a new lambda every time you call this.foo.

from tslint-react.

StokeMasterJack avatar StokeMasterJack commented on June 30, 2024 26

@IllusionMH I think you missed the main thing I am asking: about passing an arg to the event handler. The arg I am talking about is row-specific and part of a list. For example, in a list of customers like this like this:

customers.map( c => <Btn onClick={ () => this.deleteCust(c.id) } /> );

The only way I know of to pass the customer id (from the current row) to the deleteCust method is to either use a lambda (like in this example) or to use bind. Both techniques cause a new function object to be created on every render.

from tslint-react.

StokeMasterJack avatar StokeMasterJack commented on June 30, 2024 23

Now you are creating a whole new component with each render in a loop. I'm not so convinced that this represents a best practice.

Here is my take-away (unless presented with some new information): In my opinion, using a lambda (or a bind) in a jsx property (i.e. for an event handler) is a completely valid and appropriate use-case in certain situations: particularly inside of a loop when you need to pass arguments.

Here is a StackOverflow thread on the subject: Are lambda in JSX attributes an anti-pattern?

from tslint-react.

StokeMasterJack avatar StokeMasterJack commented on June 30, 2024 20

@mohsen1 but you still have a lambda as a jsx prop. Thus you will still get the warning, right?

from tslint-react.

IllusionMH avatar IllusionMH commented on June 30, 2024 14

@StokeMasterJack @mohsen1 as far as I know - this is only way to avoid function creation on each render in a loop, but still can be slower depending on actual structure

class DeleteByIdButton extends PureComponent {
    constructor(props) {
        super(props);
        this.handleDelete= this.handleDelete.bind(this) // remove  in case you are using decorators for binding
    }
    // @autobind  // uncomment in case you are using decorators for binding
    handleDelete() {
        this.props.onClick(this.props.id);
    }
    render() {
        return <Btn onClick={this.handleDelete}>Delete</Btn>
    }
}
// ...
users.map(u => <DeleteByIdButton id={u.id} onClick={deleteUserFunction} key={u.id}/>)

from tslint-react.

jameskuizon13 avatar jameskuizon13 commented on June 30, 2024 11

Just add "jsx-no-lambda": false in your tslint.json under the rules. Below is the snippet of my tslint.json

{ "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], "linterOptions": { "exclude": ["config/**/*.js", "node_modules/**/*.ts"] }, "rules": { "interface-name": [true, "never-prefix"], "no-console": false, "jsx-no-lambda": false } }

from tslint-react.

Bomber77 avatar Bomber77 commented on June 30, 2024 11

I use a high-order component to resolve the issue.
I want to use map method to create sub component in Steps, it a component in ant design. like below.

const SomeComponent = ({stepList}) => {  
  const handleClick = () => {
     //do something.
  }
  return (
    <div>
      <Steps>
        {
           stepList.map(step => (
              <Step onClick={handleClick}/>
           ))
        }
      </Steps>
    </div>
  )
}

I add a new high-order component named "StepWithData" to pass step into handleClick function, like this:

const StepWithData = (props) => {
  const {step, onClick, ...rest } = props;
  const onClickWithData = () => {
    onClick(step);
  }
  return(
    <Step onClick={onClickWithData} {...rest} />
  )
};

const SomeComponent = ({stepList}) => {  
  const handleClick = () => {
     //do something.
  }
  return (
    <div>
      <Steps>
        {
           stepList.map(step => (
              <StepWithData step={step} onClick={handleClick}/>
           ))
        }
      </Steps>
    </div>
  )
}

It work for me.

from tslint-react.

vlaja avatar vlaja commented on June 30, 2024 5

The rule itself is still enabled inside Typescript:recommended, and for me it's one of the best rules out there, because it allows people who recently started to code react to avoid perf bottlenecks.

Using a lambda inside the render method is definitively a bad pattern.

When you use a lambda inside the render, it will be recreated each time when the component is re-rendered. Meaning, that it will be recreated on each state / prop change.

However, if lambda is created inside the class itself, as it's private mehtod it will be instanced once, and just reused / re-binded when it is used with this.handleSomething ....

This is usually not a problem, but can sometimes be a huge difference, especially with longer component trees, and it's not simply syntactic sugar stuff.

Also, considering that in each render a lambda is re-created it will always differ from the one in the last pass, meaning that component or a whole component tree will probably re-render far more often they required, which is probably why this rule was created in the first place.

Use:

@IllusionMH example

If this is not possible for any reason, you can also achieve passing data with datasets, for example on elements which don't have a value prop (which serialise a DOM string map), and passing callback functions from parent to child components.

from tslint-react.

derrickbeining avatar derrickbeining commented on June 30, 2024 3

@StokeMasterJack (and @adidahiya , @IllusionMH , @mohsen1, @bezreyhan , @johnsimons ), I've been looking for an answer to this for a while. Found this thread while googling it (yet again). I'm not a TS user, but this is a general React concern, especially if trying to take advantage of React.PureComponent.

I've been trying to come up with a non-laborious and not-too-clever way of doing exactly what you're looking for: avoiding passing a function literal on props, particularly when rendering out components from a list of some data on state, where it almost feels impossible to avoid passing function expressions on props.

Here's the solution I've come up with (sans TS):

Instead of doing this:

class CatList extends React.Component {

  static propTypes = {
    kitties: PropTypes.arrayOf(PropTypes.instanceOf(Cat)),
  }

  render() {
    // ... other stuff ...

    // ... the problem:
    {this.props.kitties.map(kitty => {
      return <PureKittyCat key={kitty.id} onClick={ () => this.props.adoptKitty(kitty) } />

      // onClick is getting a new Function object every time CatList re-renders
      // causing PureKittyCat to re-render as well because its props have "changed".
      // This feels necessary bc we need to pass `kitty` to `this.props.adoptKitty`
    })}
  }
}

Do this:

class CatList extends React.Component {

  static propTypes = {
    kitties: PropTypes.arrayOf(PropTypes.instanceOf(Cat)),
  }

  // create a component-local cache to store instance methods you want to associate
  // with arbitrary entities on state. Use WeakMap to mitigate potential memory leaks:

  this.methodCache = new WeakMap()
 

  render() {
    // ... other stuff...

    // ... the good stuff:
    {this.props.kitties.map(kitty => {
      if ( !this.methodCache.has(kitty) ) {
        this.methodCache.set(kitty, {
          adopt: () => this.props.adoptKitty(kitty),
        })
      }
                          // as long is this is the same in-memory kitty, onClick will
                          // receive the same in-memory function object every render
                         // so prop won't change, avoiding a re-render in a PureComponent
      return <PureKittyCat key={kitty.id} onClick={this.methodCache.get(kitty).adopt} />
    })}
  }
}

I believe this is a viable solution, but if someone foresees a problem, please share how this may not work out the way I think it should. Or if anyone has a more straightforward approach, I'd love to hear alternatives. But I think this is way is pretty explicit and minimalistic.

Of course for this to work, you have to consistently follow the convention of treating all of your state immutably throughout your application. This could create some weird gotchas if you're ever mutating state/props directly.

from tslint-react.

alamothe avatar alamothe commented on June 30, 2024 3

Unless this rule can detect a loop, it's meaningless - there are no workarounds which are more performant - in fact this seems a case where it creates the opposite effect (people trying to circumvent the rule create less performant and/or less readable code)

In fact, this rule is disabled in TypeScript-React-Starter

from tslint-react.

vlaja avatar vlaja commented on June 30, 2024 3

@cytrowski It was a behaviour i observed in a multitude of my projects, but it might be something tied to Typescript (which I use on almost all of my projects) or my compiler configuration. I'll retest this and let you know.

As for the discussion in place. It seems that we have a lot of subjective opinions on the matter and I don't see it leading anywhere, as nobody is providing any real performance tests (shame on me aswell). For this reason I'm adding 2 articles that could shed some light on the matter.

Inline functions in React
https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578

Arrow function performance in class properties
https://medium.com/@charpeni/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think-3b3551c440b1

Both articles are property of their perspective authors, I just found them to widen my perception of the problem a bit, and even made me change my mind to this is a more of a case by case basis.

It seems to depend a lot on how many times the component actually updates and the fact that having simple events such as this.setState() as anonymous functions inline could make the bundle footprint smaller, which is often overlooked as a potential benefit.

from tslint-react.

IllusionMH avatar IllusionMH commented on June 30, 2024 3

Function components doesn't have shouldComponenUpdate lifecycle (if we aren't talking 16.7-alpha and hooks) and they will be re-rendered each time parent will be rendered (but underlying DOM won't be changed until it is necessary).

Probably this rule should be enabled by default only for PureComponent's where arrow functions will make default shouldComponentUpdate useless and will force constant re-renderings, but in case of SFC's it won't make any difference.

UPD. Under enabled for I mean to show error only of arrow function is prop of PureComponent.

@dacevedo12 if SFC is re-rendered each time than most likely render will be called on it's children. Unless you use PureComponent as child and pass arrow function as prop - it wont make any difference.

Please correct me if I'm wrong.

from tslint-react.

karfau avatar karfau commented on June 30, 2024 3

So many things have been said here, not sure if this idea has already been suggested (didn't find it when searching for it):
For me it sounds like this rule could be changed to (optionally) only trigger inside class components.

PS: For all the people that don't like this rule at all, just disable it in you config...

from tslint-react.

haggholm avatar haggholm commented on June 30, 2024 3

If one never uses lambdas in a jsx arg, how would one pass args to an event handler? For example:
<Foo onClick={ (event) => this.foo(23) }/>

myFunction => (var23) => (event) => console.log(var23, event)

<Foo onClick={myFunction(23)} />

Ultimately, though, it’s just a bit less clear with no genuine improvement, as it’s still creating a new closure every iteration; it’s only that it doesn’t look like a lambda in the JSX…

from tslint-react.

cytrowski avatar cytrowski commented on June 30, 2024 2

@carpben than you for the feedback :) I agree and in the same time I consider my solution a "premature optimization". Right now (since my last response was in August) I use event handler makers with memoization on the component level which is much more readable and can be fully statically typed. It gives me the similar performance and I find it easier to reason about too.

BTW Merry X-mass ;)

from tslint-react.

jessepinho avatar jessepinho commented on June 30, 2024 2

@cytrowski Ha, true — I guess I was thinking more of fixing the underlying performance problem, rather than just the linting issue. Honestly, the linting rule isn't probably the best. Been reading some articles (like this one from Ryan Florence) that seems to indicate there isn't much of a performance hit when using inline lambdas anyway.

from tslint-react.

naijopkr avatar naijopkr commented on June 30, 2024 2

If one never uses lambdas in a jsx arg, how would one pass args to an event handler? For example:

<Foo onClick={ (event) => this.foo(23) }/>

myFunction => (var23) => (event) => console.log(var23, event)

<Foo onClick={myFunction(23)} />

from tslint-react.

IllusionMH avatar IllusionMH commented on June 30, 2024 1

@StokeMasterJack rendering in a loop is really different case which is hard to guess from first post :)

  1. Use // tslint:disable-next-line jsx-no-lambda comment
  2. Create class component which will use bound method (will be more useful Foo is larger that one button).

IMHO Not sure that there is anything than can (or should) be corrected in this rule.

from tslint-react.

adidahiya avatar adidahiya commented on June 30, 2024 1

@StokeMasterJack I agree that in practice, you sometimes have to use lambdas in JSX properties and the performance cost is negligible. Despite that, I still think this lint rule is useful because it guards against sloppy code patterns in the majority of cases. In the few cases where you need to circumvent the rule you can simply use a // tslint:disable flag.

from tslint-react.

bezreyhan avatar bezreyhan commented on June 30, 2024 1

@StokeMasterJack I may be wrong on this, but with @IllusionMH example, you only instantiate the component once. When you use bind or lambdas you create n new lambdas on each render, n being the number of times you are looping. By creating a new component, you are also able to do further optimizations with shouldComponentUpdate.

Though, creating a new component is more boilerplate than having to write a new component class...

from tslint-react.

dacevedo12 avatar dacevedo12 commented on June 30, 2024 1

Also, how would you deal with this when using SFCs ? there's no this and also no .bind

from tslint-react.

SomethingSexy avatar SomethingSexy commented on June 30, 2024 1

@cytrowski especially since React seems to embrace the idea with Hooks. At least based on their documentation.

from tslint-react.

jessepinho avatar jessepinho commented on June 30, 2024 1

If tslint weren’t being deprecated, I’d advocate for a PR to remove this rule entirely. It just causes confusion, there are no clear benefits to it, and it’s causing people to develop workarounds (currying) that have no actual impact on performance since they’re still creating new lambdas each time.

I think it’s time to stop trying to fix this rule and simply stop using it.

from tslint-react.

StokeMasterJack avatar StokeMasterJack commented on June 30, 2024
  1. I just hard-coded 23 as an example. Sorry. I thought that was obvious.
  2. Wouldn't bind produce a similar warning as it too is creating a new function with each render?
  3. I believe it would trigger the "jsx-no-bind" rule.

from tslint-react.

IllusionMH avatar IllusionMH commented on June 30, 2024

@StokeMasterJack we are using class components with bindings in constructor in this case. React docs on handling events has same examples (see Toggle component example). In this case function is created and bound only once on instance and not on each re-render.

Moreover in TS you can use decorators for binding (e.g. first from search) or make own implementation like Office UI Fabric React did

from tslint-react.

icenold avatar icenold commented on June 30, 2024

bad pattern or good pattern in this case is very subjective.
I'm not going to create another component just to render a simple table.
Much more when my table has something like <td onClick="{()=>this.bar(item)}" >{item.name}<td>
where <td> is inside a <tr> rendered by Array.map(..)

from tslint-react.

andrewjboyd avatar andrewjboyd commented on June 30, 2024

I got @mohsen1 solution working with
<foo onClick={this.deleteClick.bind(this, id)} />
My deleteClick event looks like this (I am using TypeScript)
private deleteClick(word:number) { ... }

I also got this working when passing deleteClick through as a prop <foo onClick={this.props.deleteClick.bind(this, id)} />

Not sure if it's best practice, but it certainly got me running again

from tslint-react.

alamothe avatar alamothe commented on June 30, 2024

@andrewjboyd bind will create a lambda, so the "fix" is only to silence the linter, performance-wise it is the same as the vanilla version.

This goes back to how the main effect of this rule is to make people circumvent it by writing effectively the same (or worse) code in a different way, only to lose precious time in the process 😉

from tslint-react.

andrewjboyd avatar andrewjboyd commented on June 30, 2024

Fair call @alamothe... I'm new to React / TypeScript so I was very happy to finally see it working this morning, but I'll change my code to reflect the solution @IllusionMH put forward

from tslint-react.

Sharlaan avatar Sharlaan commented on June 30, 2024

How about exploiting event bubbling with all your child methods stacked on the list's container component, then catching the clicked child's id through the passed event.target object ?

example

from tslint-react.

vlaja avatar vlaja commented on June 30, 2024

@cytrowski Sure, datasets are a way to go. This is a great example.

You can also skip calling parseInt if you use object destructuring as it will cast the id to int automatically.

  handleClick = event => {
    const { itemId } = event.target.dataset;
    this.setState({ items: this.state.items.filter(item => item.id !== itemId })
  }

from tslint-react.

cytrowski avatar cytrowski commented on June 30, 2024

@jeff3dx - #96 (comment) - what is wrong with my example?

@vlaja - I'm not sure you are right with the casting in destructuring - is it documented somewhere?

from tslint-react.

janhesters avatar janhesters commented on June 30, 2024

@cytrowski I tried your approach in React Native with TypeScript like this:

handlePress = (event: GestureResponderEvent) => {
    this.props.toggleEmotion(event.target.dataset.item);
  };

  renderItem = ({ item }: { item: Emotion }) => (
    <ListItem
      title={item.name}
      bottomDivider={true}
      checkmark={item.chosen}
      checkmarkColor={activeColor}
      data-item={item}
      onPress={this.handlePress}
    />
  );

But it does not work. It throws the error: [ts] Property 'dataset' does not exist on type 'number'.

from tslint-react.

cytrowski avatar cytrowski commented on June 30, 2024

@janhesters I'm not experienced with TS and ReactNative but it looks like data- attributes behave differently there. I believe the GestureResponderEvent should be generic so that you can specify what kind of component can trigger that. Here the typing system assumes that target is a number which is already weird here. In the browser you can do it.

from tslint-react.

jeanbenitez avatar jeanbenitez commented on June 30, 2024

Hi all. In this article the author has a good approach to solve this issue:

https://medium.freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36

I hope this will be useful for all.

from tslint-react.

carpben avatar carpben commented on June 30, 2024

@cytrowski, though your solution is relevant and practical, personally I would avoid it. It kind of uses "truth in the dom", or at least it transfers data via the dom. The main reason we all use TS is to enable static type checking, which is lost once you transfer data via the dom. Besides the fact that it is considered a bad practice, conceptually I think of the dom the output of our JS/TS code, so it makes more sense to me to store information in actual JS/TS code.

from tslint-react.

jessepinho avatar jessepinho commented on June 30, 2024

@cytrowski Could you provide an example of memoization for the handler makers? Do you mean you're wrapping the entire component in React.memo?

from tslint-react.

cytrowski avatar cytrowski commented on June 30, 2024

Nope, it's much simpler than that :)

const memoize = (f, cache = {}) => x =>
  cache[x] ? cache[x] : (cache[x] = f(x))
class TaskList extends Component {
  // ...
  toggleTaskCompletion = memoize(taskId => () => doSthWithTask(taskId))
  // ...
  render() {
    return (
      <ul>
      {
         this.state.tasks.map( task => (
           <li 
             key={task.id}  
             onClick={toggleTaskCompletion(task.id)}
           >
             ....
           </li>
         )
      }
   )
  }

}

from tslint-react.

jessepinho avatar jessepinho commented on June 30, 2024

@cytrowski Ah, got it, thank you! But is that really worth the trouble? Why not just wrap the component inside React.memo(...)?

from tslint-react.

cytrowski avatar cytrowski commented on June 30, 2024

As far as I know .memo memoizes the whole component (works like a PureComponent class) so in this case it wont' help (fix me if I'm wrong). Anyway - I do use memoize only if the performance is really a thing and it's easy since you need to wrap only the "suboptimal" handler with it.

from tslint-react.

haggholm avatar haggholm commented on June 30, 2024

@cytrowski That’s a pretty neat solution, though it does come with a caveat: now all these allocated functions will sit around in your cache until the component is destroyed. Presumably not a problem in most cases, but in most cases these kinds of optimisations probably aren’t relevant anyway. On the other hand, if you have a big paginated table with millions of numerically indexed rows, you might end up with millions of cached lambdas…

from tslint-react.

cytrowski avatar cytrowski commented on June 30, 2024

@haggholm - that's true :) But for that case I suggest to embrace event delegation and add event listener on the parent of that list.

from tslint-react.

Webbrother avatar Webbrother commented on June 30, 2024

Seems the reason of issue is in definition of callback function on every render iteration.
Just define callback in separate variable and use it.

const clickHandler = (event) => console.log('clicked');
<Foo onClick={clickHandler}/>

or, in case if you use class based component

class MyComponent extends Component {
  constructor() {
     super()
     this.clickHandler.bind(this)
  }
  foo(val) {
    console.log(val);
  }
  clickHandler() {
    this.foo(23)
  }
  render() {
    return <Foo onClick={ this.clickHandler }/>
  }
}

from tslint-react.

cytrowski avatar cytrowski commented on June 30, 2024

@Webbrother - we are talking about the case when you need an extra, non-event-based param during the call (eg. item id - for some REST API call).

I think with React Hooks the whole discussion here becomes irrelevant anyway ;)

from tslint-react.

fadykstas avatar fadykstas commented on June 30, 2024

ES6 you should be able to use currying, eg:

<Foo onClick={ this.foo(23) }/>

foo = (event) => (id) => {

}

@johnsimons actually to work it should be vice versa:

<Foo onClick={ this.foo(23) }/>

foo = (id) => (event) => {

}

from tslint-react.

adidahiya avatar adidahiya commented on June 30, 2024

Closing due to deprecation, see #210

from tslint-react.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.