You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
-**Search patterns**: Multiple `useEffect` in single component, large component bodies mixing data access/navigation/UI state/lifecycle, hooks or utilities handling several unrelated responsibilities
1195
+
1196
+
-**Condition**: Flag when a component, hook, or utility aggregates multiple unrelated responsibilities in a single unit, making it difficult to modify one concern without touching others.
1197
+
1198
+
**Signs of violation:**
1199
+
- Component has several `useEffect` hooks handling unrelated concerns (e.g., telemetry, deep linking, audio, session management all in one component)
1200
+
- A single `useEffect` or hook handles multiple distinct responsibilities
1201
+
- Unrelated state variables are interdependent or updated together
1202
+
- Logic mixes data fetching, navigation, UI state, and lifecycle behavior in one place
1203
+
- Removing one piece of functionality requires careful untangling from others
1204
+
1205
+
**What counts as "unrelated":**
1206
+
- Group by responsibility (what the code does), NOT by timing (when it runs)
1207
+
- Data fetching and analytics are NOT related — they serve different purposes even if both run on mount
1208
+
- Session management and audio configuration are NOT related — different domains entirely
1209
+
1210
+
**DO NOT flag if:**
1211
+
- Component is a thin orchestration layer that ONLY composes child components (no business logic, no effects beyond rendering)
1212
+
- Effects are extracted into focused custom hooks with single responsibilities (e.g., `useDebugShortcut`, `usePriorityMode`) — inline `useEffect` calls are a code smell and should be named hooks
1213
+
1214
+
-**Reasoning**: When multiple unrelated responsibilities are grouped into a single component, hook, or utility, if any one concern changes, then unrelated logic must be touched as well, increasing coupling, regression risk, and cognitive load. This is the single responsibility principle for React: extract small units that do very little, very well. A component with several unrelated effects is a code smell - even a single effect can benefit from extraction to something with a good name, proper description, and isolated tests.
1215
+
1216
+
**Bucketing questions for refactoring:**
1217
+
1. Does this logic need the React render loop? YES → Extract to a focused custom hook. NO → Extract out of React entirely (e.g., Onyx migration, global initialization).
1218
+
2. Does this logic need to be in this component? YES → Keep it, but use a focused hook. NO → Extract to a separate component that owns this concern.
1219
+
1220
+
**Hook granularity guidance:**
1221
+
- Group effects that serve the same purpose into one hook (e.g., all telemetry setup in `useTelemetry`)
1222
+
- Group effects that can be reused together across components
1223
+
- Don't create 15 separate single-effect hooks if 5 well-named grouped hooks make more sense
1224
+
1225
+
Good (separated concerns):
1226
+
1227
+
- Each piece of logic is extracted to a focused hook or component
1228
+
- Parent component only orchestrates what to render
1229
+
- State subscriptions in smaller components don't cause re-renders in parent
1230
+
- Component-scoped hooks can be co-located in the same directory for maintainability
1231
+
1232
+
```tsx
1233
+
function DebugMenu() {
1234
+
useDebugShortcut();
1235
+
1236
+
return (
1237
+
// Debug menu UI
1238
+
);
1239
+
}
1240
+
1241
+
function ParentComponent({ reportID }: { reportID:string }) {
- The component handles telemetry, deep linking, audio, session, navigation, splash screen, and more
1339
+
- Each concern is interleaved with others, making it hard to modify one without risking regression in another
1340
+
- Effects could be extracted to focused hooks: `useTelemetrySpans`, `useDeepLinking`, `useAudioMode`, etc.
1341
+
- Entry points don't get special treatment — extracting effects into named hooks improves clarity and makes it possible to understand what each effect does and how to safely modify it
1342
+
1343
+
---
1344
+
1345
+
### [CLEAN-REACT-PATTERNS-5] Keep state and subscriptions narrow
1346
+
1347
+
-**Search patterns**: Contexts/hooks/stores exposing large bundled objects, providers with many unrelated `useOnyx` calls, state structures mixing unrelated concerns
1348
+
1349
+
-**Condition**: Flag when a state structure (context, hook, store, or subscription) bundles unrelated concerns together, causing consumers to re-render when data they don't use changes.
1350
+
1351
+
**Signs of violation:**
1352
+
- State provider (context, hook, or store) that bundles unrelated data (e.g., navigation state + list data + cache utilities in one structure)
1353
+
- State object where properties serve different purposes and change independently
1354
+
- Multiple unrelated subscriptions (`useOnyx`, `useContext`, store selectors) aggregated into a single exposed value
1355
+
- Consumers of a state source that only use a subset of the provided values
1356
+
1357
+
**DO NOT flag if:**
1358
+
- State values are cohesive — they change together and serve the same purpose (e.g., `keyboardHeight` + `isKeyboardShown` both relate to keyboard state)
1359
+
- The state structure is intentionally designed as an aggregation point and consumers use most/all values
1360
+
- Individual `useOnyx` calls without selectors — this is covered by [PERF-11]
1361
+
1362
+
-**Reasoning**: When unrelated pieces of data are grouped into a single state structure, if an unused part changes, then all consumers re-render unnecessarily. This silently expands render scope, increases coupling, and makes performance regressions hard to detect. Structuring state around cohesive concerns ensures render scope stays predictable and changes remain local.
1363
+
1364
+
**Distinction from PERF-11**: PERF-11 addresses individual `useOnyx` selector usage. This rule addresses state structure — how multiple values are grouped and exposed to consumers via contexts, hooks, or stores.
1365
+
1366
+
**Distinction from CLEAN-REACT-PATTERNS-2**: PATTERNS-2 addresses data flow direction — parent shouldn't fetch data just to pass to children. This rule addresses how state is structured and grouped within any state provider.
1367
+
1368
+
Good (cohesive state — all values serve one purpose):
1369
+
1370
+
- All state relates to one concern (keyboard)
1371
+
- Values change together — no wasted re-renders
1372
+
- Derived state computed inline, not stored separately
1373
+
1374
+
```tsx
1375
+
typeKeyboardStateContextValue= {
1376
+
isKeyboardShown:boolean;
1377
+
isKeyboardActive:boolean;
1378
+
keyboardHeight:number;
1379
+
};
1380
+
1381
+
function KeyboardStateProvider({children}:ChildrenProps) {
0 commit comments