Skip to content

01 - React + TypeScript Patterns ​

Why This Matters for Glomopay ​

They build checkout SDKs and merchant dashboards — type safety is critical in fintech where a wrong type can mean wrong money movement.


1. Component Typing ​

Basic Props ​

tsx
// Always use interface for component props (extendable, better error messages)
interface PaymentFormProps {
  amount: number;
  currency: string;
  onSubmit: (data: PaymentData) => Promise<void>;
  onCancel?: () => void; // optional
}

function PaymentForm({ amount, currency, onSubmit, onCancel }: PaymentFormProps) {
  // ...
}

Children Patterns ​

tsx
// Explicit children
interface LayoutProps {
  children: React.ReactNode; // most flexible — accepts anything renderable
}

// Render prop
interface DataLoaderProps<T> {
  url: string;
  children: (data: T, isLoading: boolean) => React.ReactNode;
}

Event Handlers ​

tsx
// Don't do: (e: any) => ...
// Do:
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
  setValue(e.target.value);
}

function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
  e.preventDefault();
}

// For callbacks passed as props — use the domain type, not the event
interface Props {
  onAmountChange: (amount: number) => void; // NOT (e: ChangeEvent) => void
}

2. Discriminated Unions (Very Important) ​

This is the #1 TypeScript pattern for React. Used for state machines, API responses, and multi-step flows (like checkout).

tsx
// Payment status — each state carries only its relevant data
type PaymentState =
  | { status: 'idle' }
  | { status: 'processing'; startedAt: number }
  | { status: 'success'; transactionId: string; receipt: Receipt }
  | { status: 'error'; error: string; retryable: boolean };

// Usage — TypeScript narrows the type inside each branch
function PaymentStatus({ state }: { state: PaymentState }) {
  switch (state.status) {
    case 'idle':
      return <p>Ready to pay</p>;
    case 'processing':
      return <Spinner startedAt={state.startedAt} />;
    case 'success':
      return <Receipt id={state.transactionId} data={state.receipt} />;
    case 'error':
      return (
        <div>
          <p>{state.error}</p>
          {state.retryable && <button>Retry</button>}
        </div>
      );
  }
}

Why it matters ​

  • Impossible states become unrepresentable
  • No if (state.transactionId) guesswork
  • Compiler catches missing cases with exhaustive check
tsx
// Exhaustive check helper
function assertNever(x: never): never {
  throw new Error(`Unexpected value: ${x}`);
}

// Add to switch default — compiler errors if you miss a case
default: return assertNever(state);

3. Generics ​

Generic Components ​

tsx
// Reusable list component
interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
  keyExtractor: (item: T) => string;
  emptyMessage?: string;
}

function List<T>({ items, renderItem, keyExtractor, emptyMessage }: ListProps<T>) {
  if (items.length === 0) return <p>{emptyMessage ?? 'No items'}</p>;
  return (
    <ul>
      {items.map((item) => (
        <li key={keyExtractor(item)}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}

// Usage — T is inferred
<List
  items={transactions}
  renderItem={(tx) => <TransactionRow tx={tx} />}
  keyExtractor={(tx) => tx.id}
/>

Generic Hooks ​

tsx
// API fetch hook with generic return type
function useApi<T>(url: string): {
  data: T | null;
  isLoading: boolean;
  error: string | null;
} {
  const [data, setData] = useState<T | null>(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    fetch(url)
      .then((res) => res.json())
      .then((json: T) => setData(json))
      .catch((err) => setError(err.message))
      .finally(() => setIsLoading(false));
  }, [url]);

  return { data, isLoading, error };
}

// Usage
const { data: payment } = useApi<Payment>('/api/payments/123');
// payment is Payment | null — fully typed

4. Utility Types (Know These) ​

tsx
interface User {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'merchant' | 'user';
  createdAt: Date;
}

// Pick — only specific fields
type UserPreview = Pick<User, 'id' | 'name'>;

// Omit — everything except
type CreateUserInput = Omit<User, 'id' | 'createdAt'>;

// Partial — all fields optional (useful for update payloads)
type UpdateUserInput = Partial<Omit<User, 'id'>>;

// Required — make all fields required
type CompleteUser = Required<User>;

// Record — typed dictionary
type TransactionsByStatus = Record<PaymentStatus, Transaction[]>;

// Extract / Exclude — filter union types
type SuccessOrError = Extract<PaymentState, { status: 'success' | 'error' }>;

5. Hooks Typing ​

tsx
// useRef — specify the element type
const inputRef = useRef<HTMLInputElement>(null);
const timerRef = useRef<number | null>(null); // for mutable refs

// useReducer — covered in state management notes
// useState with complex types
const [filters, setFilters] = useState<FilterState>({ page: 1, query: '' });

// forwardRef (React 19 — ref is a regular prop now)
// React 19:
interface InputProps {
  label: string;
  ref?: React.Ref<HTMLInputElement>;
}
function Input({ label, ref }: InputProps) {
  return <input ref={ref} aria-label={label} />;
}

// Pre-React 19:
const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
  return <input ref={ref} aria-label={props.label} />;
});

6. as const and Literal Types ​

tsx
// as const for config objects
const PAYMENT_METHODS = ['card', 'upi', 'netbanking', 'wallet'] as const;
type PaymentMethod = (typeof PAYMENT_METHODS)[number];
// type is 'card' | 'upi' | 'netbanking' | 'wallet' — not string[]

// Useful for steps in a checkout flow
const CHECKOUT_STEPS = ['cart', 'payment', 'review', 'confirmation'] as const;
type CheckoutStep = (typeof CHECKOUT_STEPS)[number];

Quick Revision ​

  • Use interface for props, type for unions and computed types
  • Discriminated unions for any multi-state scenario
  • Generics for reusable components and hooks
  • Utility types to derive types (don't duplicate)
  • as const for literal arrays/objects
  • Never use any — use unknown if the type is truly unknown

Frontend interview preparation reference.