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.
Configure#
Off by default. To turn it on:
[lint.rules]
"react/only-export-components" = "error" # or "warn"Every option, set to its default:
[lint.rules]
"react/only-export-components" = ["error", {
allowConstantExport = false,
allowExportNames = [], # list of strings
allowCompoundComponents = false,
customHOCs = [], # list of strings
checkJS = false,
}]Examples#
export function formatPriceFast refresh only works when a file only exports components. Use a new file to share constants or functions between components.(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:
export default () => <p>Loading</p>;Fast refresh can't handle anonymous components. Add a name to your export.export * from "./buttons";This rule can't verify that export * only exports components.import { createContext } from "react";
export const ThemeContextFast refresh only works when a file only exports components. Move your React context(s) to a separate file. = createContext("light");
export function ThemeLabel({ theme }) {
return <span>{theme}</span>;
}const RowFast refresh only works when a file only exports components. Move your component(s) to a separate file. = ({ item }) => <li>{item.name}</li>;
export const renderRows = items => items.map(item => <Row key={item.id} item={item} />);Not reported:
export function formatPrice(cents) {
return `$${(cents / 100).toFixed(2)}`;
}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:
{ "allowConstantExport": true }No problemsexport 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"]:
{ "allowExportNames": ["loader"] }No problemsexport async function loader() {
return fetch("/api/user");
}
export function Profile({ user }) {
return <h1>{user.name}</h1>;
}With allowCompoundComponents: true:
{ "allowCompoundComponents": true }No problemsconst Root = ({ children }) => <div role="tablist">{children}</div>;
const Tab = ({ label }) => <button role="tab">{label}</button>;
export const Tabs = { Root, Tab };With customHOCs: ["observer"]:
{ "customHOCs": ["observer"] }No problemsimport { observer } from "mobx-react-lite";
export default observer(function Cart({ cart }) {
return <span>{cart.count}</span>;
});