# react/only-export-components

> Require files with components to export only components, so Fast Refresh works

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>;
});
```
