diff --git a/STYLE.md b/STYLE.md index eced40ceee9d..7a9fe40deed9 100644 --- a/STYLE.md +++ b/STYLE.md @@ -264,9 +264,7 @@ const {name, accountID, email} = data; **React Components** -- Avoid destructuring props and state at the *same time*. It makes the source of a given variable unclear. -- Avoid destructuring either props or state when there are other variables declared in a render block. This helps us quickly know which variables are from props, state, or declared inside the render. -- Use parameter destructuring for stateless function components when there are no additional variable declarations in the render. +Don't destructure props or state. It makes the source of a given variable unclear. This guideline helps us quickly know which variables are from props, state, or from some other scope. ```javascript // Bad @@ -276,12 +274,20 @@ render() { ... } -// Good +// Bad const UserInfo = ({name, email}) => ( -
-

Name: {name}

-

Email: {email}

-
+ + Name: {name} + Email: {email} + +); + +// Good +const UserInfo = props => ( + + Name: {props.name} + Email: {props.email} + ); ``` diff --git a/package-lock.json b/package-lock.json index 88665519ed6c..3feb54dd9837 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21989,9 +21989,9 @@ } }, "eslint-config-expensify": { - "version": "2.0.17", - "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.17.tgz", - "integrity": "sha512-m+7Rsz517u+jtkmN9MqMOhQ7cUSJo07Zx7Wt1mCwOw2DjC5DDhx7iiGE12oAtsmQTR+DHMyoUY3cAUXaakeh6w==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/eslint-config-expensify/-/eslint-config-expensify-2.0.18.tgz", + "integrity": "sha512-jEBAcXWm89NptiprvlBrRoDsK8zJbbkt8yVZOgzo/Vadp86gL0oc7blgyLfJHA2cjecs1kgfPYGByXb6V8v8cw==", "dev": true, "requires": { "@lwc/eslint-plugin-lwc": "^0.11.0", diff --git a/package.json b/package.json index 4942eec533b3..b72712930259 100644 --- a/package.json +++ b/package.json @@ -146,7 +146,7 @@ "electron-notarize": "^1.0.0", "electron-reloader": "^1.2.0", "eslint": "^7.6.0", - "eslint-config-expensify": "2.0.17", + "eslint-config-expensify": "^2.0.18", "eslint-loader": "^4.0.2", "eslint-plugin-detox": "^1.0.0", "eslint-plugin-jest": "^24.1.0", diff --git a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.js b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.js index 9291774806c2..295c6a6541c8 100644 --- a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.js +++ b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import React from 'react'; import {Pressable, StyleSheet} from 'react-native'; import lodashGet from 'lodash/get'; @@ -9,31 +10,22 @@ import {CONTEXT_MENU_TYPES} from '../../../pages/home/report/ContextMenu/Context import AttachmentView from '../../AttachmentView'; import fileDownload from '../../../libs/fileDownload'; - /* * This is a default anchor component for regular links. */ -const BaseAnchorForCommentsOnly = ({ - href, - rel, - target, - children, - style, - fileName, - ...props -}) => { +const BaseAnchorForCommentsOnly = (props) => { let linkRef; + const rest = _.omit(props, _.keys(propTypes)); return ( - props.isAttachment ? ( { - fileDownload(href, fileName); + fileDownload(props.href, props.fileName); }} > @@ -45,7 +37,7 @@ const BaseAnchorForCommentsOnly = ({ showContextMenu( CONTEXT_MENU_TYPES.LINK, event, - href, + props.href, lodashGet(linkRef, 'current'), ); } @@ -53,14 +45,17 @@ const BaseAnchorForCommentsOnly = ({ > linkRef = el} - style={StyleSheet.flatten(style)} + style={StyleSheet.flatten(props.style)} accessibilityRole="link" - href={href} - hrefAttrs={{rel, target}} - // eslint-disable-next-line react/jsx-props-no-spreading - {...props} + href={props.href} + hrefAttrs={{ + rel: props.rel, + target: props.target, + }} + // eslint-disable-next-line react/jsx-props-no-spreading + {...rest} > - {children} + {props.children} ) diff --git a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.native.js b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.native.js index 0890ba75b23c..49a3b581e27b 100644 --- a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.native.js +++ b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly/index.native.js @@ -1,3 +1,4 @@ +import _ from 'underscore'; import React from 'react'; import lodashGet from 'lodash/get'; import {Linking, StyleSheet, Pressable} from 'react-native'; @@ -13,27 +14,21 @@ import styles from '../../../styles/styles'; /* * This is a default anchor component for regular links. */ -const BaseAnchorForCommentsOnly = ({ - href, - children, - style, - isAttachment, - fileName, - ...props -}) => { +const BaseAnchorForCommentsOnly = (props) => { let linkRef; + const rest = _.omit(props, _.keys(propTypes)); return ( - isAttachment + props.isAttachment ? ( { - fileDownload(href, fileName); + fileDownload(props.href, props.fileName); }} > @@ -45,20 +40,20 @@ const BaseAnchorForCommentsOnly = ({ showContextMenu( CONTEXT_MENU_TYPES.LINK, event, - href, + props.href, lodashGet(linkRef, 'current'), ); } } - onPress={() => Linking.openURL(href)} + onPress={() => Linking.openURL(props.href)} > linkRef = el} - style={StyleSheet.flatten(style)} - // eslint-disable-next-line react/jsx-props-no-spreading - {...props} + style={StyleSheet.flatten(props.style)} + // eslint-disable-next-line react/jsx-props-no-spreading + {...rest} > - {children} + {props.children} ) diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index 0243be64389c..1dacf4b88c68 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -157,7 +157,7 @@ class AvatarWithImagePicker extends React.Component { } render() { - const {DefaultAvatar} = this.props; + const DefaultAvatar = this.props.DefaultAvatar; const additionalStyles = _.isArray(this.props.style) ? this.props.style : [this.props.style]; const indicatorStyles = [ diff --git a/src/components/BigNumberPad.js b/src/components/BigNumberPad.js index b373f07a36d9..26299c661b8f 100644 --- a/src/components/BigNumberPad.js +++ b/src/components/BigNumberPad.js @@ -17,7 +17,7 @@ const padNumbers = [ ['.', '0', '<'], ]; -const BigNumberPad = ({numberPressed}) => ( +const BigNumberPad = props => ( {_.map(padNumbers, (row, rowIndex) => ( @@ -30,7 +30,7 @@ const BigNumberPad = ({numberPressed}) => ( key={column} style={[styles.flex1, marginLeft]} text={column} - onPress={() => numberPressed(column)} + onPress={() => props.numberPressed(column)} /> ); })} diff --git a/src/components/Button.js b/src/components/Button.js index c62283607481..0e86f61e83f3 100644 --- a/src/components/Button.js +++ b/src/components/Button.js @@ -109,7 +109,7 @@ class Button extends Component { } renderContent() { - const {ContentComponent} = this.props; + const ContentComponent = this.props.ContentComponent; if (ContentComponent) { return ; } diff --git a/src/components/Checkbox.js b/src/components/Checkbox.js index afd939fa9a59..970363b193ee 100644 --- a/src/components/Checkbox.js +++ b/src/components/Checkbox.js @@ -24,22 +24,17 @@ const defaultProps = { disabled: false, }; -const Checkbox = ({ - isChecked, - onPress, - hasError, - disabled, -}) => ( +const Checkbox = props => ( onPress(!isChecked)} + disabled={props.disabled} + onPress={() => props.onPress(!props.isChecked)} > diff --git a/src/components/CheckboxWithLabel.js b/src/components/CheckboxWithLabel.js index db2d23ccb263..623bbce3af4a 100644 --- a/src/components/CheckboxWithLabel.js +++ b/src/components/CheckboxWithLabel.js @@ -38,26 +38,25 @@ const defaultProps = { errorText: '', }; -const CheckboxWithLabel = ({ - LabelComponent, isChecked, onPress, style, label, hasError, errorText, -}) => { +const CheckboxWithLabel = (props) => { + const LabelComponent = props.LabelComponent; const defaultStyles = [styles.flexRow, styles.alignItemsCenter]; - const wrapperStyles = _.isArray(style) ? [...defaultStyles, ...style] : [...defaultStyles, style]; + const wrapperStyles = _.isArray(props.style) ? [...defaultStyles, ...props.style] : [...defaultStyles, props.style]; - if (!label && !LabelComponent) { + if (!props.label && !LabelComponent) { throw new Error('Must provide at least label or LabelComponent prop'); } return ( <> onPress(!isChecked)} - label={label} - hasError={hasError} + isChecked={props.isChecked} + onPress={() => props.onPress(!props.isChecked)} + label={props.label} + hasError={props.hasError} /> onPress(!isChecked)} + onPress={() => props.onPress(!props.isChecked)} style={[ styles.ml3, styles.pr2, @@ -68,17 +67,17 @@ const CheckboxWithLabel = ({ styles.alignItemsCenter, ]} > - {label && ( + {props.label && ( - {label} + {props.label} )} {LabelComponent && ()} - {!_.isEmpty(errorText) && ( + {!_.isEmpty(props.errorText) && ( - {errorText} + {props.errorText} )} diff --git a/src/components/DatePicker/index.android.js b/src/components/DatePicker/index.android.js index cbc726688d1b..d4b2bfa4cf50 100644 --- a/src/components/DatePicker/index.android.js +++ b/src/components/DatePicker/index.android.js @@ -38,36 +38,25 @@ class DatePicker extends React.Component { } render() { - const { - value, - label, - placeholder, - hasError, - errorText, - translateX, - containerStyles, - disabled, - } = this.props; - - const dateAsText = value ? moment(value).format(CONST.DATE.MOMENT_FORMAT_STRING) : ''; + const dateAsText = this.props.value ? moment(this.props.value).format(CONST.DATE.MOMENT_FORMAT_STRING) : ''; return ( <> {this.state.isPickerVisible && ( diff --git a/src/components/DatePicker/index.ios.js b/src/components/DatePicker/index.ios.js index 127523b267cc..b29ae3b02bc9 100644 --- a/src/components/DatePicker/index.ios.js +++ b/src/components/DatePicker/index.ios.js @@ -64,32 +64,20 @@ class Datepicker extends React.Component { } render() { - const { - value, - label, - placeholder, - hasError, - errorText, - translateX, - containerStyles, - disabled, - } = this.props; - - const dateAsText = value ? moment(value).format(CONST.DATE.MOMENT_FORMAT_STRING) : ''; - + const dateAsText = this.props.value ? moment(this.props.value).format(CONST.DATE.MOMENT_FORMAT_STRING) : ''; return ( <> this.inputRef = input} onFocus={this.showDatepicker} - label={label} + label={this.props.label} onChangeText={this.raiseDateChange} defaultValue={this.defaultValue} - placeholder={placeholder} - hasError={hasError} - errorText={errorText} - containerStyles={containerStyles} - translateX={translateX} - disabled={disabled} + placeholder={this.props.placeholder} + hasError={this.props.hasError} + errorText={this.props.errorText} + containerStyles={this.props.containerStyles} + translateX={this.props.translateX} + disabled={this.props.disabled} /> ); } diff --git a/src/components/DisplayNames/index.native.js b/src/components/DisplayNames/index.native.js index a3862e5eb147..6ccb53888fd6 100644 --- a/src/components/DisplayNames/index.native.js +++ b/src/components/DisplayNames/index.native.js @@ -3,13 +3,9 @@ import {propTypes, defaultProps} from './displayNamesPropTypes'; import Text from '../Text'; // As we don't have to show tooltips of the Native platform so we simply render the full display names list. -const DisplayNames = ({ - fullTitle, - numberOfLines, - textStyles, -}) => ( - - {fullTitle} +const DisplayNames = props => ( + + {props.fullTitle} ); diff --git a/src/components/ExpensiPicker.js b/src/components/ExpensiPicker.js index bbb9db50b336..3f67d0352415 100644 --- a/src/components/ExpensiPicker.js +++ b/src/components/ExpensiPicker.js @@ -36,26 +36,24 @@ class ExpensiPicker extends PureComponent { } render() { - const { - label, isDisabled, ...pickerProps - } = this.props; + const pickerProps = _.omit(this.props, _.keys(propTypes)); return ( <> - {label && ( - {label} + {this.props.label && ( + {this.props.label} )} this.setState({isOpen: true})} onClose={() => this.setState({isOpen: false})} - disabled={isDisabled} + disabled={this.props.isDisabled} // eslint-disable-next-line react/jsx-props-no-spreading {...pickerProps} /> diff --git a/src/components/ExpensiTextInput/BaseExpensiTextInput.js b/src/components/ExpensiTextInput/BaseExpensiTextInput.js index b22eaa55b2b2..abfbfd1bcddd 100644 --- a/src/components/ExpensiTextInput/BaseExpensiTextInput.js +++ b/src/components/ExpensiTextInput/BaseExpensiTextInput.js @@ -146,28 +146,14 @@ class BaseExpensiTextInput extends Component { } render() { - const { - label, - value, - placeholder, - errorText, - hasError, - containerStyles, - inputStyle, - ignoreLabelTranslateX, - innerRef, - autoFocus, - multiline, - ...inputProps - } = this.props; - - const hasLabel = Boolean(label.length); + const inputProps = _.omit(this.props, _.keys(propTypes)); + const hasLabel = Boolean(this.props.label.length); return ( @@ -175,18 +161,18 @@ class BaseExpensiTextInput extends Component { style={[ styles.expensiTextInputContainer, this.state.isFocused && styles.borderColorFocus, - (hasError || errorText) && styles.borderColorDanger, + (this.props.hasError || this.props.errorText) && styles.borderColorDanger, ]} > {hasLabel ? ( <> {/* Adding this background to the label only for multiline text input, to prevent text overlaping with label when scrolling */} - {multiline && } + {this.props.multiline && } { - if (typeof innerRef === 'function') { innerRef(ref); } + if (typeof this.props.innerRef === 'function') { this.props.innerRef(ref); } this.input = ref; }} // eslint-disable-next-line {...inputProps} - value={value} - placeholder={(this.state.isFocused || !label) ? placeholder : null} + value={this.props.value} + placeholder={(this.state.isFocused || !this.props.label) ? this.props.placeholder : null} placeholderTextColor={themeColors.placeholderText} underlineColorAndroid="transparent" - style={[inputStyle, !hasLabel && styles.pv0]} - multiline={multiline} + style={[this.props.inputStyle, !hasLabel && styles.pv0]} + multiline={this.props.multiline} onFocus={this.onFocus} onBlur={this.onBlur} onChangeText={this.setValue} @@ -216,9 +202,9 @@ class BaseExpensiTextInput extends Component { - {!_.isEmpty(errorText) && ( + {!_.isEmpty(this.props.errorText) && ( - {errorText} + {this.props.errorText} )} diff --git a/src/components/ExpensiTextInput/ExpensiTextInputLabel/index.js b/src/components/ExpensiTextInput/ExpensiTextInputLabel/index.js index 88d5194a5f29..5293e90f7960 100644 --- a/src/components/ExpensiTextInput/ExpensiTextInputLabel/index.js +++ b/src/components/ExpensiTextInput/ExpensiTextInputLabel/index.js @@ -3,25 +3,20 @@ import {Animated} from 'react-native'; import styles from '../../../styles/styles'; import propTypes from './expensiTextInputLabelPropTypes'; -const ExpensiTextInputLabel = ({ - label, - labelTranslateY, - labelTranslateX, - labelScale, -}) => ( +const ExpensiTextInputLabel = props => ( - {label} + {props.label} ); diff --git a/src/components/ExpensiTextInput/ExpensiTextInputLabel/index.native.js b/src/components/ExpensiTextInput/ExpensiTextInputLabel/index.native.js index 082333f55254..8869584504e2 100644 --- a/src/components/ExpensiTextInput/ExpensiTextInputLabel/index.native.js +++ b/src/components/ExpensiTextInput/ExpensiTextInputLabel/index.native.js @@ -3,23 +3,18 @@ import {Animated} from 'react-native'; import styles from '../../../styles/styles'; import propTypes from './expensiTextInputLabelPropTypes'; -const ExpensiTextInputLabel = ({ - label, - labelTranslateX, - labelTranslateY, - labelScale, -}) => ( +const ExpensiTextInputLabel = props => ( - {label} + {props.label} ); diff --git a/src/components/FAB/index.ios.js b/src/components/FAB/index.ios.js index 863ac8f9fac0..3ebc24dcd62d 100644 --- a/src/components/FAB/index.ios.js +++ b/src/components/FAB/index.ios.js @@ -7,9 +7,9 @@ import FAB from './FAB'; import fabPropTypes from './fabPropTypes'; // KeyboardAvoidingView only need in IOS so that's the reason make platform specific FAB component. -const Fab = ({onPress, isActive}) => ( +const Fab = props => ( - + ); diff --git a/src/components/FormAlertWithSubmitButton.js b/src/components/FormAlertWithSubmitButton.js index 077814e11f2c..d7db46120845 100644 --- a/src/components/FormAlertWithSubmitButton.js +++ b/src/components/FormAlertWithSubmitButton.js @@ -51,48 +51,37 @@ const defaultProps = { isLoading: false, }; -const FormAlertWithSubmitButton = ({ - isAlertVisible, - isDisabled, - onSubmit, - buttonText, - translate, - onFixTheErrorsLinkPressed, - message, - isMessageHtml, - containerStyles, - isLoading, -}) => { +const FormAlertWithSubmitButton = (props) => { /** * @returns {React.Component} */ function getAlertPrompt() { let error = ''; - if (!_.isEmpty(message)) { - if (isMessageHtml) { + if (!_.isEmpty(props.message)) { + if (props.isMessageHtml) { error = ( - ${message}`} /> + ${props.message}`} /> ); } else { error = ( - {message} + {props.message} ); } } else { error = ( <> - {`${translate('common.please')} `} + {`${props.translate('common.please')} `} - {translate('common.fixTheErrors')} + {props.translate('common.fixTheErrors')} - {` ${translate('common.inTheFormBeforeContinuing')}.`} + {` ${props.translate('common.inTheFormBeforeContinuing')}.`} ); @@ -106,8 +95,8 @@ const FormAlertWithSubmitButton = ({ } return ( - - {isAlertVisible && ( + + {props.isAlertVisible && ( {getAlertPrompt()} @@ -116,10 +105,10 @@ const FormAlertWithSubmitButton = ({