Katkat

Build and Optimize a React Interface Against an Undocumented API

StripeFrontend Engineer · Onsite

Last updated

Tests your ability to inspect an opaque or changing backend endpoint, build a resilient React component with typed normalization and client-side caching, and handle edge cases gracefully.

Scenario

You are tasked with building a DisputeReviewPanel component in React that fetches and displays payment disputes from an undocumented internal endpoint: GET /api/v1/disputes.

Because the backend schema is currently evolving without formal documentation, you must inspect the actual response payloads, defensively normalize the data into a strict client-side model, implement client-side caching to prevent redundant network requests when switching filters, and handle race conditions when search inputs change rapidly.

Requirements

  1. Data Discovery & Normalization: The API returns an array under data, but individual dispute items use inconsistent property names and formatting:

    • Identifier may be id or dispute_id (string).
    • Amount might be integer cents (amount: 2500), a float (amount_dollars: 25.0), or a string ("$25.00"). Normalize all amounts to integer cents.
    • Status is a string that maps to one of three normalized states: 'needs_response', 'under_review', or 'closed'. Unrecognized statuses should default to 'needs_response'.
    • Creation timestamp may be Unix seconds (created) or ISO 8601 string (created_at). Normalize to standard Unix epoch milliseconds.
  2. Component Interface & State:

    • Implement DisputeReviewPanel({ fetchDisputes, pageSize = 10 }: DisputeReviewPanelProps).
    • Support filtering by normalized status: 'all' | 'needs_response' | 'under_review' | 'closed'.
    • Implement search by dispute identifier or reason.
  3. Caching & Concurrency:

    • Maintain an in-memory client cache keyed by query parameters (status, search, page) with a 60-second time-to-live (TTL).
    • Ensure in-flight requests are aborted or ignored if the filter/query changes before the response arrives (prevent race conditions / stale overwrites).
    • Provide standard loading (Loading...), error (Retry), and empty states (No disputes found).

Example Usage & Normalization

// Raw API payload snippet:
{
  "data": [
    {
      "dispute_id": "dp_101",
      "amount": 4200,
      "status": "warning_needs_response",
      "created": 1700000000
    },
    {
      "id": "dp_102",
      "amount_dollars": 15.5,
      "status": "WON",
      "created_at": "2023-11-14T22:13:20Z"
    }
  ],
  "has_more": false
}

// Expected normalized model:
[
  {
    id: "dp_101",
    amountCents: 4200,
    status: "needs_response",
    createdAtMs: 1700000000000
  },
  {
    id: "dp_102",
    amountCents: 1550,
    status: "closed",
    createdAtMs: 1700000000000
  }
]

Constraints & Edge Cases

  • Do not mutate the raw API response object directly.
  • Handle floating-point arithmetic errors when converting dollar floats to cents (e.g., Math.round(val * 100)).
  • If the network fails, allow the user to click Retry without discarding existing input values.
  • Cache keys must be deterministic regardless of parameter order.

Hints

Hint 1

Write an explicit parser/adapter function with runtime checks (such as checking `typeof` or using a schema validator) before touching React state.

Hint 2

Use an `AbortController` inside a `useEffect` hook to cancel inflight fetch requests when filter dependencies update.

Hint 3

Structure your in-memory cache as a Map or plain object mapping a normalized string key like `${status}:${query}:${page}` to `{ data, timestamp }`.

Follow-up questions

  • How would you integrate an automated schema validation library like Zod to alert your team when the undocumented API introduces breaking changes?
  • If users can resolve disputes directly in this UI, how would you handle cache invalidation and optimistic updates?
  • How would you adapt the caching layer to persist across browser page reloads without risking stale financial data?