{
  "id": 11880750,
  "title": "Our re-consent flow is two strings, and the hard part was not writing during render",
  "url": "https://urgent.news/2026/10/04/our-re-consent-flow-is-two-strings-and-the-hard-part-was-not-writing",
  "topic": "tech",
  "section": "Tech",
  "published": "2026-10-04T08:12:48.000Z",
  "source": {
    "name": "Dev.to",
    "slug": "dev-to",
    "url": "https://dev.to/daniel_pertu/our-re-consent-flow-is-two-strings-and-the-hard-part-was-not-writing-during-render-1j04"
  },
  "original_language": "en",
  "account": "The mechanism that determines whether a returning user sees a \"we have updated our policies\" notice is contained in a single file. Each account stores the versions it last accepted. The dashboard layout compares the stored versions to the current policy and privacy versions. If the stored versions differ from the current versions, the layout sets a flag indicating that policy acceptance is needed.\n\nBumping a string and publishing a new version causes accounts with outdated stored versions to see the notice on their next dashboard visit. There is no need for migration, backfill, or per-user flags to reset. The two versions (policy and privacy) move independently, so updating the privacy notice does not prompt users about the terms.\n\nThe initial implementation wrote to the database in a specific order: it noticed the mismatch, issued an UPDATE to record acceptance, inserted an audit row, and then rendered. This ordering caused correctness issues and performance problems, as React may render a component multiple times during re-render. To address these issues, the layout was made read-only. The notice records its own acceptance after rendering using a useEffect hook.\n\nSeveral deliberate decisions were made in the updated code. The hasRecorded ref variable prevents React from invoking the effect twice during development. The endpoint used to record acceptance is idempotent, meaning that duplicate calls do not affect the stored versions but would write an additional audit row for one acceptance. The failure path does nothing intentionally, ensuring that if the write fails, the user will see the notice again on the next load and have the write retried.\n\nThe new account case initially appeared as a bug. New accounts have no application row in the database, so they are considered needing policy acceptance. However, the notice does not record any acceptance for these accounts. The row creation during signup stamps both versions into the transaction, alongside other account information. An UPDATE operation against a non-existent row is silent in Postgres and does not affect the database, which is the desired behavior.\n\nThe updated notice has a dismiss button instead of an \"I agree\" button, as the terms state that continued use of the service after modifications constitutes acceptance. The notice records the version, timestamp, IP address, and user agent in the audit log, providing an exact record of when and how users were shown the updated terms. Users can verify the current policy and privacy versions by visiting specific pages on the website.",
  "summary": "The entire mechanism that decides whether a returning user sees a \"we have updated our policies\" notice is this file: export const CURRENT_TOS_VERSION = ' 1.12.0 ' ; export const CURRENT_PRIVACY_VERSION = ' 1.7.0 ' ; Each account stores the versions it last accepted. The dashboard layout compares: const needsPolicyAcceptance = ! dbUser || dbUser . tos_version !== CURRENT_TOS_VERSION || dbUser .…",
  "key_points": [],
  "editors_take": null,
  "illustration": null,
  "coverage": {
    "outlets": 1,
    "also_reported_by": []
  },
  "ai_generated": true,
  "disclaimer": "Summaries, key points and the editor’s take are written by software from other outlets’ reporting and may contain errors — always check the linked original."
}