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-forward-ref.md

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

ForwardRef Typing

Impact: CRITICAL (ensures ref types match actual DOM elements)

Properly typing components that forward refs to child elements. Mismatched ref types cause runtime errors or require unsafe type assertions.

Incorrect

// ❌ Bad
// Missing ref type annotation
const Input = React.forwardRef((props, ref) => {
  return <input ref={ref} {...props} />;
});

// Incorrect ref type
interface ButtonProps {
  variant: 'primary' | 'secondary';
}

const Button = React.forwardRef<HTMLDivElement, ButtonProps>((props, ref) => {
  // Ref type doesn't match the actual element
  return <button ref={ref as any}>{props.variant}</button>;
});

// Not exposing ref at all for imperative handle
const ComplexInput = React.forwardRef((props: InputProps, ref) => {
  const inputRef = React.useRef<HTMLInputElement>(null);

  // ref is ignored, parent can't access input
  return <input ref={inputRef} {...props} />;
});

Problems:

  • Missing type annotations result in any types for ref and props
  • Mismatched ref element types require unsafe as any casts
  • Ignoring the forwarded ref makes the component unusable by parent components

Correct

// ✅ Good
import React, { forwardRef, useRef, useImperativeHandle } from 'react';

// Basic forwardRef with proper typing
interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
  label: string;
  error?: string;
  size?: 'sm' | 'md' | 'lg';
}

const Input = forwardRef<HTMLInputElement, InputProps>(
  ({ label, error, size = 'md', className, ...rest }, ref) => {
    return (
      <div className={`input-wrapper input-${size}`}>
        <label>{label}</label>
        <input
          ref={ref}
          className={`input ${error ? 'input-error' : ''} ${className ?? ''}`}
          aria-invalid={!!error}
          {...rest}
        />
        {error && <span className="error-message">{error}</span>}
      </div>
    );
  }
);

Input.displayName = 'Input';

// ForwardRef with useImperativeHandle for custom methods
interface FormInputHandle {
  focus: () => void;
  clear: () => void;
  getValue: () => string;
  validate: () => boolean;
}

interface FormInputProps {
  name: string;
  label: string;
  required?: boolean;
  pattern?: RegExp;
}

const FormInput = forwardRef<FormInputHandle, FormInputProps>(
  ({ name, label, required = false, pattern }, ref) => {
    const inputRef = useRef<HTMLInputElement>(null);
    const [error, setError] = React.useState<string | null>(null);

    useImperativeHandle(ref, () => ({
      focus: () => {
        inputRef.current?.focus();
      },
      clear: () => {
        if (inputRef.current) {
          inputRef.current.value = '';
          setError(null);
        }
      },
      getValue: () => {
        return inputRef.current?.value ?? '';
      },
      validate: () => {
        const value = inputRef.current?.value ?? '';

        if (required && !value) {
          setError('This field is required');
          return false;
        }

        if (pattern && !pattern.test(value)) {
          setError('Invalid format');
          return false;
        }

        setError(null);
        return true;
      },
    }));

    return (
      <div className="form-input">
        <label htmlFor={name}>{label}</label>
        <input ref={inputRef} id={name} name={name} aria-invalid={!!error} />
        {error && <span className="error">{error}</span>}
      </div>
    );
  }
);

FormInput.displayName = 'FormInput';

// Generic forwardRef component
interface SelectOption<T> {
  value: T;
  label: string;
}

interface SelectProps<T> {
  options: SelectOption<T>[];
  value?: T;
  onChange: (value: T) => void;
  placeholder?: string;
}

type GenericForwardRefComponent = <T>(
  props: SelectProps<T> & { ref?: React.ForwardedRef<HTMLSelectElement> }
) => React.ReactElement;

const Select: GenericForwardRefComponent = forwardRef(
  <T,>(
    { options, value, onChange, placeholder }: SelectProps<T>,
    ref: React.ForwardedRef<HTMLSelectElement>
  ) => {
    return (
      <select
        ref={ref}
        value={String(value)}
        onChange={(e) => {
          const selected = options.find((opt) => String(opt.value) === e.target.value);
          if (selected) onChange(selected.value);
        }}
      >
        {placeholder && <option value="">{placeholder}</option>}
        {options.map((option) => (
          <option key={String(option.value)} value={String(option.value)}>
            {option.label}
          </option>
        ))}
      </select>
    );
  }
) as GenericForwardRefComponent;

Benefits:

  • Proper typing ensures ref type matches the actual DOM element
  • useImperativeHandle enables custom methods with full type safety
  • Setting displayName improves React DevTools debugging
  • Special patterns enable generic forwardRef components
  • Parent components can imperatively control children safely

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