# 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 ### No useMemo or useCallback (React 19) **NEVER** use `useMemo` or `useCallback`. React 19's compiler handles memoization automatically. Importing and using these hooks is strictly prohibited. ```tsx // ✅ Good - just write the code naturally const filteredItems = items.filter((item) => item.active); const handleClick = () => doSomething(); const stats = computeStats(data); // ❌ Never do this - remove all useMemo/useCallback const filteredItems = useMemo(() => items.filter((item) => item.active), [items]); const handleClick = useCallback(() => doSomething(), []); ``` **Rationale:** React 19's compiler optimizes re-renders automatically. Manual memoization adds complexity without benefit and can actually prevent optimizations. The compiler is smarter than manual memoization. ### 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.