React with TypeScript Integration

1. Component Props TypeScript Interfaces and Types

Pattern Syntax Use Case Example
Basic Interface interface Props { ... } Define component prop types Simple props with required/optional fields
Type Alias type Props = { ... } Alternative to interface, unions, intersections When needing union types or primitives
Optional Props prop?: string Props that may not be provided Default values, conditional rendering
Default Props Destructure with defaults Provide fallback values { size = 'medium' }
Children Prop children: React.ReactNode Type children elements Wrapper components, layouts
Generic Props interface Props<T> { ... } Type-safe generic components Lists, tables, data displays
Discriminated Unions type Props = A | B Variant props based on type field Button variants, conditional props

Example: Component props interfaces

// Basic interface
interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;
  variant?: 'primary' | 'secondary' | 'danger';
}

function Button({ label, onClick, disabled = false, variant = 'primary' }: ButtonProps) {
  return (
    <button 
      onClick={onClick} 
      disabled={disabled}
      className={`btn btn-${variant}`}
    >
      {label}
    </button>
  );
}

// Children prop
interface CardProps {
  title: string;
  children: React.ReactNode;
  footer?: React.ReactNode;
}

function Card({ title, children, footer }: CardProps) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <div className="card-body">{children}</div>
      {footer && <div className="card-footer">{footer}</div>}
    </div>
  );
}

// Generic props
interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => React.ReactNode;
  keyExtractor: (item: T) => string | number;
}

function List<T>({ items, renderItem, keyExtractor }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={keyExtractor(item)}>
          {renderItem(item, index)}
        </li>
      ))}
    </ul>
  );
}

// Usage with type inference
<List
  items={users}
  renderItem={(user) => <span>{user.name}</span>}
  keyExtractor={(user) => user.id}
/>

Example: Discriminated unions for variant props

// Button with discriminated union
type BaseButtonProps = {
  label: string;
  disabled?: boolean;
};

type PrimaryButtonProps = BaseButtonProps & {
  variant: 'primary';
  color: 'blue' | 'green' | 'red';
};

type SecondaryButtonProps = BaseButtonProps & {
  variant: 'secondary';
  outlined: boolean;
};

type LinkButtonProps = BaseButtonProps & {
  variant: 'link';
  href: string;
  target?: '_blank' | '_self';
};

type ButtonProps = PrimaryButtonProps | SecondaryButtonProps | LinkButtonProps;

function Button(props: ButtonProps) {
  const { variant, label, disabled } = props;
  
  if (variant === 'primary') {
    // TypeScript knows props.color exists
    return <button className={`btn-${props.color}`}>{label}</button>;
  }
  
  if (variant === 'secondary') {
    // TypeScript knows props.outlined exists
    return <button className={props.outlined ? 'outlined' : ''}>{label}</button>;
  }
  
  // TypeScript knows props.href exists
  return <a href={props.href} target={props.target}>{label}</a>;
}

// Complex discriminated union
type FormFieldProps =
  | { type: 'text'; maxLength?: number; pattern?: string }
  | { type: 'number'; min?: number; max?: number; step?: number }
  | { type: 'select'; options: Array<{ label: string; value: string }> }
  | { type: 'checkbox'; checked: boolean };

function FormField(props: FormFieldProps & { name: string; label: string }) {
  switch (props.type) {
    case 'text':
      return <input type="text" maxLength={props.maxLength} pattern={props.pattern} />;
    case 'number':
      return <input type="number" min={props.min} max={props.max} step={props.step} />;
    case 'select':
      return (
        <select>
          {props.options.map(opt => <option value={opt.value}>{opt.label}</option>)}
        </select>
      );
    case 'checkbox':
      return <input type="checkbox" checked={props.checked} />;
  }
}

2. Hook Type Definitions and Generic Hooks (useState)

Hook Type Syntax Type Inference Example
useState useState<Type>(initial) Auto-infers from initial value const [count, setCount] = useState(0)
useRef useRef<HTMLElement | null>(null) Must specify element type const ref = useRef<HTMLDivElement>(null)
useReducer useReducer<Reducer<State, Action>> Type state and action Typed reducer with discriminated unions
useContext useContext<ContextType>(Context) Infers from context creation Type-safe context consumption
useMemo useMemo<ReturnType>(() => ...) Auto-infers return type Type-safe memoization
useCallback useCallback<FunctionType>(...) Infers function signature Type-safe callbacks
Custom Hooks function useHook<T>(): ReturnType Explicit return type Generic reusable hooks

Example: Typed React hooks

// useState with explicit type
const [user, setUser] = useState<User | null>(null);
const [count, setCount] = useState(0); // inferred as number

// useState with complex types
interface FormState {
  email: string;
  password: string;
  errors: Record<string, string>;
}

const [form, setForm] = useState<FormState>({
  email: '',
  password: '',
  errors: {},
});

// useRef with DOM elements
const inputRef = useRef<HTMLInputElement>(null);
const divRef = useRef<HTMLDivElement>(null);

useEffect(() => {
  if (inputRef.current) {
    inputRef.current.focus(); // TypeScript knows .focus() exists
  }
}, []);

// useRef for mutable values
const intervalRef = useRef<NodeJS.Timeout | null>(null);

useEffect(() => {
  intervalRef.current = setInterval(() => {
    console.log('tick');
  }, 1000);
  
  return () => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
    }
  };
}, []);

// useReducer with typed actions
type State = { count: number; lastAction: string };

type Action =
  | { type: 'increment'; payload?: number }
  | { type: 'decrement'; payload?: number }
  | { type: 'reset' };

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'increment':
      return { 
        count: state.count + (action.payload ?? 1), 
        lastAction: 'increment' 
      };
    case 'decrement':
      return { 
        count: state.count - (action.payload ?? 1), 
        lastAction: 'decrement' 
      };
    case 'reset':
      return { count: 0, lastAction: 'reset' };
    default:
      return state;
  }
}

const [state, dispatch] = useReducer(reducer, { count: 0, lastAction: 'init' });

// TypeScript validates action types
dispatch({ type: 'increment', payload: 5 }); // ✅
dispatch({ type: 'invalid' }); // ❌ Error

Example: Generic custom hooks

// Generic fetch hook
function useFetch<T>(url: string) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    let cancelled = false;

    async function fetchData() {
      try {
        const response = await fetch(url);
        const json = await response.json();
        
        if (!cancelled) {
          setData(json as T);
          setLoading(false);
        }
      } catch (err) {
        if (!cancelled) {
          setError(err instanceof Error ? err : new Error('Unknown error'));
          setLoading(false);
        }
      }
    }

    fetchData();

    return () => {
      cancelled = true;
    };
  }, [url]);

  return { data, loading, error };
}

// Usage with type inference
interface User {
  id: number;
  name: string;
  email: string;
}

function UserProfile({ userId }: { userId: number }) {
  const { data: user, loading, error } = useFetch<User>(`/api/users/${userId}`);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!user) return <div>No user found</div>;
  
  // TypeScript knows user has id, name, email
  return <div>{user.name} - {user.email}</div>;
}

// Generic array hook with constraints
function useArray<T>(initialValue: T[] = []) {
  const [array, setArray] = useState<T[]>(initialValue);

  const push = useCallback((item: T) => {
    setArray(prev => [...prev, item]);
  }, []);

  const remove = useCallback((index: number) => {
    setArray(prev => prev.filter((_, i) => i !== index));
  }, []);

  const update = useCallback((index: number, item: T) => {
    setArray(prev => prev.map((val, i) => i === index ? item : val));
  }, []);

  const clear = useCallback(() => {
    setArray([]);
  }, []);

  return { array, set: setArray, push, remove, update, clear };
}

// Usage
const { array: todos, push, remove } = useArray<Todo>([]);

// Generic storage hook
function useLocalStorage<T>(key: string, initialValue: T) {
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });

  const setValue = useCallback((value: T | ((val: T) => T)) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  }, [key, storedValue]);

  return [storedValue, setValue] as const;
}

3. Event Handler Type Safety (React.MouseEvent, React.ChangeEvent)

Event Type React Type Native Type Common Usage
Click React.MouseEvent<HTMLElement> MouseEvent Button clicks, div clicks
Change React.ChangeEvent<HTMLInputElement> Event Input, textarea, select changes
Submit React.FormEvent<HTMLFormElement> Event Form submissions
Focus React.FocusEvent<HTMLElement> FocusEvent Input focus/blur
Keyboard React.KeyboardEvent<HTMLElement> KeyboardEvent Key press, key down, key up
Drag React.DragEvent<HTMLElement> DragEvent Drag and drop interactions

Example: Type-safe event handlers

// Click events
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
  event.preventDefault();
  console.log('Button clicked', event.currentTarget.name);
}

// Different element types
function handleDivClick(event: React.MouseEvent<HTMLDivElement>) {
  console.log('Div clicked at', event.clientX, event.clientY);
}

// Input change events
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
  const value = event.target.value;
  const name = event.target.name;
  console.log(`${name}: ${value}`);
}

// Select change events
function handleSelectChange(event: React.ChangeEvent<HTMLSelectElement>) {
  const selectedValue = event.target.value;
  const selectedIndex = event.target.selectedIndex;
  console.log(`Selected: ${selectedValue} at index ${selectedIndex}`);
}

// Form submission
function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
  event.preventDefault();
  const formData = new FormData(event.currentTarget);
  const data = Object.fromEntries(formData);
  console.log('Form data:', data);
}

// Keyboard events
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
  if (event.key === 'Enter' && !event.shiftKey) {
    event.preventDefault();
    console.log('Enter pressed');
  }
  
  // Check modifiers
  if (event.ctrlKey && event.key === 's') {
    event.preventDefault();
    console.log('Ctrl+S pressed');
  }
}

// Complete form component
interface FormData {
  email: string;
  password: string;
}

function LoginForm() {
  const [formData, setFormData] = useState<FormData>({
    email: '',
    password: '',
  });

  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = event.target;
    setFormData(prev => ({ ...prev, [name]: value }));
  };

  const handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    console.log('Submitting:', formData);
  };

  return (
    <form onSubmit={handleFormSubmit}>
      <input
        type="email"
        name="email"
        value={formData.email}
        onChange={handleInputChange}
      />
      <input
        type="password"
        name="password"
        value={formData.password}
        onChange={handleInputChange}
      />
      <button type="submit">Login</button>
    </form>
  );
}

Example: Generic event handler types

// Reusable event handler types
type ClickHandler = React.MouseEvent<HTMLButtonElement>;
type InputChangeHandler = React.ChangeEvent<HTMLInputElement>;
type FormSubmitHandler = React.FormEvent<HTMLFormElement>;

interface ButtonProps {
  onClick: (event: ClickHandler) => void;
  label: string;
}

function Button({ onClick, label }: ButtonProps) {
  return <button onClick={onClick}>{label}</button>;
}

// Generic handler wrapper
type ElementClickHandler<T extends HTMLElement> = (
  event: React.MouseEvent<T>
) => void;

interface ClickableProps<T extends HTMLElement> {
  onClick: ElementClickHandler<T>;
  children: React.ReactNode;
}

// Event handler with custom parameters
interface SearchInputProps {
  onSearch: (query: string) => void;
  onClear: () => void;
}

function SearchInput({ onSearch, onClear }: SearchInputProps) {
  const [query, setQuery] = useState('');

  const handleChange: InputChangeHandler = (event) => {
    setQuery(event.target.value);
  };

  const handleSubmit: FormSubmitHandler = (event) => {
    event.preventDefault();
    onSearch(query); // Call with typed parameter
  };

  const handleClear: ClickHandler = (event) => {
    event.preventDefault();
    setQuery('');
    onClear();
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={query} onChange={handleChange} />
      <button type="submit">Search</button>
      <button type="button" onClick={handleClear}>Clear</button>
    </form>
  );
}

4. Context API TypeScript Patterns and Type Inference

Pattern Implementation Benefits Use Case
Typed Context createContext<Type | null>(null) Type safety for context value All context implementations
Context with Hook Custom hook that throws if outside provider Guaranteed non-null context, better DX Enforcing provider usage
Generic Context createContext<T> with generic Reusable context pattern Generic state containers
Context with Actions Separate state and dispatch Type-safe actions, better organization Complex state management
Multiple Contexts Compose multiple providers Separation of concerns Large apps with different domains

Example: Type-safe context with custom hook

// Define context type
interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user';
}

interface AuthContextType {
  user: User | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  isAuthenticated: boolean;
}

// Create context with null default
const AuthContext = createContext<AuthContextType | null>(null);

// Provider component
export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  const login = async (email: string, password: string) => {
    // API call
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    });
    const userData = await response.json();
    setUser(userData);
  };

  const logout = () => {
    setUser(null);
  };

  const value: AuthContextType = {
    user,
    login,
    logout,
    isAuthenticated: user !== null,
  };

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

// Custom hook with type guard
export function useAuth(): AuthContextType {
  const context = useContext(AuthContext);
  
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  
  return context;
}

// Usage - TypeScript knows context is never null
function Profile() {
  const { user, logout } = useAuth(); // No null check needed
  
  return (
    <div>
      <h1>{user?.name}</h1>
      <button onClick={logout}>Logout</button>
    </div>
  );
}

Example: Context with reducer pattern

// State and actions
interface Todo {
  id: string;
  text: string;
  completed: boolean;
}

interface TodoState {
  todos: Todo[];
  filter: 'all' | 'active' | 'completed';
}

type TodoAction =
  | { type: 'ADD_TODO'; text: string }
  | { type: 'TOGGLE_TODO'; id: string }
  | { type: 'DELETE_TODO'; id: string }
  | { type: 'SET_FILTER'; filter: TodoState['filter'] };

// Reducer
function todoReducer(state: TodoState, action: TodoAction): TodoState {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [
          ...state.todos,
          { id: Date.now().toString(), text: action.text, completed: false },
        ],
      };
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.id ? { ...todo, completed: !todo.completed } : todo
        ),
      };
    case 'DELETE_TODO':
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.id),
      };
    case 'SET_FILTER':
      return { ...state, filter: action.filter };
    default:
      return state;
  }
}

// Context types
interface TodoContextType {
  state: TodoState;
  dispatch: React.Dispatch<TodoAction>;
}

const TodoContext = createContext<TodoContextType | null>(null);

// Provider
export function TodoProvider({ children }: { children: React.ReactNode }) {
  const [state, dispatch] = useReducer(todoReducer, {
    todos: [],
    filter: 'all',
  });

  return (
    <TodoContext.Provider value={{ state, dispatch }}>
      {children}
    </TodoContext.Provider>
  );
}

// Custom hooks
export function useTodos() {
  const context = useContext(TodoContext);
  if (!context) throw new Error('useTodos must be used within TodoProvider');
  return context;
}

// Helper hooks
export function useTodoActions() {
  const { dispatch } = useTodos();

  return {
    addTodo: (text: string) => dispatch({ type: 'ADD_TODO', text }),
    toggleTodo: (id: string) => dispatch({ type: 'TOGGLE_TODO', id }),
    deleteTodo: (id: string) => dispatch({ type: 'DELETE_TODO', id }),
    setFilter: (filter: TodoState['filter']) => dispatch({ type: 'SET_FILTER', filter }),
  };
}

// Usage
function TodoList() {
  const { state } = useTodos();
  const { toggleTodo, deleteTodo } = useTodoActions();

  return (
    <ul>
      {state.todos.map(todo => (
        <li key={todo.id}>
          <input 
            type="checkbox" 
            checked={todo.completed}
            onChange={() => toggleTodo(todo.id)}
          />
          {todo.text}
          <button onClick={() => deleteTodo(todo.id)}>Delete</button>
        </li>
      ))}
    </ul>
  );
}

5. Custom Hook Type Safety and Return Types

Concept Pattern Benefit Example
Return Type Inference Let TypeScript infer return type Less verbose, auto-updates Hook without explicit return type
Explicit Return Type Define return type explicitly Better documentation, API contracts Public library hooks
Tuple Return return [value, setter] as const Array destructuring with types useState-like hooks
Generic Hooks function useHook<T>() Reusable with different types Generic data fetching, storage
Conditional Types Return type based on parameters Smart type inference Optional parameters affecting return
Type Guards Runtime checks with type narrowing Type safety with validation Parsing external data

Example: Custom hook with type inference

// Inferred return type
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);
  
  const increment = useCallback(() => setCount(c => c + 1), []);
  const decrement = useCallback(() => setCount(c => c - 1), []);
  const reset = useCallback(() => setCount(initialValue), [initialValue]);
  
  // Return type inferred as { count: number; increment: () => void; ... }
  return { count, increment, decrement, reset };
}

// Tuple return with const assertion
function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);
  const toggle = useCallback(() => setValue(v => !v), []);
  
  // 'as const' makes it a tuple, not array
  return [value, toggle] as const;
}

// Usage with proper types
const [isOpen, toggleOpen] = useToggle(); // isOpen: boolean, toggleOpen: () => void

// Generic hook with constraints
function useAsync<T, E = Error>(
  asyncFunction: () => Promise<T>,
  immediate = true
) {
  const [status, setStatus] = useState<'idle' | 'pending' | 'success' | 'error'>('idle');
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<E | null>(null);

  const execute = useCallback(async () => {
    setStatus('pending');
    setData(null);
    setError(null);

    try {
      const response = await asyncFunction();
      setData(response);
      setStatus('success');
      return response;
    } catch (err) {
      setError(err as E);
      setStatus('error');
      throw err;
    }
  }, [asyncFunction]);

  useEffect(() => {
    if (immediate) {
      execute();
    }
  }, [execute, immediate]);

  return { execute, status, data, error };
}

// Usage with type inference
interface User {
  id: number;
  name: string;
}

function UserProfile() {
  const { data: user, status } = useAsync<User>(
    () => fetch('/api/user').then(r => r.json()),
    true
  );
  
  // TypeScript knows user is User | null
  if (status === 'success' && user) {
    return <div>{user.name}</div>;
  }
  
  return <div>Loading...</div>;
}

Example: Advanced generic hooks

// Generic form hook
interface UseFormOptions<T> {
  initialValues: T;
  onSubmit: (values: T) => void | Promise<void>;
  validate?: (values: T) => Partial<Record<keyof T, string>>;
}

function useForm<T extends Record<string, any>>({
  initialValues,
  onSubmit,
  validate,
}: UseFormOptions<T>) {
  const [values, setValues] = useState<T>(initialValues);
  const [errors, setErrors] = useState<Partial<Record<keyof T, string>>>({});
  const [isSubmitting, setIsSubmitting] = useState(false);

  const handleChange = useCallback((name: keyof T, value: any) => {
    setValues(prev => ({ ...prev, [name]: value }));
    setErrors(prev => ({ ...prev, [name]: undefined }));
  }, []);

  const handleSubmit = useCallback(async (event?: React.FormEvent) => {
    event?.preventDefault();

    if (validate) {
      const validationErrors = validate(values);
      if (Object.keys(validationErrors).length > 0) {
        setErrors(validationErrors);
        return;
      }
    }

    setIsSubmitting(true);
    try {
      await onSubmit(values);
    } finally {
      setIsSubmitting(false);
    }
  }, [values, validate, onSubmit]);

  const reset = useCallback(() => {
    setValues(initialValues);
    setErrors({});
  }, [initialValues]);

  return { values, errors, isSubmitting, handleChange, handleSubmit, reset };
}

// Usage with type inference
interface LoginForm {
  email: string;
  password: string;
}

function Login() {
  const { values, errors, handleChange, handleSubmit } = useForm<LoginForm>({
    initialValues: { email: '', password: '' },
    onSubmit: async (values) => {
      await login(values.email, values.password);
    },
    validate: (values) => {
      const errors: Partial<Record<keyof LoginForm, string>> = {};
      if (!values.email) errors.email = 'Required';
      if (!values.password) errors.password = 'Required';
      return errors;
    },
  });

  // TypeScript knows values.email and values.password exist
  return (
    <form onSubmit={handleSubmit}>
      <input
        value={values.email}
        onChange={(e) => handleChange('email', e.target.value)}
      />
      {errors.email && <span>{errors.email}</span>}
      
      <input
        type="password"
        value={values.password}
        onChange={(e) => handleChange('password', e.target.value)}
      />
      {errors.password && <span>{errors.password}</span>}
      
      <button type="submit">Login</button>
    </form>
  );
}

6. React.FC vs Function Component Types - Best Practices

Approach Syntax Pros Cons
React.FC const Comp: React.FC<Props> = ... Includes children type, shorter syntax Implicit children (not always wanted), verbose
Function Declaration function Comp(props: Props) {...} Explicit, clear, standard TypeScript Need to define children explicitly
Arrow Function const Comp = (props: Props) => ... Concise, explicit types No hoisting, slightly more verbose
With Generics function Comp<T>(props: Props<T>) {...} Type-safe generic components More complex, requires understanding generics

Example: React.FC vs function declaration

// ❌ React.FC (less recommended now)
interface ButtonProps {
  label: string;
  onClick: () => void;
}

const Button: React.FC<ButtonProps> = ({ label, onClick, children }) => {
  // children is implicitly included (might not be wanted)
  return <button onClick={onClick}>{label}{children}</button>;
};

// ✅ Function declaration (recommended)
interface CardProps {
  title: string;
  children: React.ReactNode; // Explicit children
}

function Card({ title, children }: CardProps) {
  return (
    <div className="card">
      <h2>{title}</h2>
      {children}
    </div>
  );
}

// ✅ Arrow function (also good)
interface AlertProps {
  message: string;
  type: 'info' | 'warning' | 'error';
}

const Alert = ({ message, type }: AlertProps) => {
  return <div className={`alert alert-${type}`}>{message}</div>;
};

// ✅ Function with explicit return type
function UserCard({ user }: { user: User }): JSX.Element {
  return (
    <div>
      <h3>{user.name}</h3>
      <p>{user.email}</p>
    </div>
  );
}

// Generic component - function declaration
function List<T>({ items, renderItem }: {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

// Component with default props (function declaration)
interface BadgeProps {
  text: string;
  color?: 'red' | 'blue' | 'green';
  size?: 'small' | 'medium' | 'large';
}

function Badge({ text, color = 'blue', size = 'medium' }: BadgeProps) {
  return <span className={`badge badge-${color} badge-${size}`}>{text}</span>;
}

Example: Component type patterns

// Polymorphic component (advanced)
type AsProp<C extends React.ElementType> = {
  as?: C;
};

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

type PolymorphicComponentProp<
  C extends React.ElementType,
  Props = {}
> = React.PropsWithChildren<Props & AsProp<C>> &
  Omit<React.ComponentPropsWithoutRef<C>, PropsToOmit<C, Props>>;

type PolymorphicComponentPropWithRef<
  C extends React.ElementType,
  Props = {}
> = PolymorphicComponentProp<C, Props> & { ref?: PolymorphicRef<C> };

type PolymorphicRef<C extends React.ElementType> = 
  React.ComponentPropsWithRef<C>['ref'];

// Usage
interface TextProps {
  color?: 'primary' | 'secondary';
}

function Text<C extends React.ElementType = 'span'>({
  as,
  color = 'primary',
  children,
  ...props
}: PolymorphicComponentPropWithRef<C, TextProps>) {
  const Component = as || 'span';
  return (
    <Component className={`text-${color}`} {...props}>
      {children}
    </Component>
  );
}

// Can be used as any element
<Text>Default span</Text>
<Text as="h1">Heading</Text>
<Text as="a" href="/link">Link</Text> // href is type-safe!

// Component with display name
const MyComponent = ({ name }: { name: string }) => {
  return <div>{name}</div>;
};

MyComponent.displayName = 'MyComponent'; // For React DevTools

// Component with static methods
interface TabsComponent {
  ({ children }: { children: React.ReactNode }): JSX.Element;
  Panel: ({ children }: { children: React.ReactNode }) => JSX.Element;
}

const Tabs: TabsComponent = ({ children }) => {
  return <div className="tabs">{children}</div>;
};

Tabs.Panel = ({ children }) => {
  return <div className="tab-panel">{children}</div>;
};

// Usage: <Tabs><Tabs.Panel>Content</Tabs.Panel></Tabs>
TypeScript Best Practices: Prefer function declarations over React.FC, explicitly define children prop when needed, use generics for reusable components, leverage type inference when possible, use discriminated unions for variant props, add explicit return types for public APIs, use const assertions for tuple returns, validate external data with runtime checks.

React TypeScript Integration Summary