{"record":{"id":"a064ffac113743b3","repo":"sickn33/agentic-awesome-skills","slug":"already-liked","errorCode":null,"errorMessage":"Already liked","messagePattern":"Already liked","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"skills/firebase/SKILL.md","lineNumber":471,"sourceCode":"\n  await batch.commit();\n  return postRef.id;\n}\n\n// Transaction - read and write atomically\nasync function likePost(postId, userId) {\n  return runTransaction(db, async (transaction) => {\n    const postRef = doc(db, 'posts', postId);\n    const likeRef = doc(db, 'posts', postId, 'likes', userId);\n\n    const postSnap = await transaction.get(postRef);\n    if (!postSnap.exists()) {\n      throw new Error('Post not found');\n    }\n\n    const likeSnap = await transaction.get(likeRef);\n    if (likeSnap.exists()) {\n      throw new Error('Already liked');\n    }\n\n    // Increment like count and add like document\n    transaction.update(postRef, {\n      likeCount: increment(1)\n    });\n\n    transaction.set(likeRef, {\n      userId,\n      createdAt: serverTimestamp()\n    });\n\n    return postSnap.data().likeCount + 1;\n  });\n}\n\n### Social Login (Google, GitHub, etc.)\n","sourceCodeStart":453,"sourceCodeEnd":489,"githubUrl":"https://github.com/sickn33/agentic-awesome-skills/blob/58d857988fcfac6986206bca2b2fe223aa437e4b/skills/firebase/SKILL.md#L453-L489","documentation":"This is example code in the Firebase skill: a Firestore transaction that 'likes' a post reads the like document and throws new Error('Already liked') if it exists, aborting the transaction. The throw acts as a concurrency-safe duplicate check — the transaction rejects rather than double-incrementing likeCount.","triggerScenarios":"Calling the like() transaction twice for the same user and post: on the second run likeSnap.exists() is true and it throws. Double-taps firing two concurrent calls — one commits, the other re-runs and sees the like.","commonSituations":"Double-click/double-tap before the UI disables the button; client retry after a network hiccup where the first transaction actually committed; the same handler wired from multiple components.","solutions":["Treat 'Already liked' as an idempotency signal: catch it and set the UI to the liked state instead of showing an error","Disable the like button while the transaction is in flight","Branch the catch: e.message === 'Already liked' separately from 'Post not found'","Optionally pre-check the like document's existence, but keep the transaction check as the race-safe source of truth"],"exampleFix":"// before\nawait like(postId, userId);\n\n// after\ntry {\n  await like(postId, userId);\n  setLiked(true);\n} catch (e) {\n  if (e.message === 'Already liked') setLiked(true);\n  else throw e;\n}\n// plus: <button disabled={liked || pending} onClick={like}>Like</button>","handlingStrategy":"try-catch","validationCode":"const likeSnap = await getDoc(doc(db, 'posts', postId, 'likes', userId));\nif (likeSnap.exists()) { setLiked(true); return; }\nawait like(postId, userId);","typeGuard":null,"tryCatchPattern":"try { await like(postId, userId); }\ncatch (e) {\n  if (e.message === 'Already liked') { setLiked(true); return; }\n  if (e.message === 'Post not found') { showError('Post removed'); return; }\n  throw e;\n}","preventionTips":["Disable the like button while the transaction is pending","Treat duplicate-like as success, not failure","Derive initial liked state from the like document","Debounce rapid taps on engagement buttons"],"tags":["firebase","firestore","transaction","idempotency"],"backgroundTag":"duplicate-operation","analyzedSha":"58d857988fcfac6986206bca2b2fe223aa437e4b","analyzedAt":"2026-08-26T11:55:59.350Z","schemaVersion":2},"datasetVersion":"2026-08-26T14:46:13.012Z"}