Skip to content

BlurrableImage state initialization#671

Merged
kentcdodds merged 3 commits intomainfrom
cursor/blurrableimage-state-initialization-46b1
Feb 23, 2026
Merged

BlurrableImage state initialization#671
kentcdodds merged 3 commits intomainfrom
cursor/blurrableimage-state-initialization-46b1

Conversation

@kentcdodds
Copy link
Owner

@kentcdodds kentcdodds commented Feb 23, 2026

Refactor BlurrableImage's visibility state initialization to prevent potential DOM access errors before mount.

The initial state for BlurrableImage's visible state was attempting to access the DOM element via document.getElementById during the initial render, which could lead to errors if the element was not yet mounted. This change moves the DOM lookup to an isomorphic layout effect, ensuring the element is available when its properties are accessed, and correctly synchronizes visibility after image load.

Fixes KCD-NODE-VB


Open in Web Open in Cursor 


Note

Low Risk
Small, localized UI behavior change that only adds defensive checks around client-side DOM access during hydration.

Overview
Prevents BlurrableImage from crashing during hydration by guarding the initial document.getElementById(id) lookup and defaulting to the blurred/hidden state when the element isn’t yet in the DOM.

The initial visibility computation now only checks opacity-0 after confirming the lookup returns an HTMLImageElement, leaving the existing post-mount load/complete effect behavior intact.

Written by Cursor Bugbot for commit bb9bbeb. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • Refactor
    • Improved image component initialization during page hydration by adding a guard for missing or non-image elements. This prevents runtime errors and ensures images retain correct visibility and loaded state across server- and client-rendered scenarios, leading to more reliable image behavior during initial page load.

@cursor
Copy link

cursor bot commented Feb 23, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@coderabbitai
Copy link

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

Adds a hydration- and null-safety guard to the blurrable image component: during client hydration the code now verifies the element is an HTMLImageElement before initializing visible, returns early for non-image elements, and otherwise preserves the class-based (opacity-0) visibility detection and load handling.

Changes

Cohort / File(s) Summary
Blurrable image component
app/components/blurrable-image.tsx
Move instanceof HTMLImageElement check earlier; return early if the ref isn't an image during hydration; keep existing opacity-0-based visibility logic and subsequent load handling; minor control-flow adjustment.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I nibble code where pixels hide,
I check each ref before I glide,
If not an image, I bound away,
If opacity frees, I brighten the day,
A careful hop through hydration’s play ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: refactoring the state initialization logic for BlurrableImage's visibility state during hydration, which is the core focus of this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cursor/blurrableimage-state-initialization-46b1

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/components/blurrable-image.tsx (1)

32-38: Optional: setVisible(true) inside the complete guard is redundant.

Since useIsomorphicLayoutEffect (running as useLayoutEffect) always fires before useEffect on the client, any complete image is already handled before this point — visible is already true. The return on line 37 is still necessary to skip attaching the listener, but the setVisible(true) call on line 36 is a no-op.

♻️ Suggested simplification
 	if (imageEl.complete) {
-		setVisible(true)
 		return
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/blurrable-image.tsx` around lines 32 - 38, The guard in
React.useEffect that checks if jsImgElRef.current.complete includes a redundant
setVisible(true); remove that setVisible(true) call but keep the early return to
avoid attaching the load listener, since useIsomorphicLayoutEffect already sets
visible for complete images; update the block in the blurrable-image component
(the React.useEffect containing jsImgElRef, imageEl.complete, setVisible)
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/components/blurrable-image.tsx`:
- Around line 32-38: The guard in React.useEffect that checks if
jsImgElRef.current.complete includes a redundant setVisible(true); remove that
setVisible(true) call but keep the early return to avoid attaching the load
listener, since useIsomorphicLayoutEffect already sets visible for complete
images; update the block in the blurrable-image component (the React.useEffect
containing jsImgElRef, imageEl.complete, setVisible) accordingly.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2cb5747 and 1647722.

📒 Files selected for processing (1)
  • app/components/blurrable-image.tsx

@cursor cursor bot force-pushed the cursor/blurrableimage-state-initialization-46b1 branch from 711c554 to 4181841 Compare February 23, 2026 21:24
cursoragent and others added 3 commits February 23, 2026 23:34
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
Co-authored-by: Kent C. Dodds <me+github@kentcdodds.com>
@cursor cursor bot force-pushed the cursor/blurrableimage-state-initialization-46b1 branch from 4181841 to bb9bbeb Compare February 23, 2026 23:34
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
app/components/blurrable-image.tsx (1)

18-29: Guard is correct; useLayoutEffect would eliminate the residual paint flash for cached images

The instanceof HTMLImageElement guard at line 21 correctly handles both null (element not yet in DOM) and accidental non-img matches in a single check, and the simplified return !el.classList.contains('opacity-0') at line 29 is now provably safe. Behavior is preserved for all three paths:

Scenario getElementById result visible init Corrected by
Traditional SSR hydration HTMLImageElement opacity-based — (correct upfront)
Streaming SSR / Suspense hydration null false useEffect
CSR (SPA navigation) null false useEffect

One minor residual concern: for the bottom two rows, the correction happens inside useEffect, which fires after paint. A cached/complete image will therefore flash the blurred state for one frame before setVisible(true) takes effect. A drop-in useIsomorphicLayoutEffect (switching between useLayoutEffect on the client and useEffect on the server) at lines 33–51 would eliminate that flash without affecting SSR safety, but it's a minor UX edge case.

✨ Optional: isomorphic layout effect to avoid the post-paint flash

Add a small hook (or import it from a shared util if you already have one):

const useIsomorphicLayoutEffect = isServer ? React.useEffect : React.useLayoutEffect

Then swap the existing effect:

-	React.useEffect(() => {
+	useIsomorphicLayoutEffect(() => {
 		if (!jsImgElRef.current) return
 		if (jsImgElRef.current.complete) {
 			setVisible(true)
 			return
 		}

This keeps SSR safe (layout effects are a no-op on the server) while ensuring visibility is corrected synchronously before the browser paints on the client.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/blurrable-image.tsx` around lines 18 - 29, The current
client-side effect runs after paint causing a one-frame blurred flash for cached
images; replace the existing useEffect that checks document.getElementById(id) /
instanceof HTMLImageElement and uses el.classList.contains('opacity-0') to
setVisible(...) with an isomorphic layout effect: add a small hook (e.g. const
useIsomorphicLayoutEffect = typeof window === 'undefined' ? React.useEffect :
React.useLayoutEffect) and call useIsomorphicLayoutEffect in place of useEffect
so the DOM check (getElementById, HTMLImageElement guard, classList.contains)
and setVisible run synchronously on the client while remaining SSR-safe.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/components/blurrable-image.tsx`:
- Around line 18-29: The current client-side effect runs after paint causing a
one-frame blurred flash for cached images; replace the existing useEffect that
checks document.getElementById(id) / instanceof HTMLImageElement and uses
el.classList.contains('opacity-0') to setVisible(...) with an isomorphic layout
effect: add a small hook (e.g. const useIsomorphicLayoutEffect = typeof window
=== 'undefined' ? React.useEffect : React.useLayoutEffect) and call
useIsomorphicLayoutEffect in place of useEffect so the DOM check
(getElementById, HTMLImageElement guard, classList.contains) and setVisible run
synchronously on the client while remaining SSR-safe.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4181841 and bb9bbeb.

📒 Files selected for processing (1)
  • app/components/blurrable-image.tsx

@kentcdodds kentcdodds merged commit 1688e56 into main Feb 23, 2026
8 checks passed
@kentcdodds kentcdodds deleted the cursor/blurrableimage-state-initialization-46b1 branch February 23, 2026 23:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants