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
					
				| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user