about summary refs log tree commit diff
diff options
context:
space:
mode:
authorSorin Davidoi <sorin.davidoi@gmail.com>2017-05-19 20:58:12 +0200
committerEugen Rochko <eugen@zeonfederated.com>2017-05-19 20:58:12 +0200
commit1548695c8300618d44efa8785f5c6eb7b3a86917 (patch)
tree52a60cdccd40bc0961f1af0915c300316c92925f
parent3da521a58663f94b6e6a74d6d91d72a8e9fa7a73 (diff)
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
-rw-r--r--.eslintrc.yml1
-rw-r--r--app/javascript/mastodon/components/autosuggest_textarea.js6
-rw-r--r--app/javascript/mastodon/components/dropdown_menu.js5
-rw-r--r--app/javascript/mastodon/components/status.js7
-rw-r--r--app/javascript/mastodon/features/compose/components/privacy_dropdown.js5
-rw-r--r--app/javascript/mastodon/features/compose/components/upload_form.js9
-rw-r--r--app/javascript/mastodon/features/notifications/components/setting_toggle.js38
-rw-r--r--app/javascript/mastodon/features/ui/components/onboarding_modal.js38
8 files changed, 68 insertions, 41 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml
index aa23bebbf..12bdf342a 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -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
diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js
index b3d62ec3a..24fa2b920 100644
--- a/app/javascript/mastodon/components/autosuggest_textarea.js
+++ b/app/javascript/mastodon/components/autosuggest_textarea.js
@@ -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>
           ))}
diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js
index 99432463c..4b6168f43 100644
--- a/app/javascript/mastodon/components/dropdown_menu.js
+++ b/app/javascript/mastodon/components/dropdown_menu.js
@@ -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>
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index d2049c44a..bd62a1e28 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -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>
diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
index 22bbe68ad..1e0bb3d09 100644
--- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
+++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js
@@ -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>
diff --git a/app/javascript/mastodon/features/compose/components/upload_form.js b/app/javascript/mastodon/features/compose/components/upload_form.js
index f1bd9b7ec..f2579bf60 100644
--- a/app/javascript/mastodon/features/compose/components/upload_form.js
+++ b/app/javascript/mastodon/features/compose/components/upload_form.js
@@ -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>
diff --git a/app/javascript/mastodon/features/notifications/components/setting_toggle.js b/app/javascript/mastodon/features/notifications/components/setting_toggle.js
index 080804a40..804049531 100644
--- a/app/javascript/mastodon/features/notifications/components/setting_toggle.js
+++ b/app/javascript/mastodon/features/notifications/components/setting_toggle.js
@@ -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;
diff --git a/app/javascript/mastodon/features/ui/components/onboarding_modal.js b/app/javascript/mastodon/features/ui/components/onboarding_modal.js
index 889ecaaff..d83a3d5c4 100644
--- a/app/javascript/mastodon/features/ui/components/onboarding_modal.js
+++ b/app/javascript/mastodon/features/ui/components/onboarding_modal.js
@@ -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>