Avoid useless renders (#3141)
* feat(eslint): Set react/jsx-no-bind: error * refactor(notifications/setting_toggle): Do not use bind * refactor(components/dropdown_menu): Do not use bind * refactor(components/autosuggest_textarea): Do not use bind * refactor(compose/privacy_dropdown): Do not use bind * refactor(compose/upload_form): Do not use bind * refactor(components/status): Do not use bind * refactor(components/onboarding_modal): Do not use bind * refactor: PR feedback * chore(notifications/setting_toggle): Lint * refactor: PR feedback
This commit is contained in:
		
							parent
							
								
									3da521a586
								
							
						
					
					
						commit
						1548695c83
					
				
					 8 changed files with 68 additions and 41 deletions
				
			
		| 
						 | 
				
			
			@ -49,6 +49,7 @@ rules:
 | 
			
		|||
  no-trailing-spaces: warn
 | 
			
		||||
 | 
			
		||||
  react/jsx-wrap-multilines: error
 | 
			
		||||
  react/jsx-no-bind: error
 | 
			
		||||
  react/self-closing-comp: error
 | 
			
		||||
  react/prop-types: error
 | 
			
		||||
  react/no-multi-comp: off
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -145,7 +145,8 @@ class AutosuggestTextarea extends ImmutablePureComponent {
 | 
			
		|||
    }, 100);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onSuggestionClick (suggestion, e) {
 | 
			
		||||
  onSuggestionClick (e) {
 | 
			
		||||
    const suggestion = Number(e.currentTarget.getAttribute('data-index'));
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion);
 | 
			
		||||
    this.textarea.focus();
 | 
			
		||||
| 
						 | 
				
			
			@ -204,8 +205,9 @@ class AutosuggestTextarea extends ImmutablePureComponent {
 | 
			
		|||
              role='button'
 | 
			
		||||
              tabIndex='0'
 | 
			
		||||
              key={suggestion}
 | 
			
		||||
              data-index={suggestion}
 | 
			
		||||
              className={`autosuggest-textarea__suggestions__item ${i === selectedSuggestion ? 'selected' : ''}`}
 | 
			
		||||
              onClick={this.onSuggestionClick.bind(this, suggestion)}>
 | 
			
		||||
              onClick={this.onSuggestionClick}>
 | 
			
		||||
              <AutosuggestAccountContainer id={suggestion} />
 | 
			
		||||
            </div>
 | 
			
		||||
          ))}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,7 +24,8 @@ class DropdownMenu extends React.PureComponent {
 | 
			
		|||
    this.dropdown = c;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleClick = (i, e) => {
 | 
			
		||||
  handleClick = (e) => {
 | 
			
		||||
    const i = Number(e.currentTarget.getAttribute('data-index'));
 | 
			
		||||
    const { action } = this.props.items[i];
 | 
			
		||||
 | 
			
		||||
    if (typeof action === 'function') {
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +44,7 @@ class DropdownMenu extends React.PureComponent {
 | 
			
		|||
 | 
			
		||||
    return (
 | 
			
		||||
      <li className='dropdown__content-list-item' key={ text + i }>
 | 
			
		||||
        <a href={href} target='_blank' rel='noopener' onClick={this.handleClick.bind(this, i)} className='dropdown__content-list-link'>
 | 
			
		||||
        <a href={href} target='_blank' rel='noopener' onClick={this.handleClick} data-index={i} className='dropdown__content-list-link'>
 | 
			
		||||
          {text}
 | 
			
		||||
        </a>
 | 
			
		||||
      </li>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,8 +43,9 @@ class Status extends ImmutablePureComponent {
 | 
			
		|||
    this.context.router.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleAccountClick = (id, e) => {
 | 
			
		||||
  handleAccountClick = (e) => {
 | 
			
		||||
    if (e.button === 0) {
 | 
			
		||||
      const id = Number(e.currentTarget.getAttribute('data-id'));
 | 
			
		||||
      e.preventDefault();
 | 
			
		||||
      this.context.router.push(`/accounts/${id}`);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +73,7 @@ class Status extends ImmutablePureComponent {
 | 
			
		|||
        <div className='status__wrapper'>
 | 
			
		||||
          <div className='status__prepend'>
 | 
			
		||||
            <div className='status__prepend-icon-wrapper'><i className='fa fa-fw fa-retweet status__prepend-icon' /></div>
 | 
			
		||||
            <FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick.bind(this, status.getIn(['account', 'id']))} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong dangerouslySetInnerHTML={displayNameHTML} /></a> }} />
 | 
			
		||||
            <FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name muted'><strong dangerouslySetInnerHTML={displayNameHTML} /></a> }} />
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <Status {...other} wrapped={true} status={status.get('reblog')} account={status.get('account')} />
 | 
			
		||||
| 
						 | 
				
			
			@ -103,7 +104,7 @@ class Status extends ImmutablePureComponent {
 | 
			
		|||
            <a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <a onClick={this.handleAccountClick.bind(this, status.getIn(['account', 'id']))} href={status.getIn(['account', 'url'])} className='status__display-name'>
 | 
			
		||||
          <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name'>
 | 
			
		||||
            <div className='status__avatar'>
 | 
			
		||||
              {statusAvatar}
 | 
			
		||||
            </div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,7 +36,8 @@ class PrivacyDropdown extends React.PureComponent {
 | 
			
		|||
    this.setState({ open: !this.state.open });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleClick = (value, e) => {
 | 
			
		||||
  handleClick = (e) => {
 | 
			
		||||
    const value = e.currentTarget.getAttribute('data-index');
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    this.setState({ open: false });
 | 
			
		||||
    this.props.onChange(value);
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +81,7 @@ class PrivacyDropdown extends React.PureComponent {
 | 
			
		|||
        <div className='privacy-dropdown__value'><IconButton className='privacy-dropdown__value-icon' icon={valueOption.icon} title={intl.formatMessage(messages.change_privacy)} size={18} active={open} inverted onClick={this.handleToggle} style={iconStyle}/></div>
 | 
			
		||||
        <div className='privacy-dropdown__dropdown'>
 | 
			
		||||
          {options.map(item =>
 | 
			
		||||
            <div role='button' tabIndex='0' key={item.value} onClick={this.handleClick.bind(this, item.value)} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}>
 | 
			
		||||
            <div role='button' tabIndex='0' key={item.value} data-index={item.value} onClick={this.handleClick} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}>
 | 
			
		||||
              <div className='privacy-dropdown__option__icon'><i className={`fa fa-fw fa-${item.icon}`} /></div>
 | 
			
		||||
              <div className='privacy-dropdown__option__content'>
 | 
			
		||||
                <strong>{item.shortText}</strong>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,6 +18,11 @@ class UploadForm extends React.PureComponent {
 | 
			
		|||
    intl: PropTypes.object.isRequired
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  onRemoveFile = (e) => {
 | 
			
		||||
    const id = Number(e.currentTarget.parentElement.getAttribute('data-id'));
 | 
			
		||||
    this.props.onRemoveFile(id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { intl, media } = this.props;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -25,8 +30,8 @@ class UploadForm extends React.PureComponent {
 | 
			
		|||
      <div className='compose-form__upload' key={attachment.get('id')}>
 | 
			
		||||
        <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}>
 | 
			
		||||
          {({ scale }) =>
 | 
			
		||||
            <div className='compose-form__upload-thumbnail' style={{ transform: `translateZ(0) scale(${scale})`, backgroundImage: `url(${attachment.get('preview_url')})` }}>
 | 
			
		||||
              <IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.props.onRemoveFile.bind(this, attachment.get('id'))} />
 | 
			
		||||
            <div className='compose-form__upload-thumbnail' data-id={attachment.get('id')} style={{ transform: `translateZ(0) scale(${scale})`, backgroundImage: `url(${attachment.get('preview_url')})` }}>
 | 
			
		||||
              <IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.onRemoveFile} />
 | 
			
		||||
            </div>
 | 
			
		||||
          }
 | 
			
		||||
        </Motion>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,19 +3,31 @@ import PropTypes from 'prop-types';
 | 
			
		|||
import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
			
		||||
import Toggle from 'react-toggle';
 | 
			
		||||
 | 
			
		||||
const SettingToggle = ({ settings, settingKey, label, onChange, htmlFor = '' }) => (
 | 
			
		||||
  <label htmlFor={htmlFor} className='setting-toggle__label'>
 | 
			
		||||
    <Toggle checked={settings.getIn(settingKey)} onChange={(e) => onChange(settingKey, e.target.checked)} />
 | 
			
		||||
    <span className='setting-toggle'>{label}</span>
 | 
			
		||||
  </label>
 | 
			
		||||
);
 | 
			
		||||
class SettingToggle extends React.PureComponent {
 | 
			
		||||
 | 
			
		||||
SettingToggle.propTypes = {
 | 
			
		||||
  settings: ImmutablePropTypes.map.isRequired,
 | 
			
		||||
  settingKey: PropTypes.array.isRequired,
 | 
			
		||||
  label: PropTypes.node.isRequired,
 | 
			
		||||
  onChange: PropTypes.func.isRequired,
 | 
			
		||||
  htmlFor: PropTypes.string
 | 
			
		||||
};
 | 
			
		||||
  static propTypes = {
 | 
			
		||||
    settings: ImmutablePropTypes.map.isRequired,
 | 
			
		||||
    settingKey: PropTypes.array.isRequired,
 | 
			
		||||
    label: PropTypes.node.isRequired,
 | 
			
		||||
    onChange: PropTypes.func.isRequired,
 | 
			
		||||
    htmlFor: PropTypes.string
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onChange = (e) => {
 | 
			
		||||
    this.props.onChange(this.props.settingKey, e.target.checked);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
    const { settings, settingKey, label, onChange, htmlFor = '' } = this.props;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <label htmlFor={htmlFor} className='setting-toggle__label'>
 | 
			
		||||
        <Toggle checked={settings.getIn(settingKey)} onChange={this.onChange} />
 | 
			
		||||
        <span className='setting-toggle'>{label}</span>
 | 
			
		||||
      </label>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default SettingToggle;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,6 +11,8 @@ import NavigationBar from '../../compose/components/navigation_bar';
 | 
			
		|||
import ColumnHeader from './column_header';
 | 
			
		||||
import Immutable from 'immutable';
 | 
			
		||||
 | 
			
		||||
const noop = () => { };
 | 
			
		||||
 | 
			
		||||
const messages = defineMessages({
 | 
			
		||||
  home_title: { id: 'column.home', defaultMessage: 'Home' },
 | 
			
		||||
  notifications_title: { id: 'column.notifications', defaultMessage: 'Notifications' },
 | 
			
		||||
| 
						 | 
				
			
			@ -48,14 +50,14 @@ const PageTwo = ({ me }) => (
 | 
			
		|||
        suggestions={Immutable.List()}
 | 
			
		||||
        mentionedDomains={[]}
 | 
			
		||||
        spoiler={false}
 | 
			
		||||
        onChange={() => {}}
 | 
			
		||||
        onSubmit={() => {}}
 | 
			
		||||
        onPaste={() => {}}
 | 
			
		||||
        onPickEmoji={() => {}}
 | 
			
		||||
        onChangeSpoilerText={() => {}}
 | 
			
		||||
        onClearSuggestions={() => {}}
 | 
			
		||||
        onFetchSuggestions={() => {}}
 | 
			
		||||
        onSuggestionSelected={() => {}}
 | 
			
		||||
        onChange={noop}
 | 
			
		||||
        onSubmit={noop}
 | 
			
		||||
        onPaste={noop}
 | 
			
		||||
        onPickEmoji={noop}
 | 
			
		||||
        onChangeSpoilerText={noop}
 | 
			
		||||
        onClearSuggestions={noop}
 | 
			
		||||
        onFetchSuggestions={noop}
 | 
			
		||||
        onSuggestionSelected={noop}
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -72,10 +74,10 @@ const PageThree = ({ me, domain }) => (
 | 
			
		|||
    <div className='figure non-interactive'>
 | 
			
		||||
      <Search
 | 
			
		||||
        value=''
 | 
			
		||||
        onChange={() => {}}
 | 
			
		||||
        onSubmit={() => {}}
 | 
			
		||||
        onClear={() => {}}
 | 
			
		||||
        onShow={() => {}}
 | 
			
		||||
        onChange={noop}
 | 
			
		||||
        onSubmit={noop}
 | 
			
		||||
        onClear={noop}
 | 
			
		||||
        onShow={noop}
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <div className='pseudo-drawer'>
 | 
			
		||||
| 
						 | 
				
			
			@ -182,12 +184,14 @@ class OnboardingModal extends React.PureComponent {
 | 
			
		|||
    this.props.onClose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleDot = (i, e) => {
 | 
			
		||||
  handleDot = (e) => {
 | 
			
		||||
    const i = Number(e.currentTarget.getAttribute('data-index'));
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
    this.setState({ currentIndex: i });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleNext = (maxNum, e) => {
 | 
			
		||||
  handleNext = (e) => {
 | 
			
		||||
    const maxNum = Number(e.currentTarget.getAttribute('data-length'));
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
 | 
			
		||||
    if (this.state.currentIndex < maxNum - 1) {
 | 
			
		||||
| 
						 | 
				
			
			@ -214,9 +218,9 @@ class OnboardingModal extends React.PureComponent {
 | 
			
		|||
    let nextOrDoneBtn;
 | 
			
		||||
 | 
			
		||||
    if(hasMore) {
 | 
			
		||||
      nextOrDoneBtn = <a href='#' onClick={this.handleNext.bind(null, pages.length)} className='onboarding-modal__nav onboarding-modal__next'><FormattedMessage id='onboarding.next' defaultMessage='Next' /></a>;
 | 
			
		||||
      nextOrDoneBtn = <a href='#' data-length={pages.length} onClick={this.handleNext} className='onboarding-modal__nav onboarding-modal__next'><FormattedMessage id='onboarding.next' defaultMessage='Next' /></a>;
 | 
			
		||||
    } else {
 | 
			
		||||
      nextOrDoneBtn = <a href='#' onClick={this.handleNext.bind(null, pages.length)} className='onboarding-modal__nav onboarding-modal__done'><FormattedMessage id='onboarding.done' defaultMessage='Done' /></a>;
 | 
			
		||||
      nextOrDoneBtn = <a href='#' data-length={pages.length} onClick={this.handleNext} className='onboarding-modal__nav onboarding-modal__done'><FormattedMessage id='onboarding.done' defaultMessage='Done' /></a>;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const styles = pages.map((page, i) => ({
 | 
			
		||||
| 
						 | 
				
			
			@ -242,7 +246,7 @@ class OnboardingModal extends React.PureComponent {
 | 
			
		|||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div className='onboarding-modal__dots'>
 | 
			
		||||
            {pages.map((_, i) => <div key={i} role='button' tabIndex='0' onClick={this.handleDot.bind(null, i)} className={`onboarding-modal__dot ${i === currentIndex ? 'active' : ''}`} />)}
 | 
			
		||||
            {pages.map((_, i) => <div key={i} role='button' tabIndex='0' data-index={i} onClick={this.handleDot} className={`onboarding-modal__dot ${i === currentIndex ? 'active' : ''}`} />)}
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue