-
-
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/prefer-stateless-function false positive #491
Comments
Out of curiosity, why don't you write your component as a function and wrap it with import {connect} from 'react-redux'
import {thing} from './actions'
function Test ({thing}) {
return <button onClick={thing}>Click me!</button>;
}
export default connect(null, {thing})(Test) |
If a component has instance properties, or uses @0x80, that is totally an option for refactoring, but the rule can't and shouldn't try to catch situations like that. |
@0x80 neat! Didn’t know that was an option. |
@jacobp100 You're welcome :) |
I guess you're right. Does checking for |
Things that should disqualify a component from being an SFC in this linter rule:
In other words, a class-based component should only be an SFC when:
|
Can confirm @0x80's solution worked! |
I've run into some false positives with classes that make use of import React, { Component, PropTypes } from 'react';
import pureRender from 'pure-render-decorator';
@pureRender
export default class Example extends Component {
static propTypes = {
action: PropTypes.func.isRequired,
id: PropTypes.number.isRequired,
};
handleClick = () => this.props.action(this.props.id);
render() {
return <button onClick={this.handleClick} />;
}
} While all the data is coming from props, I can't see any way it could be rewritten as a stateless function without breaking the |
Yes, using callbacks should justify not using stateless components. |
Following @ljharb advice's I rewrote the rule and it should now better target the components that should be written as SFC. The new version will ask you to write a component as a SFC if:
I also added much more tests. Let me know I missed something. |
@yannickcr awesome! When React v15 comes out, you'll need to conditionally modify that last one, but for now this is 👍 |
@ljharb what is changing in v15 for the last one ? |
@yannickcr i thought I saw someone saying that SFCs could return falsy values/null in v15, but now I can't find the comment, nor a mention in the React blog post save https://facebook.github.io/react/blog/#rendering-null-now-uses-comment-nodes - so maybe nothing's changing ¯_(ツ)_/¯ |
@vdh I can see a way of writing it as a stateless component. I have actually used this myself. Using your example, I would do the following by making use of a simple closure: import React, { PropTypes } from 'react';
import pureRender from 'pure-render-decorator';
export default pureRender(Example);
function Example(props) {
return <button onClick={_handleClick} />;
function _handleClick() {
props.action(props.id);
}
}
Example.propTypes = {
action: PropTypes.func.isRequired,
id: PropTypes.number.isRequired
}; Also, thanks to hoisting, you can still export the pureRender'ed version up top. So, to be honest, I'm not sure I agree with this thread that they are false positives. |
@artisologic That would still re-create
So even if the code doesn't trigger As a side note, I like to use the |
@vdh I'm pretty sure they are pure by definition, maybe that wasn't comunicated well by the React docs, otherwise we would all rather use classes.
|
Hi @vdh, I realised my mistake in my sleep and woke up this morning thinking... "The handler in the closure will still be recreated everytime!" I have to say then, I find less and less use cases for using stateless components then apart from merely template components. Anything that would accept a callback is basically out of the question now. Also, thanks for the details regarding the fact that stateless components can't support pure render, I had assumed they were given their name. I think it's correct that the doc is a bit misleading. |
What about I have a class with the @CSSModules(styles)
class Button extends Component {
render() {
<button styleName="button"></button>
}
} |
@Kerumen decorators are just functions so your code is equivalent to: class Button extends Component {
render() {
<button styleName="button"></button>
}
}
export default CSSModules(styles)(Button); I'm not sure what your HOC does but technically it should be possible for Button to be a SFC like so: function Button(props) {
return <button styleName="button"></button>;
}
export default CSSModules(styles)(Button); |
Yep right! I always wrote the decorator so I forgot the good ol' syntax. Thanks :) |
Just double-checked about those stateless components and you guys are right, they aren't pure. My world just shattered 😢 |
@silvenon my world was also shattered, although there might be future optimisations to React that could enable automatic (or opt-in) pure rendering for stateless components. @artisologic I've found that stateless components are best used for simple/small components that literally just map props (& context) to some JSX. For the non-trivial components I just stick with classes (and disable Also I notice that while @decoratorWithoutParam
@decoratorWithParam(param)
class Button extends Component {
render() {
return <button>{this.props.children}</button>;
}
} // bonus fat arrow + destructuring version
const Button = ({ children }) => (<button>{children}</button>);
decoratorWithoutParam(Button);
decoratorWithParam(param)(Button);
export default Button; |
Hmm, on a second inspection it looks like there is some reassignment going on with some decorators, although it's sometimes problematic if multiple decorators try to wrap/reassign the same component. The decorator docs have examples that mutate (e.g. readonly), but also shows reassignment in the desugaring segment. So I guess until decorators get more stable in regards to reassignment vs mutation, "results may vary"? |
While using Redux, I receive a false positive when I have a component that only uses class MyComponent extends React.Component {
render() {
const { prop1, prop2 } = this.props;
return (
<div>{prop1} {prop2}</div>
)
}
}
const mapStateToProps = state => ({
prop1: state.prop1,
prop2: state.prop2,
})
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(actionCreators, dispatch)
})
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent); As far as I know, it is impossible to map state to a stateless function (duh). However, the linter continues to suggest to use a stateless component because I'm only calling Let me know if I'm missing something obvious! |
@Robinnnnn It's |
@ljharb sorry, but do you mind showing me how? |
function MyComponent({ prop1, prop2 }) {
return (
<div>{prop1} {prop2}</div>
);
}
MyComponent.propTypes = {
prop1: React.PropTypes.node,
prop2: React.PropTypes.node,
};
const mapStateToProps = ({ prop1, prop2 }) => ({
prop1,
prop2,
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(actionCreators, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent); |
Thank you!! |
Input
This gives a lint error to prefer a stateless function. However, in this case, it is not appropriate. While you could make a stateless function and just create a dispatch function every time, the value for the button onClick handler is changed between updates (i.e.
newProps.onClick. !== props.onClick
). Hence DOM updates have to occur if a stateless function was used.The text was updated successfully, but these errors were encountered: