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

Performance: Update usage of stateless, functional components to class-based components when event handlers are used #607

Closed
14 tasks done
jeffcarbs opened this issue Oct 3, 2016 · 9 comments

Comments

@jeffcarbs
Copy link
Member

jeffcarbs commented Oct 3, 2016

See below for original conversation regarding "RFC: Memoize event handlers in stateless, functional components". These are the TODOs that came out of the discussion.

Functional components that need to be turned into a class (Found via grep: const handle):

Other places where functions are created within render (Found via grep: const on[A-Z]):

There may be others so feel free to update this list!


Original discussion below regarding "RFC: Memoize event handlers in stateless, functional components"


Most of the conversation around this is in reference to actual components, not stateless functions. I've googled around a bit and the closest thing I've found to a discussion on this topic is: airbnb/javascript#784. This is also more of a general React question for you guys. I've run into this in my projects and haven't really settled on a great way to handle it.


I think using stateless, functional components is ideal and something we should strive for. However, initializing new arrays or functions inside a render (which we do) is considered an anti-pattern for performance reasons. Consider the DropdownItem component (pasting a simplified version here):

function DropdownItem(props) {
  const { value } = props

  const handleClick = (e) => {
    if (onClick) onClick(e, value)
  }

  return (
    <ElementType {...rest} className={classes} onClick={handleClick}>
      {/** content */}
    </ElementType>
  )
}

In this case, handleClick will be a new function each time this function is called so the props for the underlying element are changing.

One idea to solve this: Doesn't work, see below.

const handleClick = _.memoize((props) => (e) => {
  const { onClick, value } = props
  if (onClick) onClick(e, value)
})

function DropdownItem(props) {
  return (
    <ElementType {...rest} className={classes} onClick={handleClick(props)}>
      {/** content */}
    </ElementType>
  )
}

This would mean that a new function would be created any time the props changed but that would be way less frequent than on every single render. Trying to get more granular (e.g. using onClick as a memoization key) would probably lead to hidden gotchas.

This actually doesn't work since props will be a new object each time so the memoization comparison won't work.


Definitely open to other ideas here!

@jeffcarbs jeffcarbs added the RFC label Oct 3, 2016
@levithomason
Copy link
Member

In the linked issue, it is noted that the linter suggesting to use a stateless component when there are instance methods is a bug. It goes on to suggest that having an event handler is actually having a stateful component, because this is the state. I can see that since the event handler is data and is attached to the component.

So, we could just use class components when there are handlers.

Because we need to pass prop values to the handler, I don't see a sane way around creating the function every time. There is a somewhat insane option IMO. Technically, you could use mutation and side-effects to avoid recreating the function, though I'm thinking it is worse than using a class component:

let value
let onClick

const handleClick = (e) => {
  if (onClick) onClick(e, value)
}

function DropdownItem(props) {
  value = props.value
  onClick = props.onClick

  return (
    <ElementType {...rest} className={classes} onClick={handleClick}>
      {/* content */}
    </ElementType>
  )
}

@jeffcarbs
Copy link
Member Author

😟 that's a little rough haha. I'm also not sure it would work. handleClick is only created once and references "global" (within the scope of the file) variables value and onClick. Those variables are set to a new value on every render, so when handleClick is invoked the onClick and value will be that of the last DropdownItem rendered.

It goes on to suggest that having an event handler is actually having a stateful component, because this is the state. I can see that since the event handler is data and is attached to the component.

That makes a lot of sense to me as well. IMHO we should strive to make this library as efficient as possible so I think it's worth it to make these class-based components when there are event handlers involved.

If you agree, I'm happy to figure out which components need updating and add them as a checklist at the top of this issue.

@layershifter
Copy link
Member

So, we could just use class components when there are handlers.

Looks like best possible solution.

@jeffcarbs jeffcarbs changed the title RFC: Memoize event handlers in stateless, functional components Performance: Update usage of stateless, functional components to class-based components when event handlers are used Oct 3, 2016
@levithomason
Copy link
Member

Agreed, classes it is

@levithomason
Copy link
Member

So close on this!

@layershifter
Copy link
Member

Almost year and it's done 😄

@levithomason
Copy link
Member

Haha, I was just looking at this one... "So close..." 😄

@carkod
Copy link
Contributor

carkod commented Sep 27, 2022

Isn't it better to use react hooks now?

@layershifter
Copy link
Member

image

@carkod It was 2016... Now there are React hooks indeed.

@Semantic-Org Semantic-Org locked as resolved and limited conversation to collaborators Sep 27, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants