Code Style

TypeScript patterns and component structure used in Plainform

Modern TypeScript and React patterns used in Plainform.

TypeScript

Type Annotations

Type annotations
// Always annotate function params and returns
function formatDate(date: Date): string {
  return new Intl.DateTimeFormat('en-US').format(date);
}

// Let TypeScript infer variables
const userName = 'John';  // string inferred
const count = 42;  // number inferred

Interfaces vs Types

Interfaces vs types
// Interfaces for objects
interface IUser {
  id: string;
  name: string;
}

// Types for unions and complex types
type Status = 'pending' | 'active' | 'inactive';
type Result<T> = { success: true; data: T } | { success: false; error: string };

Type Guards

Type guards
function isError(value: unknown): value is Error {
  return value instanceof Error;
}

if (typeof value === 'string') { }
if (Array.isArray(value)) { }

Import Organization

Import organization
// 1. External packages
import { useState } from 'react';
import { z } from 'zod';

// 2. Internal modules (use @/ alias)
import { Button } from '@/components/ui/Button';
import { prisma } from '@/lib/prisma/prisma';

// 3. Types
import type { IUser } from '@/types/UserInterfaces';

// 4. Styles
import styles from './Component.module.css';

Prefer named exports over default exports (except Next.js pages/layouts).

Component Structure

Component structure
'use client';  // Only if needed

// 1. Imports
import { useState } from 'react';
import { Button } from '@/components/ui/Button';

// 2. Interface
interface IComponentProps {
  title: string;
  isActive?: boolean;
}

// 3. Component
export function ComponentName({ title, isActive = false }: IComponentProps) {
  // 4. Hooks (always at top)
  const [state, setState] = useState(false);

  // 5. Event handlers
  const handleClick = () => {
    setState(true);
  };

  // 6. Early returns
  if (!title) return null;

  // 7. JSX
  return (
    <div>
      <h1>{title}</h1>
      <Button onClick={handleClick}>Click</Button>
    </div>
  );
}

Server vs Client Components

Server vs client components
// Server Component (default) - no 'use client'
export async function BlogList() {
  const posts = await prisma.post.findMany();
  return <div>{posts.map(post => ...)}</div>;
}

// Client Component - add 'use client'
'use client';
export function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

Use Client Components for: hooks, event handlers, browser APIs, third-party libraries.

Error Handling

Error handling in API routes
export async function POST(req: Request) {
  try {
    const body = await req.json();
    const validated = schema.parse(body);
    const result = await processData(validated);
    
    return NextResponse.json({ success: true, data: result });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        { error: 'Validation failed', details: error.errors },
        { status: 400 }
      );
    }
    
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}

Async/Await

Async/await patterns
// Prefer async/await
async function fetchData(userId: string) {
  const user = await prisma.user.findUnique({ where: { id: userId } });
  const orders = await prisma.order.findMany({ where: { userId } });
  return { user, orders };
}

// Use Promise.all() for parallel operations
async function fetchDashboard() {
  const [users, orders, products] = await Promise.all([
    prisma.user.findMany(),
    prisma.order.findMany(),
    prisma.product.findMany()
  ]);
  return { users, orders, products };
}

Best Practices

Avoid magic numbers

Use constants
const MAX_FILE_SIZE = 5 * 1024 * 1024;  // 5MB
if (file.size > MAX_FILE_SIZE) { }

Single responsibility

Single responsibility
// Each function does one thing
function validateEmail(email: string): boolean { }
function sendWelcomeEmail(email: string): Promise<void> { }

DRY (Don't Repeat Yourself)

DRY principle
function formatCurrency(amount: number): string {
  return new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD'
  }).format(amount);
}

Run npm run lint and npm run prettier before committing. Husky hooks enforce this automatically.

How is this guide ?

Last updated on

On this page