This document outlines our strict coding standards aimed at maintaining exceptional code quality and readability in the Project.
- Readability Over Cleverness: Code should be immediately understandable by other developers
- Strict File Length: Maximum 300 lines per file, aim for under 100 lines
- Functional Programming: Prefer pure functions and immutable data structures
- Type Safety: Strict TypeScript usage with no compromises
-
Write pure functions whenever possible
-
Avoid state mutation and the
letkeyword -
Use immutable array methods exclusively:
// Don't for (let i = 0; i < items.length; i++) { items[i] = transform(items[i]) } // Do const newItems = items.map(transform)
-
Early returns are mandatory for improved readability:
// Don't function processUser(user: User) { if (user.isActive) { if (user.hasPermission) { return doSomething() } } return null } // Do function processUser(user: User) { if (!user.isActive) return null if (!user.hasPermission) return null return doSomething() }
- Single Responsibility: Each component should do exactly one thing
- Size Limit: Components should rarely exceed 100 lines
- Props Limit: Maximum 5 props per component, use composition for more complex needs
- Nesting Limit: Maximum 5 levels of JSX nesting
The separation between Display and Container components is mandatory in the following cases:
- When the same UI elements need to be reused across multiple features
- When the component handles complex data fetching or state management that could be reused
- When the component exceeds 100 lines due to mixing business logic with presentation
- When the same UI needs to support multiple data sources
In other cases, combining display and logic in a single component is acceptable, but consider separation when:
- The component's logic becomes complex enough to warrant testing in isolation
- You anticipate the UI being reused in the future
- The component handles multiple responsibilities that could be logically split
// Example of when separation is required (reusable UI)
function UserCard({ name, email, onEdit }: UserCardProps): JSX.Element {
return (
<div className="p-4 border rounded">
<h3 className="text-lg">{name}</h3>
<p className="text-gray-600">{email}</p>
<button onClick={onEdit}>Edit</button>
</div>
)
}
// Multiple containers can reuse the same display component
function AdminUserContainer(): JSX.Element {
const { data: user } = useQuery(['admin-user'], fetchAdminUser)
// ... admin-specific logic
return <UserCard {...user} onEdit={handleAdminEdit} />
}
function CustomerUserContainer(): JSX.Element {
const { data: user } = useQuery(['customer'], fetchCustomer)
// ... customer-specific logic
return <UserCard {...user} onEdit={handleCustomerEdit} />
}
// Example of when combining is acceptable (simple, single-use component)
function SimpleGreeting(): JSX.Element {
const { data: user } = useQuery(['user'], fetchUser)
if (!user) return <LoadingSpinner />
return (
<div className="p-2">
<h2>Welcome, {user.name}!</h2>
</div>
)
}- Must be pure functions that only depend on their props
- No hooks (except component-specific style hooks)
- No API calls or data fetching
- No connection to global state
- Should be highly reusable
- Focus only on how things look
- Receive data and callbacks exclusively via props
- Can contain other Display components
// Good Display Component
function UserCard({ name, email, onEdit }: UserCardProps): JSX.Element {
return (
<div className="p-4 border rounded">
<h3 className="text-lg">{name}</h3>
<p className="text-gray-600">{email}</p>
<button onClick={onEdit}>Edit</button>
</div>
)
}- Handle data fetching and state management
- Connect to global state management
- Contain business logic and data transformations
- Pass data down to Display components
- Should have minimal JSX
- Focus on how things work
- Must be colocated with their feature
// Good Container Component
function UserCardContainer(): JSX.Element {
const { data: user } = useQuery(['user'], fetchUser)
const { mutate: updateUser } = useMutation(saveUser)
const handleEdit = async (updates: UserUpdates): Promise<void> => {
await updateUser(updates)
}
if (!user) return <LoadingSpinner />
return <UserCard
name={user.name}
email={user.email}
onEdit={handleEdit}
/>
}anyis forbidden except in test filesunknownmust be used instead ofanyfor uncertain types- Non-null assertions (
!) are forbidden - All functions must have explicit return types
- Generic types must have meaningful names (not T, U, V)
// Don't
function process(data: any) {
return data.value!
}
// Do
function process<InputType extends Record<string, unknown>>(data: InputType): string {
if (!data.value || typeof data.value !== 'string') {
throw new Error('Invalid input')
}
return data.value
}- Only Tailwind CSS classes are allowed
- No inline styles
- No CSS-in-JS
- Custom CSS only allowed in global.css for exceptional cases
- Color values must come from Tailwind theme configuration
// Don't
<div style={{ marginLeft: '8px' }} className="text-blue-500">
// Do
<div className="ml-2 text-primary-500">-
Strict file naming:
- React components:
PascalCase.tsx - Utilities:
kebab-case.ts - Tests:
ComponentName.test.tsx - Types:
ComponentName.types.ts
- React components:
-
Special directories for shadcn:
components/ui/*: Reserved ONLY for shadcn componentslib/utils.ts: Reserved ONLY for shadcn utilities
-
Folder structure must follow:
components/ # Reusable UI components
ui/ # shadcn components ONLY
Button.tsx
Dialog.tsx
ExampleComponent/ # Each component in its own directory
index.tsx # Main component implementation
ExampleComponent.types.ts # Component-specific types and interfaces
ExampleComponent.test.tsx # Component tests
ExampleComponent.utils.ts # Component-specific utilities (used only by this component)
ExampleComponent.hooks.ts # Component-specific hooks (used only by this component)
components/ # Child components used ONLY by this component
ExampleChildComponent.tsx
lib/
utils.ts # shadcn utilities ONLY
hooks/ # Common hooks used across multiple components (could also include shadcn hooks, add those to the .eslintignore file)
useExampleHook.ts
useAnotherHook.ts
utils/ # Common utility functions used across multiple components
example-utils.ts
format-utils.ts
types/ # Common TypeScript types and interfaces
example.types.ts
models.types.ts
constants/ # Global constants and configuration
config.ts
routes.ts
- Component folders should contain ONLY code that is exclusively used by that component
- If a utility or hook is used by multiple components, it MUST be moved to the root-level
utils/orhooks/directory - Keep component folders focused - if they become too complex, split them into multiple components
- Child components that are only used by a single parent should live in that parent's
components/directory - Common types, constants, and utilities used across multiple components belong in their respective root-level directories
- shadcn components must remain isolated:
- No modifications to
components/ui/*files - No modifications to
lib/utils.ts - Use composition instead of modifying shadcn components
- No modifications to
- All project utilities must go in
/utils, never inlib/utils.ts - shadcn-related files are excluded from project linting:
components/ui/*lib/utils.ts- Third-party hooks (e.g.,
use-mobile)
- No premature optimization
- Performance improvements must be backed by profiler data
- Required optimizations:
- Use
React.memofor expensive renders only - Implement proper key props in lists
- Lazy load all routes
- Keep bundle sizes under 100KB per chunk
- Use
- All PRs must:
- Pass all TypeScript checks with strict mode
- Have no ESLint warnings
- Include tests for new functionality
- Be under 300 lines total
- Include updated documentation
- Create feature branch from main
- Write tests first (TDD approach)
- Implement feature
- Ensure all style guidelines are met
- Create PR with detailed description
- Address review comments
- Squash and merge
These guidelines are strictly enforced through:
- ESLint rules
- TypeScript compiler options
- Code Reviews
- Use React Query for server state
- Use Zustand for client-only global state
- Prefer local state when possible
- Document all global state stores
- Implement proper loading and error states
// Good state management
function UserProfile(): JSX.Element {
// Server state
const { data: user, isLoading, error } = useQuery(['user'], fetchUser)
// Local state
const [isEditing, setIsEditing] = useState(false)
// Global state (only when necessary)
const theme = useStore(state => state.theme)
if (isLoading) return <LoadingSpinner />
if (error) return <ErrorDisplay error={error} />
if (!user) return <NotFound />
return <UserDisplay user={user} theme={theme} />
}- All public functions must have JSDoc comments
- Include code examples in documentation
- Document all props with detailed descriptions
- Maintain a changelog
- Include setup instructions for new dependencies
/**
* Transforms user data for API submission
* @param user - The user object to transform
* @param options - Configuration options for the transformation
* @returns Transformed user data ready for API submission
* @throws {ValidationError} When user data is invalid
* @example
* const transformed = transformUserData({
* name: 'John',
* age: 30
* }, { includeMetadata: true })
*/
function transformUserData(user: User, options: TransformOptions): ApiUser {
// Implementation
}- Unit tests for all utility functions
- Integration tests for all API calls
- Component tests must include:
- Happy path rendering
- Error states
- Loading states
- User interactions
- Edge cases
- E2E tests for critical user flows
- Maintain 80% code coverage minimum
// Good test example
describe('UserCard', () => {
it('renders user information correctly', () => {})
it('handles missing optional fields', () => {})
it('shows loading state', () => {})
it('displays error message on API failure', () => {})
it('handles edit user interaction', () => {})
})- All images must have alt text
- Proper ARIA labels for interactive elements
- Keyboard navigation support
- Color contrast compliance
- Screen reader friendly markup
- Regular accessibility audits
// Good accessibility practices
function Button({ onClick, children }: ButtonProps): JSX.Element {
return (
<button
onClick={onClick}
className="px-4 py-2 bg-primary-500"
aria-label="Save changes"
role="button"
tabIndex={0}
>
{children}
</button>
)
}- All code must pass ESLint and Prettier checks before commit
- Maximum line length of 80 characters
- Tab indentation (4 spaces)
- Single quotes for strings
- Trailing commas in objects and arrays
- Parentheses around arrow function parameters
- Single attribute per line in JSX
// Don't
const Component = ({name, title, description}) => <div className="container" onClick={handleClick}>
{name}
</div>
// Do
const Component = ({
name,
title,
description
}: ComponentProps): JSX.Element => (
<div
className='container'
onClick={handleClick}>
{name}
</div>
)- Maximum complexity score of 10 per function
- Maximum 5 parameters per function
- Strict boolean expressions required
- No leaked renders in React components
- Enforced React compiler optimization
- Strict immutable data patterns
- Organized imports with enforced grouping:
- Built-in Node modules
- External dependencies
- Internal modules
- Parent directory imports
- Sibling files
- Index files
// Correct import ordering
import { useState } from 'react'
import { axios } from 'axios'
import { z } from 'zod'
import { AppConfig } from '@/config'
import { ParentComponent } from '../ParentComponent'
import { siblingUtil } from './utils'
import { Types } from '.'- Function components must use function declarations
- Maximum JSX nesting depth of 5 levels
- Strict enforcement of React hooks rules
- Exhaustive dependencies in effect hooks
- Proper state management patterns
// Don't
const MyComponent = () => {
useEffect(() => {
doSomething(value)
}, []) // Missing dependency
// Do
function MyComponent(): JSX.Element {
useEffect(() => {
doSomething(value)
}, [value]) // All dependencies included
}