-
-
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
button-has-type does not allow for dynamic assignment #1555
Comments
Stateless React Components should not use JS default values; that needs to be a defaultProp. |
When you say "should not" where does this recommendation come from? Is this a lint suggestion or are there language/runtime implications? |
defaultProps are introspectable by React and other tools; default arguments are not. #1184 has more discussion on it. |
Regardless of the defaultProps discussion, something’s buggy with this rule. The following code: <button type={submit ? "submit" : "button"} onClick={onClick}>
{children}
</button> … gives the following lint error:
Surely it shouldn’t say "null" there? |
@lydell very true! the ternary case would probably have to be explicitly handled; however, I'd say that the rule should be forcing a single, hardcoded type value - and that you should conditionally render two different elements if you want two types. |
I agree this rule is handled poorly. A work-around is to just disable the rule temporarily:
|
The type should not be dynamic, full stop. If you want the type to be one of the 1, 2, or 3 options, use if/else and hardcode those three. |
Why shouldn't it be dynamic? Can you explain please? |
I am also interested in "why" it should be dynamic (because dev typo in props?). Seems weird to duplicate the button three times for a hardcoded value.
|
Only twice, since reset buttons are a terrible UX pattern :-) |
It shouldn’t be dynamic because all three button types have wildly different semantics - i can’t conceive of when you’d want a submit button randomly changing to be an entirely different kind of button, or vice versa. A submit button must be inside a form; a regular button must have some kind of javascript behavior - these are different components/elements, not just two flavors of the same thing. |
Isn't that true for input types as a whole? Not just specific button? Why wouldn't this give an error as well?
|
Yes, that is certainly true! However, the primary point of the rule is to ensure that If we wanted to make a generic rule that governed input types, that would also force them to be static (and I'd encourage you not to make a non-private generic input component that takes "type" as a parameter, for the reasons I mentioned above) |
In a REST level 3 kind of app where the app state is completely controlled by the server, the server dictates the fields of a form. That's a valid scenario for when the button type needs to be dynamic. Since hardcoding the now known button types defeats the entire reason for going REST level 3, which is to have an evolutionary API, which can change in the future without touching the client but just send a new HTML6 value for button type. But since REST level 3 apps are such a rare breed as of now I'll just disable this rule that's supposed to make the button type usage "safe". |
I'm not familiar with any HTML 6 nor any plans for it in the near or far future, nor any plans for any new button type ever. When one appears, the server shouldn't be controlling that - it should only be explicitly supported in the UI. |
I think we can agree to disagree on that so I'll close this issue here. A workaround was mentioned that works just fine. |
@pke which workaround works to you? |
I don't remember @vaske :/ Maybe I just disabled the rule. |
@pke yeah I did the same ;) |
Yep, I disabled the rule too.
|
@josephshambrook at the least, make a propType that used oneOf, so you’re not just wildly accepting any random string (or worse, “reset”). Using an override comment inside a component where you’re knowingly violating a best practice is indeed the proper way to handle it. |
Since propTypes are not used in production builds anyway such safeguards against "reset" type should happen in code, not in propTypes. |
If your propType warnings are properly set up to fail your tests, there’s no need to check it in production. |
Just to add, the above "solution" doesn't work for TypeScript.
I'm going to just assume I'll have to disable the rule. |
Every decent software engineer should be that concerned with correctness. And no static type analysis or On the other hand you did not answer my question if the team would accept a PR with the option to enabled dynamic type assigned for the many reasons voiced and voted here by users of the plugin? The option would default to the current restrictive behaviour but could be relaxed. |
How would using this option be different than simply disabling the rule, or using an override comment? The rule, despite it's name, does not merely care if your button has a |
Perhaps the name should change then to be more indicative of what it's actually doing. A codebase I'm working on now has many different groups of buttons, and nearly all buttons are generated from an array of "object"s and then props are put onto the button as we iterate over the object. There are definitely valid use cases to only want to check the existence of non-empty If you don't want to support this that's alright but
Clearly indicates a problem and should be looked at it. It's clearly misleading. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
You see, that's exactly the answer I was afraid would appear any time in this thread. Many users get confused by the rule. Judging by all your arguments (against "reset" types) the rule should maybe have been named Responding to valid concerns regarding the usage of a rule should not be countered by "you can fork" or lecturing users on their code. I am beyond this, as I do currently not work with react anymore but this "you can fork" just sounded rude to me considering all the above constructive feedback on this very opinionated rule. But then maybe its also not easily possible to change the rule to support dynamic types ("reset" not included) even if the maintainers wanted to. |
@pke my response saying to fork was to "eslint is supposed to be unopinionated", any package can be as opinionated as it wants to - this plugin is opinionated like every other. I totally agree that changing the rule name would help, but the churn of doing that isn't worth it. It's not at all practical and possible to support dynamic types; that's just the nature of JavaScript itself (it could be supported in extremely limited ways that would require massive complexity, but that wouldn't be sufficient for virtually any of the use cases folks have complained about) |
…amic You can read more about the issue with the rule: jsx-eslint/eslint-plugin-react#1555
This sounds like a good solution to me. Would a PR adding this be welcome? |
@Hypnosphi sure, seems harmless enough, as long as both branches in the ternary were static strings. |
I'm having the same Eslint issue, hence I found this page. Much of the conversation essentially claimed that buttons should be hard-coded due to the different 'schemas' needed for different button types. I came up with this: export const Button = ({ className, type, ...props }) => {
Button.defaultProps = { className: '', type: 'button' };
/* eslint-disable react/button-has-type */
return (
<button
className={['button', className].join(' ')}
type={type}
{...props}
/>
);
}; As you can see, I was forced to disable the linter here, but as far as dynamic, this is about as good as I could make it to support every style of button via overloading, including if you wanted to add additional class names. I've only been coding for about 2 months, so any feedback (positive AND constructive) would be greatly appreciated! |
@musicMan1337 do you ever use export const Button = ({ className, ...props }) => {
Button.defaultProps = { className: '' };
return (
<button
className={['button', className].join(' ')}
type="button"
{...props}
/>
);
}; |
export const Button = ({ className, type, ...props }) => {
if (type === 'button') {
return (
<button
className={['button', className].join(' ')}
type="button"
{...props}
/>
);
}
if (type === 'submit') {
return (
<button
className={['button', className].join(' ')}
type="submit"
{...props}
/>
);
}
};
Button.propTypes = {
className: PropTypes.string,
type: PropTypes.oneOf(['button', 'submit']),
};
Button.defaultProps = { className: '', type: 'button' }; There is never a need to dynamically specify a Button's type (and you never ever want a "reset" button, so those two are sufficient). |
To preface, I'm not the best with forms! @Hypnosphi I've found in React I tend to only use submit buttons when I actually need a real button. If I'm just testing functionality with dummy buttons then it doesn't matter, then again I'm sure you can do wonderous effects using buttons! @ljharb It's interesting how you feel about the "reset" type, I'll have to look into that some more since I have used them in other small projects, so thanks for the nod! And as for the "if" statement, I definitely see the logic, but I personally find it very cumbersome to repeat code in that way, particularly from a readability perspective. Also, I didn't know you could put default props outside of the component, I'll have to play with that, thanks! |
@musicMan1337 you can only defaultProps outside the component. Your statement doesn’t take effect until after the component has been rendered. |
@ljharb Oh nice that's good to know! I was being misdirected by my linter since the error turns off both ways (I guess in that specific case it just scrubs the whole file without scope?). Definitely need more practice with React, most of my time has been spent on backend stuff. Also I now clearly understand the taboo of the "reset" button type! |
type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
/**
* enforcing the type attribute to be required and limited to "submit" and "button"
*/
type: 'submit' | 'button';
};
export const Button = (props: ButtonProps) => {
// see ButtonProps
// eslint-disable-next-line react/button-has-type
return <button {...props} />;
};
const Btn = () => <Button />; // <- ts error my 2cents on this. |
- disabled eslint for dynamic button type, as stated at: jsx-eslint/eslint-plugin-react#1555 - creation on button stylesheet - modularization of button into a component
This comment was marked as spam.
This comment was marked as spam.
It worked for me: import { type ButtonHTMLAttributes, type FC } from 'react';
export const Button: FC<ButtonHTMLAttributes<HTMLButtonElement>> = ({
children,
...props
}) => {
return (
<button type="button" {...props} className="myCustomClass">
{children}
</button>
);
}; |
My Button component allows to override the
type
arg butbutton-has-type
is still complaining about. It seems it does not validate the real AST path of prop assignmentDoes the plugin have access to the propTypes and can match that the only possible allowed values are within the rules allowed values and that the default
type
is also set to something specific, like in my example tobutton
?The text was updated successfully, but these errors were encountered: