Claude Code Plugins

Community-maintained marketplace

Feedback

TypeScript development best practices including type system, generics, utility types, configuration, and patterns for React, Node.js, and full-stack applications. Use when writing TypeScript code, converting JavaScript to TypeScript, debugging type errors, or implementing type-safe patterns.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name typescript
description TypeScript development best practices including type system, generics, utility types, configuration, and patterns for React, Node.js, and full-stack applications. Use when writing TypeScript code, converting JavaScript to TypeScript, debugging type errors, or implementing type-safe patterns.

TypeScript Development

Modern TypeScript patterns and type-safe development.

Type Fundamentals

Basic Types

// Primitives
const name: string = 'John';
const age: number = 30;
const isActive: boolean = true;
const nothing: null = null;
const notDefined: undefined = undefined;

// Arrays
const numbers: number[] = [1, 2, 3];
const strings: Array<string> = ['a', 'b', 'c'];
const mixed: (string | number)[] = [1, 'two', 3];

// Tuples
const tuple: [string, number] = ['hello', 42];
const namedTuple: [name: string, age: number] = ['John', 30];

// Objects
const user: { name: string; age: number } = { name: 'John', age: 30 };

// Any vs Unknown
const dangerous: any = getData();     // Avoid - no type checking
const safe: unknown = getData();       // Prefer - requires type narrowing
if (typeof safe === 'string') {
    console.log(safe.toUpperCase());   // Now TypeScript knows it's string
}

Interfaces vs Types

// Interface - extendable, for objects
interface User {
    id: number;
    name: string;
    email: string;
}

interface AdminUser extends User {
    role: 'admin';
    permissions: string[];
}

// Type - more flexible
type ID = string | number;
type Callback = (data: string) => void;
type Status = 'pending' | 'active' | 'inactive';

// Intersection types
type UserWithTimestamps = User & {
    createdAt: Date;
    updatedAt: Date;
};

// Use interface for objects, type for unions/primitives

Optional & Readonly

interface Config {
    required: string;
    optional?: string;              // May be undefined
    readonly immutable: string;     // Can't be reassigned
}

// Readonly utility
type ReadonlyUser = Readonly<User>;

// Partial - all optional
type PartialUser = Partial<User>;

// Required - all required
type RequiredUser = Required<User>;

Generics

Basic Generics

// Generic function
function identity<T>(value: T): T {
    return value;
}

const num = identity(42);        // T inferred as number
const str = identity('hello');   // T inferred as string

// Generic interface
interface Response<T> {
    data: T;
    status: number;
    message: string;
}

const userResponse: Response<User> = {
    data: { id: 1, name: 'John', email: 'john@example.com' },
    status: 200,
    message: 'Success',
};

// Generic class
class Queue<T> {
    private items: T[] = [];

    enqueue(item: T): void {
        this.items.push(item);
    }

    dequeue(): T | undefined {
        return this.items.shift();
    }
}

const numberQueue = new Queue<number>();
numberQueue.enqueue(1);

Constraints

// Constrain to specific shape
interface HasId {
    id: number;
}

function findById<T extends HasId>(items: T[], id: number): T | undefined {
    return items.find(item => item.id === id);
}

// Multiple constraints
function merge<T extends object, U extends object>(a: T, b: U): T & U {
    return { ...a, ...b };
}

// keyof constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const user = { name: 'John', age: 30 };
const name = getProperty(user, 'name');  // string
const age = getProperty(user, 'age');    // number

Utility Types

// Pick - select specific properties
type UserPreview = Pick<User, 'id' | 'name'>;

// Omit - exclude properties
type UserWithoutEmail = Omit<User, 'email'>;

// Record - map keys to values
type UserRoles = Record<string, 'admin' | 'user' | 'guest'>;

// Extract / Exclude
type Status = 'pending' | 'active' | 'deleted';
type ActiveStatus = Extract<Status, 'pending' | 'active'>;  // 'pending' | 'active'
type WithoutDeleted = Exclude<Status, 'deleted'>;           // 'pending' | 'active'

// ReturnType / Parameters
function createUser(name: string, email: string): User {
    return { id: 1, name, email };
}

type CreateUserReturn = ReturnType<typeof createUser>;      // User
type CreateUserParams = Parameters<typeof createUser>;       // [string, string]

// NonNullable
type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>;           // string

Type Guards

// typeof guard
function process(value: string | number) {
    if (typeof value === 'string') {
        return value.toUpperCase();
    }
    return value * 2;
}

// instanceof guard
class Dog {
    bark() { console.log('Woof!'); }
}

class Cat {
    meow() { console.log('Meow!'); }
}

function speak(animal: Dog | Cat) {
    if (animal instanceof Dog) {
        animal.bark();
    } else {
        animal.meow();
    }
}

// in guard
interface Bird { fly(): void; }
interface Fish { swim(): void; }

function move(animal: Bird | Fish) {
    if ('fly' in animal) {
        animal.fly();
    } else {
        animal.swim();
    }
}

// Custom type guard
interface ApiError {
    code: string;
    message: string;
}

function isApiError(error: unknown): error is ApiError {
    return (
        typeof error === 'object' &&
        error !== null &&
        'code' in error &&
        'message' in error
    );
}

// Usage
try {
    await fetchData();
} catch (error) {
    if (isApiError(error)) {
        console.log(error.code);  // TypeScript knows it's ApiError
    }
}

Advanced Patterns

Discriminated Unions

interface LoadingState {
    status: 'loading';
}

interface SuccessState<T> {
    status: 'success';
    data: T;
}

interface ErrorState {
    status: 'error';
    error: string;
}

type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;

function handleState<T>(state: AsyncState<T>) {
    switch (state.status) {
        case 'loading':
            return 'Loading...';
        case 'success':
            return state.data;  // TypeScript knows data exists
        case 'error':
            return state.error; // TypeScript knows error exists
    }
}

Template Literal Types

type EventName = 'click' | 'focus' | 'blur';
type EventHandler = `on${Capitalize<EventName>}`;  // 'onClick' | 'onFocus' | 'onBlur'

type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint = `/api/${string}`;

function request(method: HTTPMethod, url: Endpoint) {
    // ...
}

request('GET', '/api/users');    // OK
request('GET', '/users');        // Error: doesn't start with /api/

Mapped Types

// Make all properties optional
type Optional<T> = {
    [K in keyof T]?: T[K];
};

// Make all properties nullable
type Nullable<T> = {
    [K in keyof T]: T[K] | null;
};

// Prefix keys
type Prefixed<T, P extends string> = {
    [K in keyof T as `${P}${string & K}`]: T[K];
};

type PrefixedUser = Prefixed<User, 'user_'>;
// { user_id: number; user_name: string; user_email: string }

Conditional Types

// Basic conditional
type IsString<T> = T extends string ? true : false;

type A = IsString<string>;  // true
type B = IsString<number>;  // false

// Extract array element type
type ElementOf<T> = T extends (infer E)[] ? E : never;

type StringElement = ElementOf<string[]>;  // string

// Function return type
type AsyncReturnType<T> = T extends (...args: any[]) => Promise<infer R> ? R : never;

async function fetchUser(): Promise<User> {
    return { id: 1, name: 'John', email: 'john@example.com' };
}

type FetchedUser = AsyncReturnType<typeof fetchUser>;  // User

React TypeScript

Component Types

import { FC, ReactNode, ComponentProps } from 'react';

// Props interface
interface ButtonProps {
    variant: 'primary' | 'secondary';
    size?: 'sm' | 'md' | 'lg';
    children: ReactNode;
    onClick?: () => void;
}

// Function component
function Button({ variant, size = 'md', children, onClick }: ButtonProps) {
    return (
        <button className={`btn-${variant} btn-${size}`} onClick={onClick}>
            {children}
        </button>
    );
}

// With FC (includes children)
const Card: FC<{ title: string; children: ReactNode }> = ({ title, children }) => (
    <div className="card">
        <h2>{title}</h2>
        {children}
    </div>
);

// Extending HTML element props
interface InputProps extends ComponentProps<'input'> {
    label: string;
    error?: string;
}

function Input({ label, error, ...props }: InputProps) {
    return (
        <div>
            <label>{label}</label>
            <input {...props} />
            {error && <span className="error">{error}</span>}
        </div>
    );
}

Hooks

import { useState, useEffect, useRef, useCallback, useMemo } from 'react';

// useState with type
const [user, setUser] = useState<User | null>(null);
const [items, setItems] = useState<string[]>([]);

// useRef
const inputRef = useRef<HTMLInputElement>(null);
const countRef = useRef<number>(0);

// useCallback with types
const handleClick = useCallback((id: number) => {
    console.log(id);
}, []);

// useMemo
const expensiveValue = useMemo(() => {
    return items.filter(item => item.length > 5);
}, [items]);

// Custom hook
function useLocalStorage<T>(key: string, initialValue: T) {
    const [value, setValue] = useState<T>(() => {
        const stored = localStorage.getItem(key);
        return stored ? JSON.parse(stored) : initialValue;
    });

    useEffect(() => {
        localStorage.setItem(key, JSON.stringify(value));
    }, [key, value]);

    return [value, setValue] as const;
}

Configuration

tsconfig.json

{
    "compilerOptions": {
        "target": "ES2022",
        "module": "ESNext",
        "moduleResolution": "bundler",
        "lib": ["ES2022", "DOM", "DOM.Iterable"],

        "strict": true,
        "noUncheckedIndexedAccess": true,
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": true,

        "declaration": true,
        "declarationMap": true,
        "sourceMap": true,

        "esModuleInterop": true,
        "skipLibCheck": true,
        "forceConsistentCasingInFileNames": true,

        "baseUrl": ".",
        "paths": {
            "@/*": ["src/*"],
            "@components/*": ["src/components/*"]
        },

        "outDir": "dist",
        "rootDir": "src"
    },
    "include": ["src/**/*"],
    "exclude": ["node_modules", "dist"]
}

Strict Mode Benefits

// strictNullChecks - catches null/undefined errors
const user: User | null = getUser();
user.name;              // Error: user might be null
user?.name;             // OK: optional chaining

// noImplicitAny - requires explicit types
function process(data) { }  // Error: implicit 'any'
function process(data: unknown) { }  // OK

// strictPropertyInitialization - ensures class properties are initialized
class User {
    name: string;       // Error: not initialized
    name: string = '';  // OK
}