Skip to content

New: Add require-default-props rule (fixes #528) #965

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

Merged
merged 6 commits into from
Dec 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ Finally, enable all of the rules that you would like to use. Use [our preset](#
* [react/prefer-stateless-function](docs/rules/prefer-stateless-function.md): Enforce stateless React Components to be written as a pure function
* [react/prop-types](docs/rules/prop-types.md): Prevent missing props validation in a React component definition
* [react/react-in-jsx-scope](docs/rules/react-in-jsx-scope.md): Prevent missing `React` when using JSX
* [react/require-default-props](docs/rules/require-default-props.md): Enforce a defaultProps definition for every prop that is not a required prop
* [react/require-optimization](docs/rules/require-optimization.md): Enforce React components to have a shouldComponentUpdate method
* [react/require-render-return](docs/rules/require-render-return.md): Enforce ES5 or ES6 class for returning value in render function
* [react/self-closing-comp](docs/rules/self-closing-comp.md): Prevent extra closing tags for components without children (fixable)
Expand Down
190 changes: 190 additions & 0 deletions docs/rules/require-default-props.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# Enforce a defaultProps definition for every prop that is not a required prop (require-default-props)

This rule aims to ensure that any non-required `PropType` declaration of a component has a corresponding `defaultProps` value.

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`.
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.

To illustrate, consider the following example:

With `defaultProps`:
```js
const HelloWorld = ({ name }) => (
<h1>Hello, {name.first} {name.last}!</h1>
);

HelloWorld.propTypes = {
name: React.PropTypes.shape({
first: React.PropTypes.string,
last: React.PropTypes.string,
})
};

HelloWorld.defaultProps = {
name: 'john'
};

// Logs:
// Invalid prop `name` of type `string` supplied to `HelloWorld`, expected `object`.
ReactDOM.render(<HelloWorld />, document.getElementById('app'));
```

Without `defaultProps`:
```js
const HelloWorld = ({ name = 'John Doe' }) => (
<h1>Hello, {name.first} {name.last}!</h1>
);

HelloWorld.propTypes = {
name: React.PropTypes.shape({
first: React.PropTypes.string,
last: React.PropTypes.string,
})
};

// Nothing is logged, renders:
// "Hello,!"
ReactDOM.render(<HelloWorld />, document.getElementById('app'));
```

## Rule Details

The following patterns are considered warnings:

```js
function MyStatelessComponent({ foo, bar }) {
return <div>{foo}{bar}</div>;
}

MyStatelessComponent.propTypes = {
foo: React.PropTypes.string.isRequired,
bar: React.PropTypes.string
};
```

```js
var Greeting = React.createClass({
render: function() {
return <div>Hello {this.props.foo} {this.props.bar}</div>;
},

propTypes: {
foo: React.PropTypes.string,
bar: React.PropTypes.string
},

getDefaultProps: function() {
return {
foo: "foo"
};
}
});
```

```js
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.foo} {this.props.bar}</h1>
);
}
}

Greeting.propTypes = {
foo: React.PropTypes.string,
bar: React.PropTypes.string
};

Greeting.defaultProps = {
foo: "foo"
};
```

```js
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.foo} {this.props.bar}</h1>
);
}

static propTypes = {
foo: React.PropTypes.string,
bar: React.PropTypes.string.isRequired
};

static defaultProps = {
foo: "foo"
};
}
```

```js
type Props = {
foo: string,
bar?: string
};

function MyStatelessComponent(props: Props) {
return <div>Hello {props.foo} {props.bar}</div>;
}
```

The following patterns are not considered warnings:

```js
function MyStatelessComponent({ foo, bar }) {
return <div>{foo}{bar}</div>;
}

MyStatelessComponent.propTypes = {
foo: React.PropTypes.string.isRequired,
bar: React.PropTypes.string.isRequired
};
```

```js
function MyStatelessComponent({ foo, bar }) {
return <div>{foo}{bar}</div>;
}

MyStatelessComponent.propTypes = {
foo: React.PropTypes.string.isRequired,
bar: React.PropTypes.string
};

MyStatelessComponent.defaultProps = {
bar: 'some default'
};
```

```js
type Props = {
foo: string,
bar?: string
};

function MyStatelessComponent(props: Props) {
return <div>Hello {props.foo} {props.bar}</div>;
}

MyStatelessComponent.defaultProps = {
bar: 'some default'
};
```

```js
function NotAComponent({ foo, bar }) {}

NotAComponent.propTypes = {
foo: React.PropTypes.string,
bar: React.PropTypes.string.isRequired
};
```

## When Not To Use It

If you don't care about using `defaultsProps` for your component's props that are not required, you can disable this rule.

# Resources
- [Official React documentation on defaultProps](https://facebook.github.io/react/docs/typechecking-with-proptypes.html#default-prop-values)
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var allRules = {
'jsx-uses-react': require('./lib/rules/jsx-uses-react'),
'no-multi-comp': require('./lib/rules/no-multi-comp'),
'prop-types': require('./lib/rules/prop-types'),
'require-default-props': require('./lib/rules/require-default-props'),
'display-name': require('./lib/rules/display-name'),
'jsx-wrap-multilines': require('./lib/rules/jsx-wrap-multilines'),
'self-closing-comp': require('./lib/rules/self-closing-comp'),
Expand Down
21 changes: 2 additions & 19 deletions lib/rules/no-unused-prop-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

var Components = require('../util/Components');
var variable = require('../util/variable');
var annotations = require('../util/annotations');

// ------------------------------------------------------------------------------
// Constants
Expand Down Expand Up @@ -108,24 +109,6 @@ module.exports = {
return false;
}

/**
* Checks if we are declaring a `props` argument with a flow type annotation.
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if the node is a type annotated props declaration, false if not.
*/
function isAnnotatedFunctionPropsDeclaration(node) {
if (node && node.params && node.params.length) {
var tokens = context.getFirstTokens(node.params[0], 2);
var isAnnotated = node.params[0].typeAnnotation;
var isDestructuredProps = node.params[0].type === 'ObjectPattern';
var isProps = tokens[0].value === 'props' || (tokens[1] && tokens[1].value === 'props');
if (isAnnotated && (isDestructuredProps || isProps)) {
return true;
}
}
return false;
}

/**
* Checks if we are declaring a prop
* @param {ASTNode} node The AST node being checked.
Expand Down Expand Up @@ -799,7 +782,7 @@ module.exports = {
* FunctionDeclaration, or FunctionExpression
*/
function markAnnotatedFunctionArgumentsAsDeclared(node) {
if (!node.params || !node.params.length || !isAnnotatedFunctionPropsDeclaration(node)) {
if (!node.params || !node.params.length || !annotations.isAnnotatedFunctionPropsDeclaration(node, context)) {
return;
}
markPropTypesAsDeclared(node, resolveTypeAnnotation(node.params[0]));
Expand Down
21 changes: 2 additions & 19 deletions lib/rules/prop-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

var Components = require('../util/Components');
var variable = require('../util/variable');
var annotations = require('../util/annotations');

// ------------------------------------------------------------------------------
// Constants
Expand Down Expand Up @@ -114,24 +115,6 @@ module.exports = {
return false;
}

/**
* Checks if we are declaring a `props` argument with a flow type annotation.
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if the node is a type annotated props declaration, false if not.
*/
function isAnnotatedFunctionPropsDeclaration(node) {
if (node && node.params && node.params.length) {
var tokens = context.getFirstTokens(node.params[0], 2);
var isAnnotated = node.params[0].typeAnnotation;
var isDestructuredProps = node.params[0].type === 'ObjectPattern';
var isProps = tokens[0].value === 'props' || (tokens[1] && tokens[1].value === 'props');
if (isAnnotated && (isDestructuredProps || isProps)) {
return true;
}
}
return false;
}

/**
* Checks if we are declaring a prop
* @param {ASTNode} node The AST node being checked.
Expand Down Expand Up @@ -792,7 +775,7 @@ module.exports = {
* FunctionDeclaration, or FunctionExpression
*/
function markAnnotatedFunctionArgumentsAsDeclared(node) {
if (!node.params || !node.params.length || !isAnnotatedFunctionPropsDeclaration(node)) {
if (!node.params || !node.params.length || !annotations.isAnnotatedFunctionPropsDeclaration(node, context)) {
return;
}
markPropTypesAsDeclared(node, resolveTypeAnnotation(node.params[0]));
Expand Down
Loading