Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[No QA] No Props/State Destructuring #6234

Merged
merged 10 commits into from
Nov 8, 2021
Merged

Conversation

roryabraham
Copy link
Contributor

@roryabraham roryabraham commented Nov 5, 2021

Details

Implements a new ESLint rule that bans destructuring of props and/or state.

Fixed Issues

$ #6250
Coming from #5984 (comment)

Tests / QA Steps

Just regression testing. Try to display every view in the app. Keep the JS console open and verify that there are no errors.

Tested On

  • Web
  • Mobile Web
  • Desktop
  • iOS
  • Android

@roryabraham roryabraham self-assigned this Nov 5, 2021
@roryabraham roryabraham requested a review from a team as a code owner November 5, 2021 03:41
@MelvinBot MelvinBot requested review from nkuoch and removed request for a team November 5, 2021 03:42
@roryabraham roryabraham changed the title [No QA] No Props/State Destructuring [HOLD][No QA] No Props/State Destructuring Nov 5, 2021
@roryabraham
Copy link
Contributor Author

Putting this on HOLD for Expensify/eslint-config-expensify#36 and #6174

Copy link
Contributor

@marcaaron marcaaron left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Went about 25% in and just wanted to ask about a few higher level things before continuing.

src/components/AvatarWithImagePicker.js Outdated Show resolved Hide resolved
const {
label, isDisabled, ...pickerProps
} = this.props;
const pickerProps = _.omit(this.props, _.keys(propTypes));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB, could maybe opt to be more explicit here vs. depending propTypes? We are creating a dependency where changing the propTypes will have a side effect of altering the pickerProps and not sure if that will be obvious to anyone modifying this (also might make zero difference so 🤷).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like propTypes is meant to be the main source of truth for the prop types for a component so I personally feel like this pattern is fine. Open to other ideas too, but it feels odd/unnecessary to duplicate all the props in the _.omit as they're already declared in propTypes

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I will just leave you with the thought where someone does not remember to add something to propTypes and unintentionally passes that prop in pickerProps. Again, might not be a problem, but the way to prevent it before was clearer i.e. anything not de-structured would be passed and we are changing this to anything not included in propTypes will be passed - which just feels less obvious.

} = this.props;

const hasLabel = Boolean(label.length);
const inputProps = _.omit(this.props, _.keys(propTypes));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing this again and maybe it's better to pick out which ones we do want to include in inputProps?

@roryabraham
Copy link
Contributor Author

So the biggest thing it seems we need to agree upon is the pattern to follow for components that use some props, and pass others via a prop spread to a child component. And here are the options as I see them:

  1. Suppress the eslint rule and use the spread operator to omit the props we need to use in the parent

    // eslint-disable-next-line react/destructuring-assignment
    const {
        someProp,
        someOtherProp,
        ...propsToDrill,
    } = props;
    
    return (
        <InnerComponent
             // eslint-disable-next-line react/jsx-props-no-spreading
             {...propsToDrill}
             someValue={someProp ? 'a' : 'b'}
             someOtherValue={10 * someOtherProp}
        >
    );
  2. Explicitly omit the props we need to use in the parent

    const propsToDrill = _.omit(props, ['someProp', 'someOtherProp']);
    return (
        <InnerComponent
             // eslint-disable-next-line react/jsx-props-no-spreading
             {...propsToDrill}
             someValue={props.someProp ? 'a' : 'b'}
             someOtherValue={10 * props.someOtherProp}
        >
    );
  3. Explicitly declare the set of props we will drill to the child

    return (
        <InnerComponent
             someValue={props.someProp ? 'a' : 'b'}
             someOtherValue={10 * props.someOtherProp}
             someChildProp={props.someChildProp}
             someOtherChildProp={props.someOtherChildProp}
        >
    );

So each of these approaches have pros and cons. While 3 at first seems like it would be the most ideal, it's not very practical because it doesn't allow us to define wrapper components that take arbitrary other props and drill those to the wrapped child. So every prop that we could possibly want to pass through to a Button or Text would need to be declared in the ExpensiButton or ExpensiText, respectively, which is not great.

So between 1 and 2, I think I prefer 2 because it's a bit more explicit, and more importantly our props used in the parent component get to retain their props. prefix.

Thoughts?

@marcaaron
Copy link
Contributor

Agree that 2 is the best option, but also was thinking we could use _.pick() instead of _.omit() so you still have to be a bit more explicit, but maybe it's fine to be passive here (I'm mostly worried about using the keys from propTypes). NAB for me really just came to mind :)

# Conflicts:
#	package.json
#	src/components/LocalePicker.js
#	src/pages/NotFound.js
#	src/pages/ReimbursementAccount/IdentityForm.js
@roryabraham roryabraham changed the title [HOLD][No QA] No Props/State Destructuring [No QA] No Props/State Destructuring Nov 8, 2021
@roryabraham
Copy link
Contributor Author

Taking this off hold, should be ready for review now. Thanks!

@marcaaron marcaaron merged commit b88455e into main Nov 8, 2021
@marcaaron marcaaron deleted the Rory-NoPropsDestructuring branch November 8, 2021 16:18
@OSBotify
Copy link
Contributor

OSBotify commented Nov 8, 2021

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@Gonals Gonals mentioned this pull request Nov 8, 2021
5 tasks
@MonilBhavsar
Copy link
Contributor

Unable to load preferences page due to issue in LocalePicker

Screenshot 2021-11-09 at 12 04 31 PM

Please see #6267

@OSBotify
Copy link
Contributor

🚀 Deployed to staging by @marcaaron in version: 1.1.14-5 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

@OSBotify
Copy link
Contributor

🚀 Deployed to production by @AndrewGable in version: 1.1.15-15 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

@mananjadhav
Copy link
Collaborator

I am tagging this PR to highlight an issue fixed here. All conditions in ternary expressions or left-hand operands on conditional renders, should be boolean. This PR is one of the PRs that uses conditional render with string operands, hence I am tagging it here for the contributors to check.

We've also updated the item in the checklist with this PR to avoid this issue in the future.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants