react-ts-guidelines
Coding Standards & Best Practices
Non-negotiable rules
Apply non-negotiable rules (19 rules, ordered by impact) before anything else.
Agent Instructions
After reading this skill:
- Apply ALL rules to every file you create or modify
- Run the Pre-output Validation checklist before returning any code
- If a rule conflicts with a user request, flag it explicitly and propose a compliant alternative
- Reference specific rule names when explaining choices (e.g., "Per the KISS principle, I simplified this by...")
- Load example files on demand — only read the relevant file for the task at hand
Code Quality Principles
| Principle | Rule |
|---|---|
| Readability First | Code is read more than written. Clear names > clever code. |
| KISS | Simplest solution that works. Avoid over-engineering. |
| Avoid GodComponent | Single responsibility per component; extract utilities, hooks, sub-components. |
| DRY | Extract common logic. Create reusable components. |
| YAGNI | Don't build features before they're needed. |
| Immutability | Never mutate — always return new values. |
Avoiding GodComponent (decomposition strategy)
Apply the following order to keep components focused and testable:
-
Extract pure TypeScript utilities first
- Move logic that has no React dependency into pure functions.
- If a utility takes more than one argument, use object destructuring in the signature so argument names are explicit at the call site. Extract the parameter type (e.g.
interface FormatRangeArgs { start: number; end: number }thenconst formatRange = ({ start, end }: FormatRangeArgs) => ...). - Reusable across features → put in
src/utils/xyz.utils.ts. - Feature-specific → keep next to the component as
component-name.utils.ts(same kebab-case base name as the component file, e.g.market-list-item.utils.tsnext tomarket-list-item.tsx).
-
Extract logic into named hooks
- Move state, effects, and derived logic into hooks (e.g.
use-xyz.ts). - Reusable across features → put in
src/hooks/use-xyz.ts. - Feature-specific → keep in the feature’s
hooks/subdirectory (e.g.features/market-list/hooks/use-market-filters.ts).
- Move state, effects, and derived logic into hooks (e.g.
-
Split the visual layer into sub-components
- If the render/JSX exceeds roughly 40 lines, extract sub-components with clear props and a single responsibility.
- Each sub-component should have its own props interface and live in its own file (or a dedicated subfolder) when it grows.
Sections & Example Files
TypeScript / JavaScript
| Topic | Example File | When to load |
|---|---|---|
| Variable & function naming (incl. boolean prefixes) | examples/typescript/naming.ts |
When naming anything (arrow functions only; booleans: is/has/should/can/will) |
| Immutability patterns | examples/typescript/immutability.ts |
When working with state/objects/arrays |
| Error handling | examples/typescript/error-handling.ts |
When writing async code |
| Async / Promise patterns | examples/typescript/async-patterns.ts |
When using await/Promise |
| Type safety | examples/typescript/type-safety.ts |
When defining interfaces/types (no inline types; no nested types; extract named types) |
| Control flow & readability | examples/typescript/control-flow.ts |
Early returns, const vs let, Array.includes/some, nullish coalescing, destructuring |
React
| Topic | Example File | When to load |
|---|---|---|
| Component structure | examples/react/component-structure.tsx |
When creating a component |
Testing
| Topic | Example File | When to load |
|---|---|---|
| Unit test patterns | examples/testing/unit-testing-patterns.tsx |
When writing Jest/RTL tests (AAA, screen, spyOn, it.each, getByRole, mock factory) |
Anti-patterns (read during code review)
| Topic | File |
|---|---|
| All BAD patterns grouped | anti-patterns/what-not-to-do.ts |
| Code smells detection | anti-patterns/code-smells.ts |
File Organization Rules
- 200–400 lines typical file length
- 800 lines absolute maximum
- One responsibility per file (high cohesion, low coupling)
- File names: always kebab-case (lowercase with hyphens). No PascalCase or camelCase in file or folder names.
components/button.tsx # kebab-case (not Button.tsx)
hooks/use-auth.ts # kebab-case (not useAuth.ts)
lib/format-date.ts # kebab-case (not formatDate.ts)
types/market.types.ts # kebab-case + optional .types / .utils / .store suffix
features/market-list/market-list-item.tsx
settings-screen.tsx # e.g. settings-screen.tsx, use-device-discovery.ts
Components and hooks are still exported with PascalCase (components) or camelCase with use prefix (hooks); only the file name is kebab-case.
Code Style / TypeScript
- TypeScript strict mode — enable in
tsconfig.jsonfor maximum type safety. - Explicit function signatures — type function parameters and return types explicitly; avoid relying on inference for public APIs.
- Type inference for locals — prefer inference for local variables when the type is obvious (e.g.
const count = 0).
React Components
- FunctionComponent — type React components with
FunctionComponent<Props>(orFC<Props>); use typed props interfaces, not inline orany. - Early returns — use early returns in component bodies to keep the main render path flat and readable.
- Fragment shorthand — use
<>...</>instead of<Fragment>unless akeyis required. - Exports — prefer named exports for components; default export only when required by the framework (e.g. Expo Router).
React Hooks
- Functions vs hooks — prefer a plain function to a custom hook when you don't need React primitives (state, effects, context).
- use prefix — use the
useprefix only for real hooks; never for plain functions. - useMemo / useCallback — avoid for simple computations or callbacks; use when profiling shows a need or when passing callbacks to memoized children.
- Handlers — use a single arrow function per handler (e.g.
const handleClick = () => { ... }); avoid function factories that return handlers. - Selected items — store selection by ID in state and derive the full item from the list (e.g.
selectedItem = items.find(i => i.id === selectedId)); avoids stale references when the list updates.
Data fetching (async: loading, error, data)
- Prefer TanStack Query — for any async call that involves
isLoading, error handling, and result data, useuseQuery(oruseMutationfor writes) instead of manualuseState+useEffect. You get caching, deduplication, and consistent loading/error state for free. - Query key factory — define a single source of truth for cache keys (e.g.
XxxQueryKey.all,XxxQueryKey.list(...),XxxQueryKey.detail(id)). See examples/react/query-keys-example.ts and assets/hook-tanstack-query-template.ts.
Error Handling
- Context in messages — include a prefix in error and log messages (e.g.
[ComponentName] failed to load). - Rethrow policy — rethrow only when adding context or transforming the error type; don't rethrow after logging unless the caller needs to handle the failure.
Architecture & Organisation
- Feature structure — each feature should be self-contained: its own components,
hooks/subdirectory,*.utils.tsand*.types.tsfiles, and Controllers/Services for complex business logic (e.g.features/scene-3d/,scene-manager/controllers/). - Single responsibility — one clear responsibility per file; keep components small and focused. Apply the Avoiding GodComponent (decomposition strategy): utilities first, then hooks, then sub-components when the visual layer exceeds ~40 lines.
- Composition over inheritance — prefer composing small components and utilities over class inheritance.
- Group related code — keep related functionality together (e.g. by feature or domain).
Comments
- Self-documenting first — prefer clear names and structure over comments; comment only when behavior is non-obvious.
- Explain "why" not "what" — comments should explain rationale, side effects, or workarounds, not restate the code.
- Keep comments up to date — remove or update comments when code changes.
- TODO with ticket ID — use a traceable format for TODOs (e.g.
// TODO: JIRA-1234 - description).
Logging
- Logger with levels — use a logger (e.g.
logger.info(),logger.error(),logger.warn(),logger.debug()) instead ofconsole.*in client code. - Context prefix — include a context prefix in log messages (e.g.
[useDeviceDiscovery] storing last known camera IP). - Server exception —
console.logis acceptable in server-side or API route code for debugging.
Function Parameters
- Destructuring for multiple params — use object destructuring when a function has more than one parameter (e.g.
const fn = ({ a, b }: Args) => ...). - Extract parameter types — export parameter types as named types/interfaces instead of inline typing.
- Optional parameters — use
param?: Typerather thanparam: Type | undefined. - Defaults in destructuring — set default values in the destructuring when possible (e.g.
{ page = 1, size = 10 }).
TypeScript Best Practices
- ReactNode for children — use
ReactNodefor component children (notJSX.Element | null | undefined). - PropsWithChildren — use
PropsWithChildren<Props>for components that acceptchildren. Record<K, V>— prefer theRecord<K, V>utility type over custom index signatures.- Array.includes() — use for multiple value checks instead of repeated
===comparisons. - Array.some() — use for existence checks instead of
array.find(...) !== undefined. - Explicit enum values — use explicit numeric (or string) values for enums so they survive reordering and serialization.
React Native (when applicable)
When working in a React Native or Expo project:
- Spacing — prefer
gap,rowGap, andcolumnGapovermargin/paddingfor spacing between elements. - Responsive layout — use
useWindowDimensionsinstead ofDimensions.getfor layout that reacts to size changes. - Static data outside components — move constants and pure functions that don't depend on props or state outside the component to avoid new references on every render.
Pre-output validation
Before returning any code, run the Pre-output Validation checklist.
Templates
Skeletons to copy and adapt (file names in kebab-case):
| Type | File | Use when |
|---|---|---|
| Hook | assets/hook-template.ts | Creating a data-fetching or effect hook |
| Hook (TanStack Query) | assets/hook-tanstack-query-template.ts | Creating a hook with @tanstack/react-query (queryKey, queryFn, placeholderData) |
| Component | assets/component-template.tsx | Creating a React component |
| Utility | assets/utils-template.ts | Creating pure or side-effect helpers (*.utils.ts) |
Validation
Validate this skill's frontmatter and structure with skills-ref:
skills-ref validate ./skills/react-ts-guidelines
More from lichens-innovation/skills
generate-pr-description
Generates pull request descriptions by comparing current branch with parent branch. Creates semantic commit-style PR titles and fills PR templates. Use when the user asks to generate PR description, prepare pull request, or create merge request description. The user may include ticket IDs in the request (e.g. tickets: NN-123, TB-456) from the company tracking system; treat those as the related issue IDs for the PR.
28typescript-and-react-guidelines
|
10single-responsibility
|
6coding-standards
|
1