feat: add react/no-did-mount-set-state lint rule
MODIFY
packages/@romejs/diagnostics/categories.ts
packages/@romejs/diagnostics/categories.ts
@@ -162,6 +162,7 @@ type LintDiagnosticCategory =
| "lint/react/noChildrenProp"
| "lint/react/noDanger"
| "lint/react/noDangerWithChildren"
+ | "lint/react/noDidMountSetState"
| "lint/react/noDidUpdateSetState"
| "lint/react/noFindDOMNode"
| "lint/react/noWillUpdateSetState"

MODIFY
packages/@romejs/diagnostics/descriptions.ts
packages/@romejs/diagnostics/descriptions.ts
@@ -292,6 +292,10 @@ export const descriptions = createMessages({
},
// @romejs/js-compiler
LINT: {
+ REACT_NO_DID_MOUNT_SET_STATE: {
+ category: "lint/react/noDidMountSetState",
+ message: "Avoid <emphasis>this.setState</emphasis> in <emphasis>componentDidMount</emphasis>. This can cause an unexpected second render, which can cause visual layout thrashing.",
+ },
REACT_BUTTON_HAS_TYPE: {
category: "lint/react/buttonHasType",
message: `Use an explicit <emphasis>type</emphasis> prop on <emphasis>${escapeMarkup(

MODIFY
packages/@romejs/js-compiler/lint/rules/index.ts
packages/@romejs/js-compiler/lint/rules/index.ts
@@ -76,6 +76,7 @@ import jsxNoCommentText from "./react/jsxNoCommentText";
import noChildrenProp from "./react/noChildrenProp";
import noDanger from "./react/noDanger";
import noDangerWithChildren from "./react/noDangerWithChildren";
+import noDidMountSetState from "./react/noDidMountSetState";
import noDidUpdateSetState from "./react/noDidUpdateSetState";
import noFindDOMNode from "./react/noFindDOMNode";
import noWillUpdateSetState from "./react/noWillUpdateSetState";
@@ -155,6 +156,7 @@ export const lintTransforms = [
noChildrenProp,
noDanger,
noDangerWithChildren,
+ noDidMountSetState,
noDidUpdateSetState,
noFindDOMNode,
noWillUpdateSetState,

ADD
packages/@romejs/js-compiler/lint/rules/react/noDidMountSetState.test.ts
+import {test} from "rome";
+import {testLintMultiple} from "../testHelpers";
+
+test(
+ "no did mount set state",
+ async (t) => {
+ await testLintMultiple(
+ t,
+ [
+ // INVALID
+ `
+ class Hello extends React.Component {
+ componentDidMount() {
+ this.setState({
+ name: 'John'
+ });
+ }
+ }
+ `,
+ `
+ class Hello extends React.Component {
+ componentDidMount() {
+ foo();
+ this.setState({
+ name: 'John'
+ });
+ }
+ }
+ `,
+ `
+ class Hello extends Component {
+ componentDidMount() {
+ this.setState({
+ name: 'John'
+ });
+ }
+ }
+ `,
+ `
+ class Hello extends Component {
+ componentDidMount() {
+ foo();
+ this.setState({
+ name: 'John'
+ });
+ }
+ }
+ `,
+ // VALID
+ `
+ class Hello extends React.Component {
+ componentDidMount() {
+ if (condition) {
+ this.setState({
+ name: 'John'
+ });
+ }
+ }
+ }
+ `,
+ ],
+ {category: "lint/react/noDidMountSetState"},
+ );
+ },
+);

ADD
packages/@romejs/js-compiler/lint/rules/react/noDidMountSetState.ts
+import {Path, TransformExitResult} from "@romejs/js-compiler";
+import {descriptions} from "@romejs/diagnostics";
+import {doesNodeMatchPattern, isConditional} from "@romejs/js-ast-utils";
+
+function inComponentDidMount(path: Path): boolean {
+ const func = path.findAncestry(({node}) => isConditional(node)) !== undefined;
+ return (
+ !func &&
+ !!path.findAncestry(({node}) =>
+ node.type === "JSClassMethod" &&
+ node.key.type === "JSStaticPropertyKey" &&
+ node.key.value.type === "JSIdentifier" &&
+ node.key.value.name === "componentDidMount"
+ )
+ );
+}
+
+export default {
+ name: "noDidMountSetState",
+ enter(path: Path): TransformExitResult {
+ const {node} = path;
+
+ if (doesNodeMatchPattern(node, "this.setState") && inComponentDidMount(path)) {
+ path.context.addNodeDiagnostic(
+ node,
+ descriptions.LINT.REACT_NO_DID_MOUNT_SET_STATE,
+ );
+ }
+
+ return node;
+ },
+};

feat: add react/no-did-mount-set-state lint rule (#489)

Co-authored-by: Sebastian <853712+sebmck@users.noreply.github.com>