Skip to content

Commit e1abec9

Browse files
authored
Add require-default-props rule (fixes #528)
2 parents f89acbd + 4ae2fae commit e1abec9

File tree

9 files changed

+2518
-40
lines changed

9 files changed

+2518
-40
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
104104
* [react/prefer-stateless-function](docs/rules/prefer-stateless-function.md): Enforce stateless React Components to be written as a pure function
105105
* [react/prop-types](docs/rules/prop-types.md): Prevent missing props validation in a React component definition
106106
* [react/react-in-jsx-scope](docs/rules/react-in-jsx-scope.md): Prevent missing `React` when using JSX
107+
* [react/require-default-props](docs/rules/require-default-props.md): Enforce a defaultProps definition for every prop that is not a required prop
107108
* [react/require-optimization](docs/rules/require-optimization.md): Enforce React components to have a shouldComponentUpdate method
108109
* [react/require-render-return](docs/rules/require-render-return.md): Enforce ES5 or ES6 class for returning value in render function
109110
* [react/self-closing-comp](docs/rules/self-closing-comp.md): Prevent extra closing tags for components without children (fixable)

docs/rules/require-default-props.md

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# Enforce a defaultProps definition for every prop that is not a required prop (require-default-props)
2+
3+
This rule aims to ensure that any non-required `PropType` declaration of a component has a corresponding `defaultProps` value.
4+
5+
One advantage of `defaultProps` over custom default logic in your code is that `defaultProps` are resolved by React before the `PropTypes` typechecking happens, so typechecking will also apply to your `defaultProps`.
6+
The same also holds true for stateless functional components: default function parameters do not behave the same as `defaultProps` and thus using `defaultProps` is still preferred.
7+
8+
To illustrate, consider the following example:
9+
10+
With `defaultProps`:
11+
```js
12+
const HelloWorld = ({ name }) => (
13+
<h1>Hello, {name.first} {name.last}!</h1>
14+
);
15+
16+
HelloWorld.propTypes = {
17+
name: React.PropTypes.shape({
18+
first: React.PropTypes.string,
19+
last: React.PropTypes.string,
20+
})
21+
};
22+
23+
HelloWorld.defaultProps = {
24+
name: 'john'
25+
};
26+
27+
// Logs:
28+
// Invalid prop `name` of type `string` supplied to `HelloWorld`, expected `object`.
29+
ReactDOM.render(<HelloWorld />, document.getElementById('app'));
30+
```
31+
32+
Without `defaultProps`:
33+
```js
34+
const HelloWorld = ({ name = 'John Doe' }) => (
35+
<h1>Hello, {name.first} {name.last}!</h1>
36+
);
37+
38+
HelloWorld.propTypes = {
39+
name: React.PropTypes.shape({
40+
first: React.PropTypes.string,
41+
last: React.PropTypes.string,
42+
})
43+
};
44+
45+
// Nothing is logged, renders:
46+
// "Hello,!"
47+
ReactDOM.render(<HelloWorld />, document.getElementById('app'));
48+
```
49+
50+
## Rule Details
51+
52+
The following patterns are considered warnings:
53+
54+
```js
55+
function MyStatelessComponent({ foo, bar }) {
56+
return <div>{foo}{bar}</div>;
57+
}
58+
59+
MyStatelessComponent.propTypes = {
60+
foo: React.PropTypes.string.isRequired,
61+
bar: React.PropTypes.string
62+
};
63+
```
64+
65+
```js
66+
var Greeting = React.createClass({
67+
render: function() {
68+
return <div>Hello {this.props.foo} {this.props.bar}</div>;
69+
},
70+
71+
propTypes: {
72+
foo: React.PropTypes.string,
73+
bar: React.PropTypes.string
74+
},
75+
76+
getDefaultProps: function() {
77+
return {
78+
foo: "foo"
79+
};
80+
}
81+
});
82+
```
83+
84+
```js
85+
class Greeting extends React.Component {
86+
render() {
87+
return (
88+
<h1>Hello, {this.props.foo} {this.props.bar}</h1>
89+
);
90+
}
91+
}
92+
93+
Greeting.propTypes = {
94+
foo: React.PropTypes.string,
95+
bar: React.PropTypes.string
96+
};
97+
98+
Greeting.defaultProps = {
99+
foo: "foo"
100+
};
101+
```
102+
103+
```js
104+
class Greeting extends React.Component {
105+
render() {
106+
return (
107+
<h1>Hello, {this.props.foo} {this.props.bar}</h1>
108+
);
109+
}
110+
111+
static propTypes = {
112+
foo: React.PropTypes.string,
113+
bar: React.PropTypes.string.isRequired
114+
};
115+
116+
static defaultProps = {
117+
foo: "foo"
118+
};
119+
}
120+
```
121+
122+
```js
123+
type Props = {
124+
foo: string,
125+
bar?: string
126+
};
127+
128+
function MyStatelessComponent(props: Props) {
129+
return <div>Hello {props.foo} {props.bar}</div>;
130+
}
131+
```
132+
133+
The following patterns are not considered warnings:
134+
135+
```js
136+
function MyStatelessComponent({ foo, bar }) {
137+
return <div>{foo}{bar}</div>;
138+
}
139+
140+
MyStatelessComponent.propTypes = {
141+
foo: React.PropTypes.string.isRequired,
142+
bar: React.PropTypes.string.isRequired
143+
};
144+
```
145+
146+
```js
147+
function MyStatelessComponent({ foo, bar }) {
148+
return <div>{foo}{bar}</div>;
149+
}
150+
151+
MyStatelessComponent.propTypes = {
152+
foo: React.PropTypes.string.isRequired,
153+
bar: React.PropTypes.string
154+
};
155+
156+
MyStatelessComponent.defaultProps = {
157+
bar: 'some default'
158+
};
159+
```
160+
161+
```js
162+
type Props = {
163+
foo: string,
164+
bar?: string
165+
};
166+
167+
function MyStatelessComponent(props: Props) {
168+
return <div>Hello {props.foo} {props.bar}</div>;
169+
}
170+
171+
MyStatelessComponent.defaultProps = {
172+
bar: 'some default'
173+
};
174+
```
175+
176+
```js
177+
function NotAComponent({ foo, bar }) {}
178+
179+
NotAComponent.propTypes = {
180+
foo: React.PropTypes.string,
181+
bar: React.PropTypes.string.isRequired
182+
};
183+
```
184+
185+
## When Not To Use It
186+
187+
If you don't care about using `defaultsProps` for your component's props that are not required, you can disable this rule.
188+
189+
# Resources
190+
- [Official React documentation on defaultProps](https://facebook.github.io/react/docs/typechecking-with-proptypes.html#default-prop-values)

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ var allRules = {
44
'jsx-uses-react': require('./lib/rules/jsx-uses-react'),
55
'no-multi-comp': require('./lib/rules/no-multi-comp'),
66
'prop-types': require('./lib/rules/prop-types'),
7+
'require-default-props': require('./lib/rules/require-default-props'),
78
'display-name': require('./lib/rules/display-name'),
89
'jsx-wrap-multilines': require('./lib/rules/jsx-wrap-multilines'),
910
'self-closing-comp': require('./lib/rules/self-closing-comp'),

lib/rules/no-unused-prop-types.js

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
var Components = require('../util/Components');
1111
var variable = require('../util/variable');
12+
var annotations = require('../util/annotations');
1213

1314
// ------------------------------------------------------------------------------
1415
// Constants
@@ -108,24 +109,6 @@ module.exports = {
108109
return false;
109110
}
110111

111-
/**
112-
* Checks if we are declaring a `props` argument with a flow type annotation.
113-
* @param {ASTNode} node The AST node being checked.
114-
* @returns {Boolean} True if the node is a type annotated props declaration, false if not.
115-
*/
116-
function isAnnotatedFunctionPropsDeclaration(node) {
117-
if (node && node.params && node.params.length) {
118-
var tokens = context.getFirstTokens(node.params[0], 2);
119-
var isAnnotated = node.params[0].typeAnnotation;
120-
var isDestructuredProps = node.params[0].type === 'ObjectPattern';
121-
var isProps = tokens[0].value === 'props' || (tokens[1] && tokens[1].value === 'props');
122-
if (isAnnotated && (isDestructuredProps || isProps)) {
123-
return true;
124-
}
125-
}
126-
return false;
127-
}
128-
129112
/**
130113
* Checks if we are declaring a prop
131114
* @param {ASTNode} node The AST node being checked.
@@ -799,7 +782,7 @@ module.exports = {
799782
* FunctionDeclaration, or FunctionExpression
800783
*/
801784
function markAnnotatedFunctionArgumentsAsDeclared(node) {
802-
if (!node.params || !node.params.length || !isAnnotatedFunctionPropsDeclaration(node)) {
785+
if (!node.params || !node.params.length || !annotations.isAnnotatedFunctionPropsDeclaration(node, context)) {
803786
return;
804787
}
805788
markPropTypesAsDeclared(node, resolveTypeAnnotation(node.params[0]));

lib/rules/prop-types.js

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
var Components = require('../util/Components');
1111
var variable = require('../util/variable');
12+
var annotations = require('../util/annotations');
1213

1314
// ------------------------------------------------------------------------------
1415
// Constants
@@ -114,24 +115,6 @@ module.exports = {
114115
return false;
115116
}
116117

117-
/**
118-
* Checks if we are declaring a `props` argument with a flow type annotation.
119-
* @param {ASTNode} node The AST node being checked.
120-
* @returns {Boolean} True if the node is a type annotated props declaration, false if not.
121-
*/
122-
function isAnnotatedFunctionPropsDeclaration(node) {
123-
if (node && node.params && node.params.length) {
124-
var tokens = context.getFirstTokens(node.params[0], 2);
125-
var isAnnotated = node.params[0].typeAnnotation;
126-
var isDestructuredProps = node.params[0].type === 'ObjectPattern';
127-
var isProps = tokens[0].value === 'props' || (tokens[1] && tokens[1].value === 'props');
128-
if (isAnnotated && (isDestructuredProps || isProps)) {
129-
return true;
130-
}
131-
}
132-
return false;
133-
}
134-
135118
/**
136119
* Checks if we are declaring a prop
137120
* @param {ASTNode} node The AST node being checked.
@@ -792,7 +775,7 @@ module.exports = {
792775
* FunctionDeclaration, or FunctionExpression
793776
*/
794777
function markAnnotatedFunctionArgumentsAsDeclared(node) {
795-
if (!node.params || !node.params.length || !isAnnotatedFunctionPropsDeclaration(node)) {
778+
if (!node.params || !node.params.length || !annotations.isAnnotatedFunctionPropsDeclaration(node, context)) {
796779
return;
797780
}
798781
markPropTypesAsDeclared(node, resolveTypeAnnotation(node.params[0]));

0 commit comments

Comments
 (0)