diff options
author | Sorin Davidoi <sorin.davidoi@gmail.com> | 2017-05-19 20:58:12 +0200 |
---|---|---|
committer | Eugen Rochko <eugen@zeonfederated.com> | 2017-05-19 20:58:12 +0200 |
commit | 1548695c8300618d44efa8785f5c6eb7b3a86917 (patch) | |
tree | 52a60cdccd40bc0961f1af0915c300316c92925f | |
parent | 3da521a58663f94b6e6a74d6d91d72a8e9fa7a73 (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
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> |