lightsoutAlpha

react-size-thresholds

a component or hook measured as the wrong kind, judged against the base threshold instead of its own, or past its own

Agent checkadvisory by defaultreactcode

The argument

React - Function Size Limits

The base function size thresholds are defined in code:style-guide/references/patterns/functions.md. The overrides below apply to the file types they specify — when a file matches a classification here, use these thresholds instead of the base.

File Classification

  • .tsx files with a named/default export returning JSX → Component (use component thresholds)
  • .ts files exporting a function starting with use → Hook (use hook thresholds)
  • Everything else → Utility (50-line threshold applies)

Line Counting

Count from function signature to closing brace. Exclude imports, type declarations outside the function, and file-level comments.

Components (.tsx)

LinesAssessment
<100Almost always fine
100–150Review — acceptable if mostly JSX composition with no inline logic
150+Likely needs extraction
200+Definitely needs extraction

Hooks (.ts)

LinesAssessment
<80Fine
80–120Review — look for extractable pure logic
120+Likely needs utility extraction
160+Definitely needs extraction

Pure logic inside hooks should be extracted to utility functions. The hook itself should compose, not compute.

Default Actions — Components & Hooks

Issue TypeDefault ActionReview Level
Component >200 linesExtract sub-componentsMedium
Hook >160 linesExtract pure logic to utilitiesMedium
Inline styles / repeated className logicExtract to shared class or componentLow

The proof

fail
interface Issue {
	id: string;
	title: string;
	state: string;
	assignee: string | null;
}

interface Props {
	issues: Issue[];
}

// Past the threshold with inline logic rather than JSX composition: the header,
// the filters and the row rendering are three sub-components waiting to be cut
// out of it.
export const IssuePanel = ({ issues }: Props) => {
	const open = issues.filter((issue) => issue.state === 'open');
	const closed = issues.filter((issue) => issue.state === 'closed');
	const unassigned = open.filter((issue) => issue.assignee === null);
	const byAssignee: Record<string, Issue[]> = {};

	for (const issue of open) {
		const key = issue.assignee ?? 'unassigned';

		byAssignee[key] = [...(byAssignee[key] ?? []), issue];
	}

	return (
		<section>
			<header>
				<h2>Issues</h2>
				<p>
					{open.length} open, {closed.length} closed, {unassigned.length} unassigned
				</p>
			</header>
			{Object.entries(byAssignee).map(([assignee, assigned]) => (
				<div key={assignee}>
					<h3>{assignee}</h3>
					<ul>
						{assigned.map((issue) => (
							<li key={issue.id}>
								{issue.title} — {issue.state}
							</li>
						))}
					</ul>
				</div>
			))}
		</section>
	);
};
pass
interface Issue {
	id: string;
	state: string;
	amount: number;
}

export const buildIssueSummary = ({ issues }: { issues: Issue[] }): Record<string, number> => {
	const totals: Record<string, number> = {};

	for (const issue of issues) {
		totals[issue.state] = (totals[issue.state] ?? 0) + issue.amount;
	}

	return totals;
};

Turn it down

Both lines go in your lightsout.config.json.

"standards-checks": { "react-size-thresholds": "advisory" }
"standards-checks": { "react-size-thresholds": "off" }