# Code Conventions This document outlines the coding patterns and conventions used in this project. ## Component Organization ### Feature Folder Pattern Components that represent a feature or screen should use a folder structure with a barrel export. ``` Feature/ ├── index.tsx # exports from Feature.tsx ├── Feature.tsx # main component ├── SubComponent.tsx └── utils.ts ``` **index.tsx:** ```tsx export * from './Feature'; ``` **Rationale:** Keeps imports clean (`import { Feature } from './Feature'`) while allowing the feature to grow into multiple files without changing import paths. Subcomponents that only serve this feature live in the same directory rather than being abstracted to a shared components folder. ### Types Alongside Components Export component prop types from the same file as the component. ```tsx export type FeatureProps = { value: string; onChange: (value: string) => void; }; export const Feature = ({ value, onChange }: FeatureProps) => { // ... }; ``` **Rationale:** Keeps types discoverable and colocated with their usage. Consumers can import both the component and its types from the same path. ## State Management ### Manager Pattern for Hooks Hooks that manage complex state should return a "manager" object. Components receive this manager as a prop. ```tsx // Hook export const useFeatureManager = () => { const [state, setState] = useState(''); const [filter, setFilter] = useState(''); const filteredItems = (() => { // derived state computation })(); return { state, setState, filter, setFilter, filteredItems, }; }; export type FeatureManager = ReturnType; // Parent component const Parent = () => { const manager = useFeatureManager(); return ; }; // Child component const Child = ({ manager }: { manager: FeatureManager }) => { const { state, filteredItems } = manager; // ... }; ``` **Rationale:** Centralizes state logic in one place. Child components don't need to know about individual state setters - they just receive the manager. Makes refactoring easier since state shape changes only affect the hook. ### State Colocation in Hooks All local state (search, filters, pagination, expanded states) should live in the feature's hook, not scattered across components. ```tsx // Good export const useFeatureManager = () => { const [search, setSearch] = useState(''); const [currentPage, setCurrentPage] = useState(1); const [expandedId, setExpandedId] = useState(null); // ... }; // Avoid const Component = () => { const [search, setSearch] = useState(''); // state in component // ... }; ``` **Rationale:** Single source of truth for feature state. Easier to understand, test, and modify. Prevents state synchronization bugs. ### Derived State in Hooks Computed/derived values should be calculated in the hook, not in components. ```tsx export const useFeatureManager = () => { const [items, setItems] = useState([]); const [filter, setFilter] = useState(''); // Derived state computed in hook const filteredItems = (() => { return items.filter((item) => item.name.includes(filter)); })(); const itemCount = filteredItems.length; return { items, filter, setFilter, filteredItems, itemCount }; }; ``` **Rationale:** Components stay focused on rendering. Business logic stays in one place. Derived values are computed once and shared across all consuming components. ## React Patterns ### Reach for useMemo / useCallback only when they do work Default to writing the code plainly. Most derived values are cheap and re-computing them per render costs less than the memo that guards them. ```tsx // ✅ Fine — cheap, so just write it const filteredItems = items.filter((item) => item.active); const handleClick = () => doSomething(); ``` But they are ordinary tools, not forbidden ones. Use them where they earn it: - a value or callback in a **dependency array**, where an unstable identity re-runs an effect or re-subscribes a socket every render; - a genuinely **expensive** computation over a large list; - a prop passed to a **memoized** child. ```tsx // ✅ Earns it — an unstable callback here would re-subscribe on every render const onConnectionChange = useCallback((state) => setConnState(state), [setConnState]); ``` **This section used to say "NEVER — React 19's compiler handles memoization automatically", which was wrong twice.** The React Compiler is a separate, opt-in build plugin and it is **not installed here** (there is no `babel-plugin-react-compiler` in `package.json`); React 19 on its own memoizes nothing. And the codebase never followed the rule — some 40 files use each hook. A convention that is both false and universally ignored is worse than none, because it makes every other rule in this file look optional. ### Computation Functions Outside Components Extract complex computations into functions declared below the component, not as IIFEs inside. ```tsx export const Component = ({ data }: Props) => { const metrics = computeMetrics(data); const stats = computeStats(data, metrics.total); return
{/* ... */}
; }; // Functions below component function computeMetrics(data: Data[]): Metrics { // complex computation } function computeStats(data: Data[], total: number): Stats { // complex computation } ``` **Rationale:** Keeps the component body focused on rendering logic. Functions are testable in isolation. Easier to read and understand the component's purpose. ### Fragment Shorthand Use `<>` for fragments. Only import and use `Fragment` when a `key` prop is required. ```tsx // Good - no key needed return ( <>
); // Good - key required import { Fragment } from 'react'; return items.map((item) => ( )); // Avoid - unnecessary Fragment import import { Fragment } from 'react'; return (
); ``` **Rationale:** `<>` is cleaner and more concise. `Fragment` is only needed for the `key` prop which `<>` doesn't support. ## Imports ### Single-Line Imports Keep imports on a single line. If an import has too many items, split into multiple import statements. ```tsx // Good import { Button, Input, Select } from '@/components/ui'; import { Card, CardHeader, CardContent } from '@/components/ui/card'; // Good - split when too long import { TableBody, TableCell, TableHead } from '@/components/ui/table'; import { TableHeader, TableRow } from '@/components/ui/table'; // Avoid - multiline imports import { Button, Input, Select, Card } from '@/components/ui'; ``` **Rationale:** Single-line imports are easier to scan and take less vertical space. Splitting by source module keeps related imports together. ## Utilities ### Use Existing Helpers Prefer existing helper functions over inline implementations. ```tsx // Good import { formatCurrency } from 'helpers/formatters'; const display = formatCurrency(amount / 100, currency, 0); // Avoid const display = new Intl.NumberFormat('en-US', { style: 'currency', currency: currency, minimumFractionDigits: 0, }).format(amount / 100); ``` **Rationale:** Consistent formatting across the app. Single place to modify behavior. Less code duplication and potential for bugs.