-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
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
react/destructuring-assignment: Add "minAttributes" option #1731
Comments
Convenience isn't the priority; destructuring the props serves many useful purposes: for instance, for function props, it ensures they're not called with the Optimize for reading, not writing: in other words, convenience when writing code does not matter in the face of improving the readability of the code. (Separately, a component that small should be an SFC, not a class-based component, and neither of those two components likely have any performance benefit by being a PureComponent) |
Other rules allow for options that most people will consider unorthodox (e.g. const {user} = this.state;
doWhatever(user); If we're talking of readability, I believe most programmers out there would understand |
@pferreir i agree that in the tiny percentage of cases where you're using a single prop or state value, it's slightly awkward to have to write the destructuring - but in the cause of minimizing future diff churn, when inevitably someone adds a second prop or state field, the change is much more minimal when they'd destructured in the first place. |
Except that there are components where it's very unlikely they'll have more than one prop in the future |
@ThiefMaster those sound like they should be SFCs, where they can be destructured in the component's arguments. |
const SnackBarConnected = props => props.message && <SnackBar {...props} />; const SnackBarConnected = ({ message, ...props }) =>
message && <SnackBar {...props} message={message} />; @ljharb Do you think variant 2 is looking better than 1? |
@UchihaVeha considering that both will throw a React error when message is function SnackBarConnected(props) {
const { message } = props;
if (!message) { return null; }
return <SnackBar {...props} />;
} |
@ljharb You did not understand me, what if I need to use some prop in current component and pass this prop to the child with other props and using the arrow function? const myComponent = props =>
<div>
<span>{props.user.email}</span>
<Account {...props} />
</div> const myComponent = ({user, ...props}) =>
<div>
<span>{user.email}</span>
<Account {...props} user={user} />
</div> |
@UchihaVeha in both cases, you should use an explicit const myComponent = (props) => {
const { user: { email } = props;
return (
<div>
<span>{props.user.email}</span>
<Account {...props} />
</div>
);
}; const myComponent = ({ user, ...props }) => {
const { email } = user;
return (
<div>
<span>{email}</span>
<Account {...props} user={user} />
</div>
);
}; |
@ljharb What do you think of this shouldComponentUpdate(nextProps) {
return this.props.uri !== nextProps.uri;
} vs this shouldComponentUpdate(nextProps) {
const { uri } = this.props;
return uri !== nextProps.uri;
} ? |
@dolsem I definitely prefer the second one, altho I think you've identified a current problematic case - specifically that this: shouldComponentUpdate({ uri }) {
return this.props.uri !== uri;
} will still warn on |
@ljharb I like neither my second version nor the version you provided, because I think accessing In the example you provided the problem is easily avoidable by not destructuring I think it's almost always best to destructure |
Another scenario where this would be useful is when there's only one mention of Too verbose for simple functionshandleThing = (evt, id) => {
const { dispatchOpenDialog } = this.props;
dispatchOpenDialog(DIALOG_ID, {
id
});
} Cleaner solution when
|
This actually seems like a duplicate of #1629. |
@dandonahoe that example is exactly the worst hazard - Even if there was an option like this (which I’m not inclined to add) it would never allow you to avoid destructuring a function prop. |
@ljharb Thanks for the response. Correct me if I'm wrong, but wouldn't this be fine since the only value being passed back from If I'm missing something, i have a good chunk of code to go back and refactor.... which if fine because I'd rather get it right. |
You’re missing that in JavaScript, the object the method is called in gets implicitly passed as the this value to the function. |
Not having this was problematic for us in this case: class MyComponent extends Component {
// How it was (good)
onButtonPress = () => {
analytics.track('buttonPressed');
this.props.doThing();
}
// How it become (bad)
onButtonPress = () => {
analytics.track('buttonPressed');
const { doThing } = this.props;
doThing();
}
// What I dream of (off topic, but I had to mention 😁)
@track('buttonPressed')
onButtonPress = () => this.props.doThing();
} |
@AlicanC that's specifically the footgun that must be avoided - |
@AlicanC I asked a similar question above, not realizing the nuance about the “this” scope. The followup posts explain |
@dandonahoe IMO your problem is better solved by something like #1968. @ljharb IMO the footgun is not |
It’s not “tons”, and this is a javascript linter, not a Flow or TS linter. Are you honestly trying to say tho that you strictly type your functions to have no |
All I mean is that this is a type issue. ESLint can't prevent me calling a function with wrong arguments and it can't prevent me calling a function with wrong If we go back to this example: class MyComponent extends Component {
onButtonPress = () => this.props.doThing();
} To achieve this we type all of our callbacks If we ever wanted to be 100% sure about this, we would need something like facebook/flow#452 and not some ESLint rule that says "I don't know if what you are doing is right or wrong, so I will disallow it completely, including the valid cases". This is sloppy and is not something we want. I can also assure you that I am not one of those "Let's rename |
eslint certainly can prevent you from calling props functions with the props object as the Using types to paper over incorrect engineering - like returning a value from If you need to turn off a rule because your developers refuse to learn best practices, you're welcome to do that - but that doesn't mean the rule should be weakened. I'd certainly be open to making this rule more autofixable, however, if that would ease the burden. |
What does this have to do with I understand that you have your best practices, but we have our own too. To us, this isn't a case of "developers refusing to learn best practices". Our developers do not refuse to learn best practices, our developers are under-experienced and I don't want to throw hard-to-follow rules at them. We've had people quit because the job was too hard. I've had people contacting me asking for help because they were losing their minds trying to figure something out. This isn't about reaching the best code quality, it's about reaching the best code quality without making people shout at their laptops and go to sleep with impostor syndrome. We even have developers who don't have to fix ESLint/Flow/Jest errors, I checkout their branches and fix the errors for them. Now, if we could have this rule to be configurable as |
@AlicanC i'd be open to modifying the rule to add an option that specifically only enforced destructuring on function invocations, and also to a PR that adds autofixing, which should address the "hard" complaint. |
Hey ljharb I was running into this issue often in class methods and I was wondering about it.
is better then specifically when it comes to class methods, I feel like the latter is much more readable and approachable |
@yardenhochman separately, you should never put arrow functions in class fields - but why it's better is that in your second example, the |
I'm wondering - in the parent component that provides the function prop, that function would normally be already bound somehow, right? Even if it's via an arrow function, but ideally with class Parent extends React.Component {
constructor(props) {
super(props);
this.handleLogin = this.handleLogin.bind(this);
}
handleLogin() { this.blah(); }
render() {
return <Child login={this.handleLogin} />;
}
} If that's the case, then |
@alexzherdev certainly it could already be bound, or be an arrow function, or simply not use |
+1 on that. I'm with @yardenhochman. I really prefer the 2nd code as this destruct is completely unnecessary IMO. I'd like to see a |
I think minAttributes would be the best middleground, but I also totally understand where @ljharb coming from. |
Can you please elaborate (with a bit of code examples) or link us to some explanatory articles about this "much larger class of bugs"? This is the first time I see it and unfortunately I have this syntax (see below) all around my codebase (usually disabling I really want to understand the problem so I can fix it properly (and of course I will stop disabling the rule, haha). Thanks for your time! Example: class App extends Component {
handleLogin = () => this.props.loginUser(...)
// ...
} |
@malyzeli the |
@ljharb well, it's true. I didn't realize that because most of my codebase is purely functional - I never mutate function input parameters and the only place I use I think it's questionable if you need to be guarded by I made an example code for those who want to see the difference, it's available here: |
Strongly encourage a way to turn this rule off. It's fine if you think always destructuring or never destructuring are better code styles, but many people disagree! You accommodate many other options and variants in eslint (tab/spaces, anyone?)--why the intransigence here? There are plenty of cases where you're only ever using a single prop or state field and now after updating you've suddenly got a giant angry warning all over it. There are many cases where you have short class methods, like
which passed before and fail now. There are plenty of cases, especially in larger/container components, where having many naked, destructured variables running around makes it harder to tell what each represents and where each of them originated from--is it props, state, a constant, or a computed value? Choosing to include the These patterns might not be to your taste, and that's fine--enforce your rules on your projects!--but they are to our taste. The addition of this rule to eslint with only ALWAYS destructuring or NEVER destructuring is making it harder, not easier, to enforce our code style. :/ |
@aldeka because it's not a style choice; it's a correctness choice, which always trumps style. |
This comment has been minimized.
This comment has been minimized.
There is always a balance between good practices and convenience. In many-many-many cases being unable to limit this rule, lets say, starting from 2 attributes or more only leads to it being disabled at all. Which is kind of sad. So, instead of inflicting good practices it is being excluded from usage at all, which contradicts the point completely. |
Upvote for this thread |
Given the inherent dangers in I understand the argument that it might cause the rule to be disabled, but I don't think that's worth making it so easy to do the wrong thing. |
This option would enable the rule only if at least n values are accessed. For example, with minAttributes=2 this would be OK:
while this would trigger the rule's warning:
The idea for this option is that especially in small components that only use one single property it's more convenient to not have to use destructuring (which does add one extra line of code!)
I wouldn't mind creating a PR to add this option if it is likely to get merged.
The text was updated successfully, but these errors were encountered: