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-rest-props.md

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

Rest Props Typing

Impact: CRITICAL (ensures type-safe prop spreading to HTML elements)

Typing rest/spread props for components that pass through HTML attributes. Proper Omit patterns prevent prop conflicts and maintain full type safety.

Incorrect

// ❌ Bad
// Using 'any' for rest props
interface ButtonProps {
  variant: 'primary' | 'secondary';
  [key: string]: any; // Loses all type safety
}

const Button = ({ variant, ...rest }: ButtonProps) => {
  return <button className={`btn-${variant}`} {...rest} />;
};

// Not properly omitting custom props from HTML props
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
  label: string;
  error: string;
}

const Input = ({ label, error, ...rest }: InputProps) => {
  // 'label' and 'error' are valid HTML attributes, causing conflicts
  return (
    <div>
      <label>{label}</label>
      <input {...rest} />
      <span>{error}</span>
    </div>
  );
};

Problems:

  • Index signatures like [key: string]: any remove all type checking on spread props
  • Not omitting custom prop names that collide with HTML attributes causes conflicts
  • Missing ComponentPropsWithoutRef can lead to ref handling issues

Correct

// ✅ Good
// Properly extending and omitting HTML attributes
interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'className'> {
  variant: 'primary' | 'secondary';
  size?: 'sm' | 'md' | 'lg';
}

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

// Using ComponentPropsWithoutRef for better ref handling
import { ComponentPropsWithoutRef } from 'react';

interface CustomInputProps {
  label: string;
  errorMessage?: string;
  helperText?: string;
}

type InputProps = CustomInputProps & Omit<ComponentPropsWithoutRef<'input'>, keyof CustomInputProps>;

function CustomInput({
  label,
  errorMessage,
  helperText,
  id,
  ...rest
}: InputProps): React.ReactElement {
  const inputId = id ?? `input-${label.toLowerCase().replace(/\s/g, '-')}`;

  return (
    <div className="form-field">
      <label htmlFor={inputId}>{label}</label>
      <input id={inputId} aria-invalid={!!errorMessage} {...rest} />
      {errorMessage && <span className="error">{errorMessage}</span>}
      {helperText && !errorMessage && <span className="helper">{helperText}</span>}
    </div>
  );
}

// Creating reusable base prop types
type BaseButtonProps = ComponentPropsWithoutRef<'button'>;

interface IconButtonProps extends Omit<BaseButtonProps, 'children'> {
  icon: React.ReactNode;
  'aria-label': string; // Required for accessibility
}

function IconButton({ icon, ...rest }: IconButtonProps): React.ReactElement {
  return <button {...rest}>{icon}</button>;
}

// Generic rest props for polymorphic components
type AsProp<C extends React.ElementType> = {
  as?: C;
};

type PropsToOmit<C extends React.ElementType, P> = keyof (AsProp<C> & P);

type PolymorphicComponentProps<
  C extends React.ElementType,
  Props = object
> = Props &
  AsProp<C> &
  Omit<React.ComponentPropsWithoutRef<C>, PropsToOmit<C, Props>>;

interface BoxOwnProps {
  padding?: 'sm' | 'md' | 'lg';
  margin?: 'sm' | 'md' | 'lg';
}

type BoxProps<C extends React.ElementType = 'div'> = PolymorphicComponentProps<C, BoxOwnProps>;

function Box<C extends React.ElementType = 'div'>({
  as,
  padding,
  margin,
  className,
  ...rest
}: BoxProps<C>): React.ReactElement {
  const Component = as ?? 'div';
  const classes = [
    className,
    padding && `p-${padding}`,
    margin && `m-${margin}`,
  ].filter(Boolean).join(' ');

  return <Component className={classes} {...rest} />;
}

// Usage with full type safety
<Box padding="md">Content in div</Box>
<Box as="section" padding="lg">Content in section</Box>
<Box as="a" href="/home" padding="sm">Link with padding</Box>

// Spreading with explicit rest type annotation
interface CardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'title'> {
  title: React.ReactNode; // Override HTML title attribute
  subtitle?: string;
}

function Card({
  title,
  subtitle,
  children,
  ...divProps
}: CardProps): React.ReactElement {
  return (
    <div {...divProps}>
      <header>
        <h2>{title}</h2>
        {subtitle && <p>{subtitle}</p>}
      </header>
      <div>{children}</div>
    </div>
  );
}

Benefits:

  • Proper typing catches invalid props at compile time
  • IDE shows valid HTML attributes when using the component
  • Omit removes props that conflict with custom props
  • ComponentPropsWithoutRef properly excludes ref from spread
  • Types clearly show which props are custom vs passed through

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