|
<title>Strict Functional TypeScript/React Coding Standards</title> |
|
<overview> |
|
Based on CUPID (Composable, Unix philosophy, Predictable, Idiomatic, Domain-based) principles, express all logic as pure functions, control side effects explicitly, and maintain a type-safe, immutable codebase. |
|
</overview> |
|
<cupid> |
|
<principle name="Composable"> |
|
Divide every piece of logic into pure functions and combine them only via compose or pipe patterns, enforcing single responsibility and explicit side-effect control. |
|
</principle> |
|
<principle name="Unix philosophy"> |
|
Ensure each module or file implements exactly one responsibility, executing side effects only at well-defined entry points. |
|
</principle> |
|
<principle name="Predictable"> |
|
Guarantee that the same input always produces the same output by separating internal state and external dependencies explicitly. |
|
</principle> |
|
<principle name="Idiomatic"> |
|
Leverage TypeScript and React’s standard APIs and language features fully, respecting their intended patterns and avoiding custom reimplementations. |
|
</principle> |
|
<principle name="Domain-based"> |
|
Reflect your domain model accurately in types and APIs, using branded or phantom types to define clear boundaries. |
|
</principle> |
|
</cupid> |
|
<guidelines> |
|
<item>Combine pure functions using compose or pipe patterns</item> |
|
<item>Memoize curried and partially applied functions strictly with useCallback/useMemo</item> |
|
<item>Implement data transformations as functional chains with map, filter, and reduce</item> |
|
<item>Define all functions as arrow functions</item> |
|
<item>Use explicit type declarations and leverage branded/phantom types to clarify boundaries</item> |
|
<item>Build composable interfaces via intersection types</item> |
|
<item>Express immutable data with readonly and allow mutable only when strictly necessary</item> |
|
<item>Enforce exhaustive checks in switch statements using never types</item> |
|
<item>Favor expressions over statements and minimize imperative code</item> |
|
<item>Define strict enums using as const</item> |
|
<item>Use named imports exclusively</item> |
|
<item>Compose existing reusable pure functions; introduce utility functions only when reuse is guaranteed</item> |
|
</guidelines> |
|
<examples> |
|
<example id="curry-and-memo"> |
|
<code> |
|
import { useCallback, useMemo } from 'react' |
|
|
|
type Add = (a: number) => (b: number) => number |
|
const add: Add = a => b => a + b |
|
|
|
export const useAdder = (a: number) => { |
|
const curried = useMemo(() => add(a), [a]) |
|
const addB = useCallback((b: number) => curried(b), [curried]) |
|
return addB |
|
} |
|
</code> |
|
</example> |
|
<example id="reduce-sum"> |
|
<code> |
|
export const sum = (numbers: readonly number[]): number => |
|
numbers.reduce((acc, x) => acc + x, 0) |
|
</code> |
|
</example> |
|
<example id="branded-type"> |
|
<code> |
|
declare const brand: unique symbol |
|
type Branded<T, B> = T & { readonly [brand]: B } |
|
export type UserId = Branded<number, 'UserId'> |
|
export const createUserId = (n: number): UserId => n as UserId |
|
</code> |
|
</example> |
|
<example id="pattern-matching"> |
|
<code> |
|
export type Shape = |
|
| { readonly kind: 'circle'; readonly radius: number } |
|
| { readonly kind: 'square'; readonly side: number } |
|
|
|
export const area = (shape: Shape): number => { |
|
switch (shape.kind) { |
|
case 'circle': |
|
return Math.PI * shape.radius ** 2 |
|
case 'square': |
|
return shape.side * shape.side |
|
default: |
|
const _exhaustiveCheck: never = shape |
|
return _exhaustiveCheck |
|
} |
|
} |
|
</code> |
|
</example> |
|
<example id="expression-style"> |
|
<code> |
|
export const getStatusColor = (status: 'success' | 'error'): string => |
|
status === 'success' ? 'green' : 'red' |
|
</code> |
|
</example> |
|
</examples> |