Skip to content

react/only-export-components Restriction ​

What it does ​

Ensures that modules only export React components (and related HMR-safe items) so that Fast Refresh (a.k.a. hot reloading) can safely preserve component state. Concretely, it validates the shape of your module’s exports and common entrypoints (e.g. createRoot(...).render(<App />)) to match what integrations like react-refresh expect. The rule name is react-refresh/only-export-components.

Why is this bad? ​

Fast Refresh can only reliably retain state if a module exports components and avoids patterns that confuse the refresh runtime. Problematic patterns (like export *, anonymous default functions, exporting arrays of JSX, or mixing non-component exports in unsupported ways) can cause:

  • Components to remount and lose state on edit
  • Missed updates (no refresh) or overly broad reloads
  • Fragile HMR behavior that differs between bundlers

By enforcing predictable exports, edits stay fast and stateful during development.

Examples ​

Examples of incorrect code for this rule:

jsx
// 1) Mixing util exports with components in unsupported ways
export const foo = () => {}; // util, not a component
export const Bar = () => <></>; // component
jsx
// 2) Anonymous default export (name is required)
export default function() {}
jsx
// 3) Re-exporting everything hides what’s exported
export * from "./foo";
jsx
// 4) Exporting JSX collections makes components non-discoverable
const Tab = () => null;
export const tabs = [<Tab />, <Tab />];
jsx
// 5) Bootstrapping a root within the same module that defines components
const App = () => null;
createRoot(document.getElementById("root")).render(<App />);

Examples of correct code for this rule:

jsx
// Named or default component exports are fine
export default function Foo() {
  return null;
}
jsx
// Utilities may coexist if allowed by options or naming conventions
const foo = () => {};
export const Bar = () => null;
jsx
// Entrypoint files may render an imported component
import { App } from "./App";
createRoot(document.getElementById("root")).render(<App />);

Options (or not) ​

allowExportNames ​

{ type: string[], default: [] }

Treat specific named exports as HMR-safe (useful for frameworks that hot-replace certain exports). For example, in Remix:

json
{
  "react/only-export-components": [
    "error",
    { "allowExportNames": ["meta", "links", "headers", "loader", "action"] }
  ]
}

allowConstantExport ​

{ type: boolean, default: false }

Allow exporting primitive constants (string/number/boolean/template literal) alongside component exports without triggering a violation. Recommended when your bundler’s Fast Refresh integration supports this (enabled by the plugin’s vite preset).

json
{
  "react/only-export-components": [
    "error",
    { "allowConstantExport": true }
  ]
}
jsx
// Allowed when allowConstantExport: true
export const VERSION = "3";
export const Foo = () => null;

customHOCs ​

{ type: string[], default: [] }

If you export components wrapped in custom higher-order components, list their identifiers here to avoid false positives:

json
{
  "react/only-export-components": [
    "error",
    { "customHOCs": ["observer", "withAuth"] }
  ]
}

checkJS ​

{ type: boolean, default: false }

Check .js files that contain JSX (in addition to .tsx/.jsx). To reduce false positives, only files that import React are checked when this is enabled.

json
{
  "react/only-export-components": ["error", { "checkJS": true }]
}

How to use ​

To enable this rule in the CLI or using the config file, you can use:

bash
oxlint --deny react/only-export-components --react-plugin
json
{
  "plugins": ["react"],
  "rules": {
    "react/only-export-components": "error"
  }
}

References ​

Released under the MIT License.