Skip to content

Conversation

@mattgperry
Copy link
Collaborator

Fixes #3360

@tsotimus
Copy link

Thanks @mattgperry - this is blocking our nextjs upgrade!

@claude
Copy link

claude bot commented Jan 6, 2026

PR Review: React 19 Ref Cleanup Support

Overview

This PR adds support for React 19's new ref cleanup function pattern while maintaining backward compatibility with React 18. The implementation correctly addresses issue #3360.


✅ Code Quality & Best Practices

Strengths:

  • Clean implementation: The new setRef utility function is well-designed and follows the single responsibility principle
  • Comprehensive documentation: JSDoc comments clearly explain the purpose and behavior
  • Follows repo conventions: Uses interface over type, named exports, and arrow functions as specified in CLAUDE.md
  • Excellent test coverage: 6 comprehensive tests covering mount/unmount, cleanup functions, ref objects, mixed refs, visual elements, and forwardRef patterns

Minor suggestions:

  1. Type safety improvement - The setRef function uses (ref as any).current. Consider using (ref as MutableRefObject<T>).current for better type safety, though the current implementation works correctly.

✅ Functionality & Correctness

Excellent implementation:

  • ✅ Correctly stores cleanup functions returned by ref callbacks
  • ✅ Calls cleanup on unmount instead of ref(null) when cleanup exists
  • ✅ Falls back to React 18 behavior (ref(null)) when no cleanup is returned
  • ✅ Handles RefObject refs correctly (no cleanup expected)
  • ✅ Properly manages cleanup lifecycle with externalRefCleanupRef

Dependency array fix:
The PR correctly adds missing dependencies to the useCallback:

[visualElement, visualState, externalRef]  // ✅ Complete

Previously it was only [visualElement], which could cause stale closure issues.


✅ Performance Considerations

Positive:

  • Uses useRef to store cleanup function (no re-renders)
  • useCallback memoization prevents unnecessary ref changes
  • No performance regressions introduced

Note:
Adding externalRef to the dependency array means the callback will update when externalRef changes. This is correct behavior but could cause more ref updates if parent components don't memoize their ref callbacks. However, this is the right tradeoff for correctness.


✅ Security Concerns

No security issues identified. The code:

  • Safely checks function types before calling
  • Uses useRef appropriately
  • No external input handling or injection risks

✅ Test Coverage

Excellent test suite:

  1. ✅ Mount behavior - verifies ref callback called with element
  2. ✅ React 18 unmount - verifies ref(null) called when no cleanup
  3. ✅ React 19 cleanup - verifies cleanup function called instead of ref(null)
  4. ✅ RefObject support - ensures no errors with object refs
  5. ✅ Mixed ref types - handles transition between callback and object refs
  6. ✅ Visual element integration - ensures cleanup works with motion animations
  7. ✅ ForwardRef compatibility - tests with wrapped components

🤔 Minor Observations

1. Cleanup handling comment clarity:

In the unmount branch, the comment says "call ref with null" but passes instance. Since we're in the else branch where instance is falsy, this works correctly, but could be more explicit for readability.

2. Ref change behavior:

If externalRef changes while mounted, the old cleanup function won't be called until final unmount. However, this matches React 19's behavior, so it's correct.


📋 Recommendations

Required changes: None - the PR is production-ready ✅

Optional improvements:

  1. Type safety: Replace (ref as any).current with (ref as MutableRefObject<T>).current in setRef

  2. Explicit null: In the unmount fallback, consider using null as T instead of instance for clarity

  3. Test description: Make test Fixing types and es references #2 more explicit about "when no cleanup is returned" to clarify it's not just about React version


✅ Final Verdict

LGTM - Approve and merge. This is a well-implemented fix that:

  • ✅ Solves the reported issue ([BUG] React 19 ref cleanup is not respected #3360)
  • ✅ Maintains backward compatibility with React 18
  • ✅ Has excellent test coverage
  • ✅ Follows the repository's coding standards
  • ✅ Introduces no performance or security concerns

The implementation correctly handles the React 19 cleanup pattern while gracefully degrading for React 18. The dependency array fix also resolves a potential stale closure bug.

Bundle size impact: Minimal (estimated < 100 bytes) - well within existing budgets.


Great work @mattgperry! This is a clean, well-tested solution to ensure forward compatibility with React 19. 🎉

@mattgperry mattgperry merged commit 3816d85 into main Jan 6, 2026
4 checks passed
@mattgperry mattgperry deleted the fix/react-19-cleanup branch January 6, 2026 10:42
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.

[BUG] React 19 ref cleanup is not respected

3 participants