diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs
index b200941cf1d1c7..918918f0ce2981 100644
--- a/crates/oxc_linter/src/rules.rs
+++ b/crates/oxc_linter/src/rules.rs
@@ -217,6 +217,7 @@ mod react {
pub mod no_find_dom_node;
pub mod no_is_mounted;
pub mod no_render_return_value;
+ pub mod no_set_state;
pub mod no_string_refs;
pub mod no_unescaped_entities;
pub mod no_unknown_property;
@@ -687,6 +688,7 @@ oxc_macros::declare_all_lint_rules! {
react::no_direct_mutation_state,
react::no_find_dom_node,
react::no_render_return_value,
+ react::no_set_state,
react::no_string_refs,
react::no_unescaped_entities,
react::no_is_mounted,
diff --git a/crates/oxc_linter/src/rules/react/no_set_state.rs b/crates/oxc_linter/src/rules/react/no_set_state.rs
new file mode 100644
index 00000000000000..e9d660a62f8d8f
--- /dev/null
+++ b/crates/oxc_linter/src/rules/react/no_set_state.rs
@@ -0,0 +1,165 @@
+use oxc_ast::{ast::Expression, AstKind};
+use oxc_diagnostics::OxcDiagnostic;
+use oxc_macros::declare_oxc_lint;
+use oxc_span::{GetSpan, Span};
+
+use crate::{
+ context::LintContext,
+ rule::Rule,
+ utils::{get_parent_es5_component, get_parent_es6_component},
+ AstNode,
+};
+
+fn no_set_state_diagnostic(span0: Span) -> OxcDiagnostic {
+ OxcDiagnostic::warn("eslint-plugin-react(no-set-state): Do not use setState").with_label(span0)
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct NoSetState;
+
+declare_oxc_lint!(
+ /// ### What it does
+ ///
+ /// Disallow the usage of `this.setState` in React components.
+ ///
+ /// ### Why is this bad?
+ ///
+ /// When using an architecture that separates your application state from your UI components
+ /// (e.g. Flux), it may be desirable to forbid the use of local component state. This rule is
+ /// especially helpful in read-only applications (that don't use forms), since local component
+ /// state should rarely be necessary in such cases.
+ ///
+ /// ### Example
+ /// ```javascript
+ /// var Hello = createReactClass({
+ /// getInitialState: function() {
+ /// return {
+ /// name: this.props.name
+ /// };
+ /// },
+ /// handleClick: function() {
+ /// this.setState({
+ /// name: this.props.name.toUpperCase()
+ /// });
+ /// },
+ /// render: function() {
+ /// return
Hello {this.state.name}
;
+ /// }
+ /// });
+ /// ```
+ NoSetState,
+ style,
+);
+
+impl Rule for NoSetState {
+ fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
+ let AstKind::CallExpression(call_expr) = node.kind() else {
+ return;
+ };
+
+ let Some(member_expr) = call_expr.callee.as_member_expression() else {
+ return;
+ };
+
+ if !matches!(member_expr.object(), Expression::ThisExpression(_))
+ || !member_expr.static_property_name().is_some_and(|str| str == "setState")
+ || !(get_parent_es5_component(node, ctx).is_some()
+ || get_parent_es6_component(ctx).is_some())
+ {
+ return;
+ }
+
+ ctx.diagnostic(no_set_state_diagnostic(call_expr.callee.span()));
+ }
+}
+
+#[test]
+fn test() {
+ use crate::tester::Tester;
+
+ let pass = vec![
+ "
+ var Hello = function() {
+ this.setState({})
+ };
+ ",
+ "
+ var Hello = createReactClass({
+ render: function() {
+ return Hello {this.props.name}
;
+ }
+ });
+ ",
+ "
+ var Hello = createReactClass({
+ componentDidUpdate: function() {
+ someNonMemberFunction(arg);
+ this.someHandler = this.setState;
+ },
+ render: function() {
+ return Hello {this.props.name}
;
+ }
+ });
+ ",
+ ];
+
+ let fail = vec![
+ "
+ var Hello = createReactClass({
+ componentDidUpdate: function() {
+ this.setState({
+ name: this.props.name.toUpperCase()
+ });
+ },
+ render: function() {
+ return Hello {this.state.name}
;
+ }
+ });
+ ",
+ "
+ var Hello = createReactClass({
+ someMethod: function() {
+ this.setState({
+ name: this.props.name.toUpperCase()
+ });
+ },
+ render: function() {
+ return Hello {this.state.name}
;
+ }
+ });
+ ",
+ "
+ class Hello extends React.Component {
+ someMethod() {
+ this.setState({
+ name: this.props.name.toUpperCase()
+ });
+ }
+ render() {
+ return Hello {this.state.name}
;
+ }
+ };
+ ",
+ "
+ class Hello extends React.Component {
+ someMethod = () => {
+ this.setState({
+ name: this.props.name.toUpperCase()
+ });
+ }
+ render() {
+ return Hello {this.state.name}
;
+ }
+ };
+ ",
+ "
+ class Hello extends React.Component {
+ render() {
+ return this.setState({dropdownIndex: index})} />;
+ }
+ };
+ ",
+ ];
+
+ Tester::new(NoSetState::NAME, pass, fail).test_and_snapshot();
+}
diff --git a/crates/oxc_linter/src/snapshots/no_set_state.snap.new b/crates/oxc_linter/src/snapshots/no_set_state.snap.new
new file mode 100644
index 00000000000000..05bc3f6bb05143
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/no_set_state.snap.new
@@ -0,0 +1,43 @@
+---
+source: crates/oxc_linter/src/tester.rs
+assertion_line: 216
+---
+ ⚠ eslint-plugin-react(no-set-state): Do not use setState
+ ╭─[no_set_state.tsx:4:16]
+ 3 │ componentDidUpdate: function() {
+ 4 │ this.setState({
+ · ─────────────
+ 5 │ name: this.props.name.toUpperCase()
+ ╰────
+
+ ⚠ eslint-plugin-react(no-set-state): Do not use setState
+ ╭─[no_set_state.tsx:4:16]
+ 3 │ someMethod: function() {
+ 4 │ this.setState({
+ · ─────────────
+ 5 │ name: this.props.name.toUpperCase()
+ ╰────
+
+ ⚠ eslint-plugin-react(no-set-state): Do not use setState
+ ╭─[no_set_state.tsx:4:16]
+ 3 │ someMethod() {
+ 4 │ this.setState({
+ · ─────────────
+ 5 │ name: this.props.name.toUpperCase()
+ ╰────
+
+ ⚠ eslint-plugin-react(no-set-state): Do not use setState
+ ╭─[no_set_state.tsx:4:16]
+ 3 │ someMethod = () => {
+ 4 │ this.setState({
+ · ─────────────
+ 5 │ name: this.props.name.toUpperCase()
+ ╰────
+
+ ⚠ eslint-plugin-react(no-set-state): Do not use setState
+ ╭─[no_set_state.tsx:4:48]
+ 3 │ render() {
+ 4 │ return
this.setState({dropdownIndex: index})} />;
+ · ─────────────
+ 5 │ }
+ ╰────