Skip to main content
ESLint Interlace
Plugin: react-featuresRules

no-wrapper-sub-component

Ban pure-passthrough wrapper sub-components — slot the primitive directly (R12)

Part of: componentApi preset (opt-in — not in recommended)

Ban pure-passthrough wrapper sub-components — function components whose entire body is return <Primitive {...props} />;. These wrappers add no structural behavior and force consumers to import through an indirection layer. Consumers should slot the primitive directly.

A wrapper is exempt if it adds at least one literal attribute (className, data-slot, aria-*, role) or includes hook calls / computed values before returning — those are structural behaviors.

Why This Matters

IssueImpactSolution
Extra indirectionConsumers pay an import tax for a no-op wrapperDelete the wrapper; export the primitive directly
Tree complexityAn extra component node in devtools and the React treeSlot the primitive; the call site is the composition point
API surface noiseThe sub-component name implies behavior that doesn't existIf the sub-component must exist, give it structural behavior

Examples

Incorrect

// Pure passthrough — adds nothing
function DialogTrigger(props) {
  return <Trigger {...props} />;  // ❌ R12
}

// Arrow form
const DialogTrigger = (props) => <Trigger {...props} />;  // ❌ R12

Correct

// Structural behavior added — data-slot makes this a real composition part
function DialogTrigger(props) {
  return <Trigger data-slot="trigger" {...props} />;  // ✅ adds behavior
}

// Or simply re-export the primitive directly
export { Trigger as DialogTrigger };

Configuration

// eslint.config.mjs
export default [
  {
    rules: {
      "react-features/no-wrapper-sub-component": "error",
    },
  },
];

This rule is part of eslint-plugin-react-features.