Skip to content

Commit

Permalink
Add support for React 16.3 lifecycle methods
Browse files Browse the repository at this point in the history
- Add React 16.3 lifecycle methods to default config
- Allow static method position to be overriden by specification
  - Note: this update means that a static method of the same name as a non-static method in the sort-comp will be considered an error if it is not placed in the position corresponding to its name
- Add `instance-variables` to the lifecycle after the `constructor` and before `getDefaultProps`
  - Note: This update is not required, but is a nice to have since the default order has to be updated anyway, and current behavior forces all instance variables to come after `componentWillUnmount` by default, which is a strange ordering.
  - Note: The super constructor runs prior to instance variables being instantiated, so `this.props` can be referenced within an instance variable definition. However, instance variables are defined prior to the current class' constructor executing. Since it is hard to represent this flow I thought it would be better to place instance variables below the constructor so as to make it obvious that `this.props` is defined, since defining an instance variable then using it in the constructor seems like an unnecessary use case.
- Add a test case for a React 16.3 class
- Update documentation to reflect changes
  • Loading branch information
dejoean authored and ljharb committed Apr 18, 2018
1 parent 230b0ad commit 768013e
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 21 deletions.
29 changes: 17 additions & 12 deletions docs/rules/sort-comp.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Enforce component methods order (react/sort-comp)

When creating React components it is more convenient to always follow the same organisation for method order to help you easily find lifecyle methods, event handlers, etc.
When creating React components it is more convenient to always follow the same organisation for method order to help you easily find lifecycle methods, event handlers, etc.

**Fixable:** This rule is automatically fixable using the [`sort-comp` transform](https://github.com/reactjs/react-codemod/blob/master/transforms/sort-comp.js) in [react-codemod](https://www.npmjs.com/package/react-codemod).

Expand All @@ -9,7 +9,7 @@ When creating React components it is more convenient to always follow the same o
The default configuration ensures that the following order must be followed:

1. static methods and properties
2. lifecycle methods: `displayName`, `propTypes`, `contextTypes`, `childContextTypes`, `mixins`, `statics`,`defaultProps`, `constructor`, `getDefaultProps`, `getInitialState`, `state`, `getChildContext`, `componentWillMount`, `componentDidMount`, `componentWillReceiveProps`, `shouldComponentUpdate`, `componentWillUpdate`, `componentDidUpdate`, `componentWillUnmount` (in this order).
2. lifecycle methods: `displayName`, `propTypes`, `contextTypes`, `childContextTypes`, `mixins`, `statics`, `defaultProps`, `constructor`, `getDefaultProps`, `state`, `getInitialState`, `getChildContext`, `getDerivedStateFromProps`, `componentWillMount`, `UNSAFE_componentWillMount`, `componentDidMount`, `componentWillReceiveProps`, `UNSAFE_componentWillReceiveProps`, `shouldComponentUpdate`, `componentWillUpdate`, `UNSAFE_componentWillUpdate`, `getSnapshotBeforeUpdate`, `componentDidUpdate`, `componentDidCatch`, `componentWillUnmount` (in this order).
3. custom methods
4. `render` method

Expand Down Expand Up @@ -70,30 +70,36 @@ The default configuration is:
'defaultProps',
'constructor',
'getDefaultProps',
'getInitialState',
'state',
'getInitialState',
'getChildContext',
'getDerivedStateFromProps',
'componentWillMount',
'UNSAFE_componentWillMount',
'componentDidMount',
'componentWillReceiveProps',
'UNSAFE_componentWillReceiveProps',
'shouldComponentUpdate',
'componentWillUpdate',
'UNSAFE_componentWillUpdate',
'getSnapshotBeforeUpdate',
'componentDidUpdate',
'componentDidCatch',
'componentWillUnmount'
]
}
}
```

* `static-methods` is a special keyword that refers to static class methods.
* `lifecycle` is referring to the `lifecycle` group defined in `groups`.
* `everything-else` is a special group that match all the methods that do not match any of the other groups.
* `render` is referring to the `render` method.
* `type-annotations`. This group is not specified by default, but can be used to enforce flow annotations positioning.
* `getters` This group is not specified by default, but can be used to enforce class getters positioning.
* `setters` This group is not specified by default, but can be used to enforce class setters positioning.
* `instance-variables` This group is not specified by default, but can be used to enforce all other instance variables positioning.
* `instance-methods` This group is not specified by default, but can be used to enforce all other instance methods positioning.
* `lifecycle` refers to the `lifecycle` group defined in `groups`.
* `everything-else` is a special group that matches all of the methods that do not match any of the other groups.
* `render` refers to the `render` method.
* `type-annotations`. This group is not specified by default, but can be used to enforce flow annotations' positioning.
* `getters` This group is not specified by default, but can be used to enforce class getters' positioning.
* `setters` This group is not specified by default, but can be used to enforce class setters' positioning.
* `instance-variables` This group is not specified by default, but can be used to enforce all other instance variables' positioning.
* `instance-methods` This group is not specified by default, but can be used to enforce all other instance methods' positioning.

You can override this configuration to match your needs.

Expand Down Expand Up @@ -217,7 +223,6 @@ class Hello extends React.Component<any, Props, void> {
}
```


## When Not To Use It

This rule is a formatting preference and not following it won't negatively affect the quality of your code. If components organisation isn't a part of your coding standards, then you can leave this rule off.
22 changes: 13 additions & 9 deletions lib/rules/sort-comp.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,18 @@ const defaultConfig = {
'state',
'getInitialState',
'getChildContext',
'getDerivedStateFromProps',
'componentWillMount',
'UNSAFE_componentWillMount',
'componentDidMount',
'componentWillReceiveProps',
'UNSAFE_componentWillReceiveProps',
'shouldComponentUpdate',
'componentWillUpdate',
'UNSAFE_componentWillUpdate',
'getSnapshotBeforeUpdate',
'componentDidUpdate',
'componentDidCatch',
'componentWillUnmount'
]
}
Expand Down Expand Up @@ -131,13 +137,6 @@ module.exports = {
let j;
const indexes = [];

if (method.static) {
const staticIndex = methodsOrder.indexOf('static-methods');
if (staticIndex >= 0) {
indexes.push(staticIndex);
}
}

if (method.getter) {
const getterIndex = methodsOrder.indexOf('getters');
if (getterIndex >= 0) {
Expand All @@ -159,8 +158,6 @@ module.exports = {
}
}

// Either this is not a static method or static methods are not specified
// in the methodsOrder.
if (indexes.length === 0) {
for (i = 0, j = methodsOrder.length; i < j; i++) {
isRegExp = methodsOrder[i].match(regExpRegExp);
Expand All @@ -175,6 +172,13 @@ module.exports = {
}
}

if (method.static) {
const staticIndex = methodsOrder.indexOf('static-methods');
if (staticIndex >= 0) {
indexes.push(staticIndex);
}
}

if (indexes.length === 0 && method.instanceVariable) {
const annotationIndex = methodsOrder.indexOf('instance-variables');
if (annotationIndex >= 0) {
Expand Down
71 changes: 71 additions & 0 deletions tests/lib/rules/sort-comp.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,77 @@ ruleTester.run('sort-comp', rule, {
'everything-else'
]
}]
}, {
// Must validate a full React 16.3 createReactClass class
code: [
'var Hello = createReactClass({',
' displayName : \'\',',
' propTypes: {},',
' contextTypes: {},',
' childContextTypes: {},',
' mixins: [],',
' statics: {},',
' getDefaultProps: function() {},',
' getInitialState: function() {},',
' getChildContext: function() {},',
' UNSAFE_componentWillMount: function() {},',
' componentDidMount: function() {},',
' UNSAFE_componentWillReceiveProps: function() {},',
' shouldComponentUpdate: function() {},',
' UNSAFE_componentWillUpdate: function() {},',
' getSnapshotBeforeUpdate: function() {},',
' componentDidUpdate: function() {},',
' componentDidCatch: function() {},',
' componentWillUnmount: function() {},',
' render: function() {',
' return <div>Hello</div>;',
' }',
'});'
].join('\n')
}, {
// Must validate React 16.3 lifecycle methods with the default parser
code: [
'class Hello extends React.Component {',
' constructor() {}',
' static getDerivedStateFromProps() {}',
' UNSAFE_componentWillMount() {}',
' componentDidMount() {}',
' UNSAFE_componentWillReceiveProps() {}',
' shouldComponentUpdate() {}',
' UNSAFE_componentWillUpdate() {}',
' getSnapshotBeforeUpdate() {}',
' componentDidUpdate() {}',
' componentDidCatch() {}',
' componentWillUnmount() {}',
' testInstanceMethod() {}',
' render() { return (<div>Hello</div>); }',
'}'
].join('\n')
}, {
// Must validate a full React 16.3 ES6 class
code: [
'class Hello extends React.Component {',
' static displayName = \'\'',
' static propTypes = {}',
' static defaultProps = {}',
' constructor() {}',
' state = {}',
' static getDerivedStateFromProps = () => {}',
' UNSAFE_componentWillMount = () => {}',
' componentDidMount = () => {}',
' UNSAFE_componentWillReceiveProps = () => {}',
' shouldComponentUpdate = () => {}',
' UNSAFE_componentWillUpdate = () => {}',
' getSnapshotBeforeUpdate = () => {}',
' componentDidUpdate = () => {}',
' componentDidCatch = () => {}',
' componentWillUnmount = () => {}',
' testArrowMethod = () => {}',
' testInstanceMethod() {}',
' render = () => (<div>Hello</div>)',
'}'
].join('\n'),
parser: 'babel-eslint'
}, {
// Must allow us to create a RegExp-based group
code: [
Expand Down

0 comments on commit 768013e

Please sign in to comment.