All skills
asyrafhussin avatar

/typescript-react-patterns

@e60e907

TypeScript best practices for React development. Use when writing typed React components, hooks, events, refs, or generic components. Triggers on tasks involving TypeScript errors, type definitions, props typing, or type-safe React patterns.

Use this Skill: https://skilld.dev/gh/asyrafhussin/agent-skills/typescript-react-patterns

This session only. Nothing lands on disk.

rulescomp-default-props.md

≈1.4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Default Props Typing

Impact: CRITICAL (avoids deprecated patterns and ensures type-aware defaults)

Modern approaches to typing default props in React with TypeScript use destructuring defaults instead of the deprecated defaultProps static property.

Incorrect

// ❌ Bad
// Using deprecated defaultProps static property
interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
}

const Button: React.FC<ButtonProps> = ({ variant, size, disabled, children }) => {
  return (
    <button className={`btn-${variant} btn-${size}`} disabled={disabled}>
      {children}
    </button>
  );
};

// This pattern is deprecated and will be removed
Button.defaultProps = {
  variant: 'primary',
  size: 'md',
  disabled: false,
};

// Default values not reflected in type system
interface CardProps {
  elevation: number;
  rounded: boolean;
}

function Card({ elevation, rounded }: CardProps) {
  return <div style={{ boxShadow: `0 ${elevation}px ${elevation * 2}px rgba(0,0,0,0.1)` }} />;
}

Card.defaultProps = {
  elevation: 2,
  rounded: true,
};

Problems:

  • defaultProps is deprecated for function components and will be removed
  • TypeScript does not reflect defaultProps values in the type system
  • Bundlers cannot tree-shake defaultProps effectively

Correct

// ✅ Good
// Using default parameters in destructuring (preferred modern approach)
interface ButtonProps {
  variant?: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
  disabled?: boolean;
  children: React.ReactNode;
}

function Button({
  variant = 'primary',
  size = 'md',
  disabled = false,
  children,
}: ButtonProps): React.ReactElement {
  return (
    <button className={`btn-${variant} btn-${size}`} disabled={disabled}>
      {children}
    </button>
  );
}

// Extracting defaults for reuse and testing
interface CardProps {
  elevation?: number;
  rounded?: boolean;
  children: React.ReactNode;
}

const cardDefaults: Required<Pick<CardProps, 'elevation' | 'rounded'>> = {
  elevation: 2,
  rounded: true,
};

function Card({
  elevation = cardDefaults.elevation,
  rounded = cardDefaults.rounded,
  children,
}: CardProps): React.ReactElement {
  return (
    <div
      className={rounded ? 'rounded' : ''}
      style={{ boxShadow: `0 ${elevation}px ${elevation * 2}px rgba(0,0,0,0.1)` }}
    >
      {children}
    </div>
  );
}

// Complex default objects with proper typing
interface FormFieldProps {
  name: string;
  label?: string;
  validation?: {
    required?: boolean;
    minLength?: number;
    maxLength?: number;
    pattern?: RegExp;
  };
  styles?: {
    container?: React.CSSProperties;
    label?: React.CSSProperties;
    input?: React.CSSProperties;
  };
}

const defaultValidation: Required<NonNullable<FormFieldProps['validation']>> = {
  required: false,
  minLength: 0,
  maxLength: Infinity,
  pattern: /.*/,
};

const defaultStyles: Required<NonNullable<FormFieldProps['styles']>> = {
  container: {},
  label: {},
  input: {},
};

function FormField({
  name,
  label = name,
  validation = {},
  styles = {},
}: FormFieldProps): React.ReactElement {
  const mergedValidation = { ...defaultValidation, ...validation };
  const mergedStyles = { ...defaultStyles, ...styles };

  return (
    <div style={mergedStyles.container}>
      <label style={mergedStyles.label}>{label}</label>
      <input
        name={name}
        required={mergedValidation.required}
        minLength={mergedValidation.minLength}
        maxLength={mergedValidation.maxLength}
        pattern={mergedValidation.pattern.source}
        style={mergedStyles.input}
      />
    </div>
  );
}

// Using satisfies for type-safe defaults
interface ThemeProps {
  colors?: {
    primary?: string;
    secondary?: string;
    background?: string;
  };
  spacing?: {
    small?: number;
    medium?: number;
    large?: number;
  };
}

const themeDefaults = {
  colors: {
    primary: '#007bff',
    secondary: '#6c757d',
    background: '#ffffff',
  },
  spacing: {
    small: 8,
    medium: 16,
    large: 24,
  },
} satisfies Required<{
  colors: Required<NonNullable<ThemeProps['colors']>>;
  spacing: Required<NonNullable<ThemeProps['spacing']>>;
}>;

type Theme = {
  colors: Required<NonNullable<ThemeProps['colors']>>;
  spacing: Required<NonNullable<ThemeProps['spacing']>>;
};

// import { createContext } from 'react'
const defaultTheme: Theme = themeDefaults;
const ThemeContext = createContext<Theme>(defaultTheme);

function ThemeProvider({
  colors = themeDefaults.colors,
  spacing = themeDefaults.spacing,
  children,
}: ThemeProps & { children: React.ReactNode }): React.ReactElement {
  const theme = {
    colors: { ...themeDefaults.colors, ...colors },
    spacing: { ...themeDefaults.spacing, ...spacing },
  };

  return (
    <ThemeContext.Provider value={theme}>
      {children}
    </ThemeContext.Provider>
  );
}

Benefits:

  • Default parameters are type-aware and TypeScript understands the values inside the function
  • No deprecation concerns since defaultProps is deprecated for function components
  • Better tree-shaking by bundlers
  • Explicit defaults make component behavior clear when reading the code
  • Extracted default objects can be imported in tests

Reference: React TypeScript Cheatsheet

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill contains educational reference material and code documentation for TypeScript React development patterns. No security risks or malicious vectors were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    30 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at e60e907. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub last month.

Steadyupdated 7 months ago
metadata
{
  "author": "agent-skills",
  "version": "2.0.0"
}

README badge

README badge for asyrafhussin/agent-skills/typescript-react-patterns