{"id":"9d13e5c8-bdf0-4e58-874b-99720e591a47","shortId":"HFSSNx","kind":"skill","title":"frontend-api-integration-patterns","tagline":"Production-ready patterns for integrating frontend applications with backend APIs, including race condition handling, request cancellation, retry strategies, error normalization, and UI state management.","description":"# Frontend API Integration Patterns\n\n## Overview\n\nThis skill provides production-ready patterns for integrating frontend applications with backend APIs.\n\nMost frontend issues are not caused by APIs being difficult to call, but by **incorrect handling of asynchronous behavior**—leading to race conditions, stale data, duplicated requests, and poor user experience.\n\nThis skill focuses on **correctness, resilience, and user experience**, not just making API calls work.\n\n---\n\n## When to Use This Skill\n\n* Connecting frontend apps (React, React Native, Vue, etc.) to backend APIs\n* Integrating ML/AI endpoints (`/predict`, `/recommend`)\n* Handling asynchronous data in UI\n* Fixing stale data, flickering UI, or duplicate requests\n* Designing scalable frontend API layers\n\n---\n\n## Core Patterns\n\n### 1. API Layer (Separation of Concerns)\n\nCentralize API logic and normalize errors.\n\n```js id=\"k1m7r2\"\nexport class ApiError extends Error {\n  constructor(message, status, payload = null) {\n    super(message);\n    this.name = \"ApiError\";\n    this.status = status;\n    this.payload = payload;\n  }\n}\n\nexport const apiClient = async (url, options = {}) => {\n  const res = await fetch(url, {\n    headers: { \"Content-Type\": \"application/json\" },\n    ...options,\n  });\n\n  if (!res.ok) {\n    let payload = null;\n    try {\n      payload = await res.json();\n    } catch (_) {}\n\n    throw new ApiError(\n      payload?.message || \"Request failed\",\n      res.status,\n      payload\n    );\n  }\n\n  // handle empty responses safely (e.g. 204 No Content)\n  if (res.status === 204) return null;\n\n  const text = await res.text();\n  return text ? JSON.parse(text) : null;\n};\n```\n\n---\n\n### 2. Race-Safe State Management\n\nPrevent stale responses from overwriting fresh data.\n\n```js id=\"y7p4ha\"\nuseEffect(() => {\n  let cancelled = false;\n\n  const load = async () => {\n    try {\n      setLoading(true);\n      setError(null);\n\n      const result = await getUser();\n\n      if (!cancelled) setData(result);\n    } catch (err) {\n      if (!cancelled) setError(err.message);\n    } finally {\n      if (!cancelled) setLoading(false);\n    }\n  };\n\n  load();\n\n  return () => {\n    cancelled = true;\n  };\n}, []);\n```\n\n> Use a cancellation flag for non-fetch async logic. For network requests, prefer AbortController.\n\n---\n\n### 3. Request Cancellation (AbortController)\n\nCancel in-flight requests to avoid memory leaks and stale updates.\n\n```js id=\"l9x2pw\"\nuseEffect(() => {\n  const controller = new AbortController();\n\n  const load = async () => {\n    try {\n      const data = await getUser({ signal: controller.signal });\n      setData(data);\n    } catch (err) {\n      if (err.name === \"AbortError\") return;\n      setError(err.message);\n    }\n  };\n\n  load();\n  return () => controller.abort();\n}, [userId]);\n```\n\n---\n\n### 4. Retry with Exponential Backoff\n\nRetry only transient failures (5xx or network errors).\n\n```js id=\"8n3zcf\"\nconst sleep = (ms) => new Promise((r) => setTimeout(r, ms));\n\nconst fetchWithBackoff = async (fn, retries = 3, delay = 300) => {\n  try {\n    return await fn();\n  } catch (err) {\n    const isAbort = err.name === \"AbortError\";\n    const isHttpError = typeof err.status === \"number\";\n    const isRetryable = !isAbort && (!isHttpError || err.status >= 500);\n\n    if (retries <= 0 || !isRetryable) throw err;\n\n    const nextDelay = delay * 2 + Math.random() * 100;\n    await sleep(nextDelay);\n\n    return fetchWithBackoff(fn, retries - 1, nextDelay);\n  }\n};\n```\n\n---\n\n### 5. Debounced API Calls\n\nAvoid excessive API calls (e.g., search inputs).\n\n```js id=\"i2r7wq\"\nconst useDebounce = (value, delay = 400) => {\n  const [debounced, setDebounced] = useState(value);\n\n  useEffect(() => {\n    const t = setTimeout(() => setDebounced(value), delay);\n    return () => clearTimeout(t);\n  }, [value, delay]);\n\n  return debounced;\n};\n```\n\n---\n\n### 6. Request Deduplication\n\nPrevent duplicate API calls across components.\n\n```js id=\"x8v4km\"\nconst inFlight = new Map();\n\nexport const dedupedFetch = (key, fn) => {\n  if (inFlight.has(key)) return inFlight.get(key);\n\n  const promise = fn().finally(() => inFlight.delete(key));\n  inFlight.set(key, promise);\n  return promise;\n};\n```\n\n---\n\n## Examples\n\n### Example 1: ML Prediction with Cancellation\n\n```js id=\"n5q2pt\"\nconst controllerRef = useRef(null);\n\nconst handlePredict = async (input) => {\n  controllerRef.current?.abort();\n  controllerRef.current = new AbortController();\n\n  try {\n    const result = await fetchWithBackoff(() =>\n      apiClient(\"/predict\", {\n        method: \"POST\",\n        body: JSON.stringify({ text: input }),\n        signal: controllerRef.current.signal,\n      })\n    );\n\n    setOutput(result);\n  } catch (err) {\n    if (err.name === \"AbortError\") return;\n    setError(err.message);\n  }\n};\n```\n\n---\n\n### Example 2: Debounced Search\n\n```js id=\"w4z8yn\"\nconst debouncedQuery = useDebounce(query, 400);\n\nuseEffect(() => {\n  if (!debouncedQuery) return;\n\n  const controller = new AbortController();\n\n  searchAPI(debouncedQuery, { signal: controller.signal })\n    .then(setResults)\n    .catch((err) => {\n      if (err.name !== \"AbortError\") {\n        setError(\"Search failed. Please try again.\");\n      }\n    });\n\n  return () => controller.abort();\n}, [debouncedQuery]);\n```\n\n---\n\n### Example 3: Optimistic UI Update\n\n```js id=\"q2k9hz\"\nconst deleteItem = async (id) => {\n  const previous = items;\n\n  setItems((curr) => curr.filter((item) => item.id !== id));\n\n  try {\n    await apiClient(`/items/${id}`, { method: \"DELETE\" });\n  } catch (err) {\n    setItems(previous);\n    setError(\"Delete failed. Please try again.\");\n  }\n};\n```\n\n---\n\n## Best Practices\n\n* ✅ Centralize API logic in a dedicated layer\n* ✅ Normalize errors using a custom error class\n* ✅ Always handle loading, error, and success states\n* ✅ Use AbortController for request cancellation\n* ✅ Retry only transient failures (5xx)\n* ✅ Use debouncing for input-driven APIs\n* ✅ Deduplicate identical requests\n\n---\n\n## Anti-Patterns\n\n* ❌ Retrying 4xx errors\n* ❌ No request cancellation (memory leaks)\n* ❌ Race-condition-prone state updates\n* ❌ Swallowing errors silently\n* ❌ Global loading/error state for multiple requests\n* ❌ Calling APIs directly inside components repeatedly\n\n---\n\n## Common Pitfalls\n\n**Problem:** UI shows stale data\n**Solution:** Use cancellation or guard against outdated responses\n\n**Problem:** Too many API calls on input\n**Solution:** Use debouncing + cancellation\n\n**Problem:** Duplicate requests from multiple components\n**Solution:** Use request deduplication\n\n**Problem:** Server overload during retry\n**Solution:** Use exponential backoff\n\n**Problem:** State updates after component unmount\n**Solution:** Use AbortController cleanup\n\n---\n\n## Limitations\n\n* These examples use vanilla JavaScript patterns; adapt them to your framework's data-fetching library when using React Query, SWR, Apollo, Relay, or similar tools.\n* Do not retry non-idempotent mutations unless the backend provides idempotency keys or another duplicate-safe contract.\n* Do not expose privileged API keys in frontend code; proxy sensitive requests through a backend.\n\n---\n\n## Additional Resources\n\n* https://developer.mozilla.org/en-US/docs/Web/API/AbortController\n* https://react.dev\n* https://axios-http.com\n\n---","tags":["frontend","api","integration","patterns","antigravity","awesome","skills","sickn33","agent-skills","agentic-skills","ai-agent-skills","ai-agents"],"capabilities":["skill","source-sickn33","skill-frontend-api-integration-patterns","topic-agent-skills","topic-agentic-skills","topic-ai-agent-skills","topic-ai-agents","topic-ai-coding","topic-ai-workflows","topic-antigravity","topic-antigravity-skills","topic-claude-code","topic-claude-code-skills","topic-codex-cli","topic-codex-skills"],"categories":["antigravity-awesome-skills"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/sickn33/antigravity-awesome-skills/frontend-api-integration-patterns","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add sickn33/antigravity-awesome-skills","source_repo":"https://github.com/sickn33/antigravity-awesome-skills","install_from":"skills.sh"}},"qualityScore":"0.700","qualityRationale":"deterministic score 0.70 from registry signals: · indexed on github topic:agent-skills · 37911 github stars · SKILL.md body (7,219 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T18:51:05.300Z","embedding":null,"createdAt":"2026-04-24T00:50:59.497Z","updatedAt":"2026-05-18T18:51:05.300Z","lastSeenAt":"2026-05-18T18:51:05.300Z","tsv":"'/en-us/docs/web/api/abortcontroller':814 '/items':605 '/predict':115,522 '/recommend':116 '0':398 '1':137,415,495 '100':407 '2':228,405,542 '204':211,216 '3':294,372,582 '300':374 '4':342 '400':435,552 '4xx':666 '5':417 '500':395 '5xx':351,651 '6':455 '8n3zcf':357 'abort':512 'abortcontrol':293,297,317,515,560,643,747 'aborterror':334,384,537,571 'across':462 'adapt':756 'addit':810 'alway':635 'anoth':790 'anti':663 'anti-pattern':662 'api':3,16,32,49,57,93,111,133,138,144,419,423,460,622,658,689,712,799 'apicli':172,521,604 'apierror':154,165,199 'apollo':771 'app':103 'applic':13,46 'application/json':185 'async':173,250,287,320,369,509,591 'asynchron':67,118 'avoid':304,421 'await':178,194,221,258,324,377,408,519,603 'axios-http.com':816 'backend':15,48,110,785,809 'backoff':346,738 'behavior':68 'best':619 'bodi':525 'call':61,94,420,424,461,688,713 'cancel':22,246,261,267,272,277,281,296,298,499,646,670,703,719 'catch':196,264,330,379,533,567,609 'caus':55 'central':143,621 'class':153,634 'cleanup':748 'cleartimeout':449 'code':803 'common':694 'compon':463,692,725,743 'concern':142 'condit':19,72,675 'connect':101 'const':171,176,219,248,256,314,318,322,358,367,381,385,390,402,431,436,442,467,472,482,503,507,517,548,557,589,593 'constructor':157 'content':183,213 'content-typ':182 'contract':794 'control':315,558 'controller.abort':340,579 'controller.signal':327,564 'controllerref':504 'controllerref.current':511,513 'controllerref.current.signal':530 'core':135 'correct':85 'curr':597 'curr.filter':598 'custom':632 'data':74,119,124,240,323,329,700,763 'data-fetch':762 'debounc':418,437,454,543,653,718 'debouncedqueri':549,555,562,580 'dedic':626 'dedupedfetch':473 'dedupl':457,659,729 'delay':373,404,434,447,452 'delet':608,614 'deleteitem':590 'design':130 'developer.mozilla.org':813 'developer.mozilla.org/en-us/docs/web/api/abortcontroller':812 'difficult':59 'direct':690 'driven':657 'duplic':75,128,459,721,792 'duplicate-saf':791 'e.g':210,425 'empti':207 'endpoint':114 'err':265,331,380,401,534,568,610 'err.message':269,337,540 'err.name':333,383,536,570 'err.status':388,394 'error':25,148,156,354,629,633,638,667,680 'etc':108 'exampl':493,494,541,581,751 'excess':422 'experi':80,89 'exponenti':345,737 'export':152,170,471 'expos':797 'extend':155 'fail':203,574,615 'failur':350,650 'fals':247,274 'fetch':179,286,764 'fetchwithbackoff':368,412,520 'final':270,485 'fix':122 'flag':282 'flicker':125 'flight':301 'fn':370,378,413,475,484 'focus':83 'framework':760 'fresh':239 'frontend':2,12,31,45,51,102,132,802 'frontend-api-integration-pattern':1 'getus':259,325 'global':682 'guard':705 'handl':20,65,117,206,636 'handlepredict':508 'header':181 'i2r7wq':430 'id':150,242,311,356,429,465,501,546,587,592,601,606 'idempot':781,787 'ident':660 'in-flight':299 'includ':17 'incorrect':64 'inflight':468 'inflight.delete':486 'inflight.get':480 'inflight.has':477 'inflight.set':488 'input':427,510,528,656,715 'input-driven':655 'insid':691 'integr':4,11,33,44,112 'isabort':382,392 'ishttperror':386,393 'isretry':391,399 'issu':52 'item':595,599 'item.id':600 'javascript':754 'js':149,241,310,355,428,464,500,545,586 'json.parse':225 'json.stringify':526 'k1m7r2':151 'key':474,478,481,487,489,788,800 'l9x2pw':312 'layer':134,139,627 'lead':69 'leak':306,672 'let':189,245 'librari':765 'limit':749 'load':249,275,319,338,637 'loading/error':683 'logic':145,288,623 'make':92 'manag':30,233 'mani':711 'map':470 'math.random':406 'memori':305,671 'messag':158,163,201 'method':523,607 'ml':496 'ml/ai':113 'ms':360,366 'multipl':686,724 'mutat':782 'n5q2pt':502 'nativ':106 'network':290,353 'new':198,316,361,469,514,559 'nextdelay':403,410,416 'non':285,780 'non-fetch':284 'non-idempot':779 'normal':26,147,628 'null':161,191,218,227,255,506 'number':389 'optimist':583 'option':175,186 'outdat':707 'overload':732 'overview':35 'overwrit':238 'pattern':5,9,34,42,136,664,755 'payload':160,169,190,193,200,205 'pitfal':695 'pleas':575,616 'poor':78 'post':524 'practic':620 'predict':497 'prefer':292 'prevent':234,458 'previous':594,612 'privileg':798 'problem':696,709,720,730,739 'product':7,40 'production-readi':6,39 'promis':362,483,490,492 'prone':676 'provid':38,786 'proxi':804 'q2k9hz':588 'queri':551,769 'r':363,365 'race':18,71,230,674 'race-condition-pron':673 'race-saf':229 'react':104,105,768 'react.dev':815 'readi':8,41 'relay':772 'repeat':693 'request':21,76,129,202,291,295,302,456,645,661,669,687,722,728,806 'res':177 'res.json':195 'res.ok':188 'res.status':204,215 'res.text':222 'resili':86 'resourc':811 'respons':208,236,708 'result':257,263,518,532 'retri':23,343,347,371,397,414,647,665,734,778 'return':217,223,276,335,339,376,411,448,453,479,491,538,556,578 'safe':209,231,793 'scalabl':131 'search':426,544,573 'searchapi':561 'sensit':805 'separ':140 'server':731 'setdata':262,328 'setdebounc':438,445 'seterror':254,268,336,539,572,613 'setitem':596,611 'setload':252,273 'setoutput':531 'setresult':566 'settimeout':364,444 'show':698 'signal':326,529,563 'silent':681 'similar':774 'skill':37,82,100 'skill-frontend-api-integration-patterns' 'sleep':359,409 'solut':701,716,726,735,745 'source-sickn33' 'stale':73,123,235,308,699 'state':29,232,641,677,684,740 'status':159,167 'strategi':24 'success':640 'super':162 'swallow':679 'swr':770 'text':220,224,226,527 'this.name':164 'this.payload':168 'this.status':166 'throw':197,400 'tool':775 'topic-agent-skills' 'topic-agentic-skills' 'topic-ai-agent-skills' 'topic-ai-agents' 'topic-ai-coding' 'topic-ai-workflows' 'topic-antigravity' 'topic-antigravity-skills' 'topic-claude-code' 'topic-claude-code-skills' 'topic-codex-cli' 'topic-codex-skills' 'transient':349,649 'tri':192,251,321,375,516,576,602,617 'true':253,278 'type':184 'typeof':387 'ui':28,121,126,584,697 'unless':783 'unmount':744 'updat':309,585,678,741 'url':174,180 'use':98,279,630,642,652,702,717,727,736,746,752,767 'usedebounc':432,550 'useeffect':244,313,441,553 'user':79,88 'useref':505 'userid':341 'usest':439 'valu':433,440,446,451 'vanilla':753 'vue':107 'w4z8yn':547 'work':95 'x8v4km':466 'y7p4ha':243","prices":[{"id":"e78d1eed-76c0-44cf-8766-0a33ce0d8082","listingId":"9d13e5c8-bdf0-4e58-874b-99720e591a47","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"sickn33","category":"antigravity-awesome-skills","install_from":"skills.sh"},"createdAt":"2026-04-24T00:50:59.497Z"}],"sources":[{"listingId":"9d13e5c8-bdf0-4e58-874b-99720e591a47","source":"github","sourceId":"sickn33/antigravity-awesome-skills/frontend-api-integration-patterns","sourceUrl":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/frontend-api-integration-patterns","isPrimary":false,"firstSeenAt":"2026-04-24T00:50:59.497Z","lastSeenAt":"2026-05-18T18:51:05.300Z"}],"details":{"listingId":"9d13e5c8-bdf0-4e58-874b-99720e591a47","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"sickn33","slug":"frontend-api-integration-patterns","github":{"repo":"sickn33/antigravity-awesome-skills","stars":37911,"topics":["agent-skills","agentic-skills","ai-agent-skills","ai-agents","ai-coding","ai-workflows","antigravity","antigravity-skills","claude-code","claude-code-skills","codex-cli","codex-skills","cursor","cursor-skills","developer-tools","gemini-cli","gemini-skills","kiro","mcp","skill-library"],"license":"mit","html_url":"https://github.com/sickn33/antigravity-awesome-skills","pushed_at":"2026-05-18T08:24:49Z","description":"Installable GitHub library of 1,400+ agentic skills for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and more. Includes installer CLI, bundles, workflows, and official/community skill collections.","skill_md_sha":"2e3c1cf7ade58e0c0ea6ac27e5413649571dc69c","skill_md_path":"skills/frontend-api-integration-patterns/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/sickn33/antigravity-awesome-skills/tree/main/skills/frontend-api-integration-patterns"},"layout":"multi","source":"github","category":"antigravity-awesome-skills","frontmatter":{"name":"frontend-api-integration-patterns","description":"Production-ready patterns for integrating frontend applications with backend APIs, including race condition handling, request cancellation, retry strategies, error normalization, and UI state management."},"skills_sh_url":"https://skills.sh/sickn33/antigravity-awesome-skills/frontend-api-integration-patterns"},"updatedAt":"2026-05-18T18:51:05.300Z"}}