---
title: "react/only-export-components"
description: "Require files with components to export only components, so Fast Refresh works"
# Generated by src/codegen/lint-rule-docs.ts. Do not edit below this line.
plugin: "react"
aliases: []
kind: "suggestion"
category: "restriction"
default: "off"
fixable: "no"
typeAware: false
deprecated: false
---

Fast Refresh swaps an edited component into the running page and keeps its state. It can only do that for a file where every export is a component. If the file also exports a function, an object, or a context, each edit reloads the whole page and the state is lost.

The rule only checks `.jsx` and `.tsx` files. It skips files with `.test.`, `.spec.`, `.cy.`, or `.stories.` in their name. An export counts as a component if its name starts with a capital letter and its value can be one, such as a function, a class that extends `Component`, or a call to `memo`, `forwardRef`, or `lazy`.

{/* Generated by src/codegen/lint-rule-docs.ts. Do not edit this section. */}

## Configure

Off by default. To turn it on:

```toml bunfig.toml
[lint.rules]
"react/only-export-components" = "error" # or "warn"
```

Every option, set to its default:

```toml bunfig.toml
[lint.rules]
"react/only-export-components" = ["error", {
  allowConstantExport = false,
  allowExportNames = [], # list of strings
  allowCompoundComponents = false,
  customHOCs = [], # list of strings
  checkJS = false,
}]
```

{/* End of the generated section. */}

## Examples

Reported:

{/* bad */}
```jsx
export function formatPrice(cents) {
  return `$${(cents / 100).toFixed(2)}`;
}

export function Price({ cents }) {
  return <span>{formatPrice(cents)}</span>;
}
```

The rule also reports a component without a name, `export *`, a context next to a component, and a component in a file that doesn't export any components:

{/* bad */}
```jsx
export default () => <p>Loading</p>;
```

{/* bad */}
```jsx
export * from "./buttons";
```

{/* bad */}
```jsx
import { createContext } from "react";

export const ThemeContext = createContext("light");

export function ThemeLabel({ theme }) {
  return <span>{theme}</span>;
}
```

{/* bad */}
```jsx
const Row = ({ item }) => <li>{item.name}</li>;

export const renderRows = items => items.map(item => <Row key={item.id} item={item} />);
```

Not reported:

{/* file "format.js" */}
```js
export function formatPrice(cents) {
  return `$${(cents / 100).toFixed(2)}`;
}
```

{/* good */}
```jsx
import { memo } from "react";
import { formatPrice } from "./format.js";

export function Price({ cents }) {
  return <span>{formatPrice(cents)}</span>;
}

export const Total = memo(function Total({ cents }) {
  return <strong>{formatPrice(cents)}</strong>;
});
```

## Options

| Option | Default | What it does |
| --- | --- | --- |
| `allowConstantExport` | `false` | Allow exports of strings, numbers, booleans, templates, and expressions like `60 * 1000`. |
| `allowExportNames` | `[]` | Names that a file can always export, such as `loader` or `meta` in a framework's route files. |
| `allowCompoundComponents` | `false` | Treat an object where every property is a component as a component. |
| `customHOCs` | `[]` | Functions that return a component, in addition to `memo`, `forwardRef`, and `lazy`. |
| `checkJS` | `false` | Also check `.js` files. With this option, only files that import something from `"react"` are checked. |

Not reported with `allowConstantExport: true`:

{/* good {"options": [{"allowConstantExport": true}]} */}
```jsx
export const PAGE_SIZE = 20;

export function Page({ items }) {
  return <ul>{items.slice(0, PAGE_SIZE).map(item => <li key={item.id}>{item.name}</li>)}</ul>;
}
```

With `allowExportNames: ["loader"]`:

{/* good {"options": [{"allowExportNames": ["loader"]}]} */}
```jsx
export async function loader() {
  return fetch("/api/user");
}

export function Profile({ user }) {
  return <h1>{user.name}</h1>;
}
```

With `allowCompoundComponents: true`:

{/* good {"options": [{"allowCompoundComponents": true}]} */}
```jsx
const Root = ({ children }) => <div role="tablist">{children}</div>;
const Tab = ({ label }) => <button role="tab">{label}</button>;

export const Tabs = { Root, Tab };
```

With `customHOCs: ["observer"]`:

{/* good {"options": [{"customHOCs": ["observer"]}]} */}
```jsx
import { observer } from "mobx-react-lite";

export default observer(function Cart({ cart }) {
  return <span>{cart.count}</span>;
});
```
