Build1 distinct publisher3 min readUpdated
A helper returned a new object on every render. Framer Motion compares variants by reference, and a once-only viewport trigger never fires again, so elements stayed stuck in their hidden state.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
Reference equality is the whole mechanism. The helper `getVariants` was called inline in the component body and returned a brand-new object on every render, even when the values inside were identical [6], and Framer Motion tracks the `variants` prop by reference rather than by deep equality [7]. Every re-render therefore handed the library what looked like a completely new set of animation variants.
That alone would produce a flicker. What makes it permanent is `viewport={{ once: true }}`. The transition to `visible` has to be triggered by the intersection observer callback, and the author's reading is that when a re-render swaps the variants object mid-transition or right as the observer fires, the animation state and the variants object fall out of sync: the component holds a `visible` state pointing at variants that no longer match what is being interpolated, and because the trigger only fires once there is no second chance to self-correct [8]. A one-shot trigger converts a race into a resting state.
Now the arithmetic the write-up leaves on the table. The anti-pattern was found in six files [10], and it announced itself in three components [3][4][5]. That leaves three call sites that carried the identical defect and were never observed failing [17]. Half the exposure was silent, which is the normal ratio for a race: it surfaces where render churn and scroll position happen to collide, not where the code is worst.
The reason the shared helper stayed invisible for two rounds is also worth naming. Before the fix, the breakage was reproducible but not consistently [12], and each local fix worked in isolation [15]. Against an intermittent race, rewriting one card's transition changes render timing, and a change in timing is indistinguishable from a repair if your test is "load the page and scroll." The first two fixes were not wrong so much as unfalsifiable.
The repair has no cleverness in it: variant objects hoisted to module-level constants, selected with a ternary instead of a function call, so the object reference stays stable across renders [9]. The author also searched for every call site of `getVariants` and its sibling `getStaggerVariants` before touching anything [11], and reports that afterwards every scroll-triggered reveal resolves correctly at every entry point he could find [13].
The transferable rule is narrower than "avoid inline objects in render." It is that a prop compared by identity, combined with a trigger that is configured to fire exactly once, has no error-recovery path at all. Wherever those two properties meet, a transient desync stops being a glitch and becomes the final rendered output until the document reloads. In this codebase the pair was introduced by the reduced-motion fallback [14], which is to say by the most conscientious code in the file.
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
With viewport={{ once: true }}, the author says that if a re-render swaps out the variants object mid-transition or right as the intersection observer fires, the animation state and the variants object fall out of sync; the component holds a visible state pointing at a variants object that no longer matches what is being interpolated, and because once:true means the trigger fires a single time there is no second chance to self-correct.
After the fix, the author reports every scroll-triggered reveal in the app resolves correctly, every time, across every entry point he could find.
Entiscore is an agent that audits a website's digital entity, checking schema markup, identity consistency, authority signals, and technical accessibility before returning a scored report.
The developer added scroll-triggered entrance animations built with Framer Motion's whileInView, a blur-to-focus reveal for cards, and staggered timing for lists.
The first failure: the Hero title on the landing page got stuck mid-animation, permanently blurred and permanently offset, frozen in its hidden state; scrolling did nothing and only a hard refresh cleared it.
A few days later a different section broke the same way: four feature cards on the homepage were stuck in a half-rendered blur that never resolved to their final state.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Single self-reported post-mortem with code on both sides
The mechanism is spelled out with before-and-after code, a named helper, a named sibling helper, a file count, and three specific failing surfaces, which is unusually concrete for a bug story. But everything rests on one author's account in one post: there is no minimal reproduction, no Framer Motion or React version, no library issue or maintainer confirmation that variants-by-reference plus once:true produces the described desync, and no test or telemetry behind the fix verification. The causal claim is plausible and internally consistent, not independently corroborated.
No adoption evidence in supplied sources
The cluster contains no release, deployment, benchmark, pricing, licensing, or usage disclosure. The only quantities are internal to one hobby/hackathon codebase — six affected files fixed in one pass — which says nothing about how widely the pattern or the fix is adopted, and no Framer Motion usage or download data is supplied. Estimating adoption would require inventing facts the source does not contain.
Mildly overstated: confident mechanism, self-verified cure
The write-up is scoped and honest about the author's own missteps, and the code-level cause is credible. It nonetheless states the reference-identity-plus-once:true desync as settled fact rather than hypothesis, and declares the bug fixed 'every time, across every entry point' after an intermittent failure — a claim stronger than manual spot-checking can carry. Framing that three bugs were one defect also generalizes from three observed failures to six patched call sites. The gap is small and mostly about certainty of diagnosis and cure, not about invented outcomes.
Disclosed contest and hackathon promotion
The post is explicitly a submission to DEV's Summer Bug Smash: Smash Stories powered by Sentry, and closes by promoting Entiscore as a build for the Kiro powered by AWS hackathon by Código Facilito. Contest formats reward a tidy detective arc — three mysterious failures, one root cause, one clean fix — which pushes toward a decisive narrative over caveats, and the post doubles as visibility for the author's product. The incentives are fully disclosed at both ends of the piece and there is no vendor claim about Framer Motion or Sentry being sold, which keeps this short of a promotional placement.
Moderate-low: one source, verifiable code, unverified mechanism
Confidence is limited by structure more than by content quality. A single publisher and a single author supply every fact; the code-level details are specific and checkable in principle, but the causal mechanism, the intermittency, and the durability of the fix are all reported by the party with a contest incentive. The practical guidance — hoist objects a library diffs by reference out of the render path — is safe regardless of whether the precise Framer Motion internals are described exactly right, which supports the actionable part of the story more than the diagnostic part.
build
Four indexes, none of them covering: the 78-second page and the one index that fixed it1 distinct publisher
build
Force the tool call, then hand Lightsail a long-lived key1 distinct publisher
build
CSA's 2026 threat list is a flat line, so ask which threats a config snapshot can prove1 distinct publisher
build
A GPU SQL Engine Lost to One CPU Thread Because a Dispatcher Constant Was 128x Too Small1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 22, 2026