diff --git a/crates/oxc_linter/src/rules.rs b/crates/oxc_linter/src/rules.rs
index 344112dce1395c..ccad7740e77d7a 100644
--- a/crates/oxc_linter/src/rules.rs
+++ b/crates/oxc_linter/src/rules.rs
@@ -227,6 +227,7 @@ mod react {
pub mod jsx_no_target_blank;
pub mod jsx_no_undef;
pub mod jsx_no_useless_fragment;
+ pub mod jsx_props_no_spread_multi;
pub mod no_children_prop;
pub mod no_danger;
pub mod no_direct_mutation_state;
@@ -733,6 +734,7 @@ oxc_macros::declare_all_lint_rules! {
react::jsx_no_comment_textnodes,
react::jsx_no_duplicate_props,
react::jsx_no_useless_fragment,
+ react::jsx_props_no_spread_multi,
react::jsx_no_undef,
react::react_in_jsx_scope,
react::no_children_prop,
diff --git a/crates/oxc_linter/src/rules/react/jsx_props_no_spread_multi.rs b/crates/oxc_linter/src/rules/react/jsx_props_no_spread_multi.rs
new file mode 100644
index 00000000000000..a7d22788416ff5
--- /dev/null
+++ b/crates/oxc_linter/src/rules/react/jsx_props_no_spread_multi.rs
@@ -0,0 +1,95 @@
+use std::collections::HashSet;
+
+use oxc_ast::{ast::JSXAttributeItem, AstKind};
+use oxc_diagnostics::OxcDiagnostic;
+use oxc_macros::declare_oxc_lint;
+use oxc_span::Span;
+
+use crate::{context::LintContext, rule::Rule, AstNode};
+
+fn jsx_props_no_spread_multi_diagnostic(span0: Span) -> OxcDiagnostic {
+ OxcDiagnostic::warn("Disallow JSX prop spreading the same identifier multiple times.")
+ .with_help("Remove duplicate spread attributes.")
+ .with_label(span0)
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct JsxPropsNoSpreadMulti;
+
+declare_oxc_lint!(
+ /// ### What it does
+ /// Enforces that any unique expression is only spread once.
+ /// Generally spreading the same expression twice is an indicator of a mistake since any attribute between the spreads may be overridden when the intent was not to.
+ /// Even when that is not the case this will lead to unnecessary computations being performed.
+ ///
+ /// ### Example
+ /// ```javascript
+ /// // Bad
+ ///
+ ///
+ /// // Good
+ ///
+ ///
+ /// ```
+ JsxPropsNoSpreadMulti,
+ correctness,
+);
+
+impl Rule for JsxPropsNoSpreadMulti {
+ fn run<'a>(&self, node: &AstNode<'a>, ctx: &LintContext<'a>) {
+ if let AstKind::JSXOpeningElement(jsx_opening_el) = node.kind() {
+ let spread_attrs = jsx_opening_el.attributes.iter().filter_map(|attr| {
+ if let JSXAttributeItem::SpreadAttribute(spread_attr) = attr {
+ if spread_attr.argument.is_identifier_reference() {
+ return Some(spread_attr);
+ }
+ }
+ None
+ });
+
+ let mut identifier_names = HashSet::new();
+
+ for spread_attr in spread_attrs {
+ let identifier_name =
+ &spread_attr.argument.get_identifier_reference().unwrap().name;
+ if !identifier_names.insert(identifier_name) {
+ ctx.diagnostic(jsx_props_no_spread_multi_diagnostic(spread_attr.span));
+ }
+ }
+ }
+ }
+}
+
+#[test]
+fn test() {
+ use crate::tester::Tester;
+
+ let pass = vec![
+ "
+ const a = {};
+
+ ",
+ "
+ const a = {};
+ const b = {};
+
+ ",
+ ];
+
+ let fail = vec![
+ "
+ const props = {};
+
+ ",
+ r#"
+ const props = {};
+
+ "#,
+ "
+ const props = {};
+
+ ",
+ ];
+
+ Tester::new(JsxPropsNoSpreadMulti::NAME, pass, fail).test_and_snapshot();
+}
diff --git a/crates/oxc_linter/src/snapshots/jsx_props_no_spread_multi.snap b/crates/oxc_linter/src/snapshots/jsx_props_no_spread_multi.snap
new file mode 100644
index 00000000000000..c9b6d3264c914f
--- /dev/null
+++ b/crates/oxc_linter/src/snapshots/jsx_props_no_spread_multi.snap
@@ -0,0 +1,38 @@
+---
+source: crates/oxc_linter/src/tester.rs
+---
+ ⚠ eslint-plugin-react(jsx-props-no-spread-multi): Disallow JSX prop spreading the same identifier multiple times.
+ ╭─[jsx_props_no_spread_multi.tsx:3:27]
+ 2 │ const props = {};
+ 3 │
+ · ──────────
+ 4 │
+ ╰────
+ help: Remove duplicate spread attributes.
+
+ ⚠ eslint-plugin-react(jsx-props-no-spread-multi): Disallow JSX prop spreading the same identifier multiple times.
+ ╭─[jsx_props_no_spread_multi.tsx:3:33]
+ 2 │ const props = {};
+ 3 │
+ · ──────────
+ 4 │
+ ╰────
+ help: Remove duplicate spread attributes.
+
+ ⚠ eslint-plugin-react(jsx-props-no-spread-multi): Disallow JSX prop spreading the same identifier multiple times.
+ ╭─[jsx_props_no_spread_multi.tsx:3:27]
+ 2 │ const props = {};
+ 3 │
+ · ──────────
+ 4 │
+ ╰────
+ help: Remove duplicate spread attributes.
+
+ ⚠ eslint-plugin-react(jsx-props-no-spread-multi): Disallow JSX prop spreading the same identifier multiple times.
+ ╭─[jsx_props_no_spread_multi.tsx:3:38]
+ 2 │ const props = {};
+ 3 │
+ · ──────────
+ 4 │
+ ╰────
+ help: Remove duplicate spread attributes.