fix(duplicates): hoist memo hooks above early returns
Rules-of-hooks violation: useRef and three useCallbacks sat after the isLoading/isError/empty early-return block, so first render (loading) called N hooks and the post-data render called N+4, crashing the view. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -180,6 +180,26 @@ export function DuplicatesView() {
|
|||||||
return () => window.removeEventListener('keydown', onKeyDown)
|
return () => window.removeEventListener('keydown', onKeyDown)
|
||||||
}, [allMemberIds, activePhotoId, columns, selectPhoto, virtualizer])
|
}, [allMemberIds, activePhotoId, columns, selectPhoto, virtualizer])
|
||||||
|
|
||||||
|
// Stable handlers — passed through memoised section + thumbnail so
|
||||||
|
// React.memo is actually effective. The `discardMutation.mutate` and
|
||||||
|
// store actions have stable identity already; we wrap them once so the
|
||||||
|
// closure identity doesn't change per render. Must sit above the early
|
||||||
|
// returns below — rules of hooks forbid conditional hook calls.
|
||||||
|
const allMemberIdsRef = useRef(allMemberIds)
|
||||||
|
allMemberIdsRef.current = allMemberIds
|
||||||
|
const handleKeepBest = useCallback(
|
||||||
|
(discardIds: string[]) => discardMutation.mutate(discardIds),
|
||||||
|
[discardMutation]
|
||||||
|
)
|
||||||
|
const handlePreviewMember = useCallback(
|
||||||
|
(memberId: string) => openPreview(memberId, allMemberIdsRef.current),
|
||||||
|
[openPreview]
|
||||||
|
)
|
||||||
|
const handleSelectMember = useCallback(
|
||||||
|
(memberId: string) => selectPhoto(memberId),
|
||||||
|
[selectPhoto]
|
||||||
|
)
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full items-center justify-center text-text-muted">
|
<div className="flex h-full items-center justify-center text-text-muted">
|
||||||
@@ -211,25 +231,6 @@ export function DuplicatesView() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stable handlers — passed through memoised section + thumbnail so
|
|
||||||
// React.memo is actually effective. The `discardMutation.mutate` and
|
|
||||||
// store actions have stable identity already; we wrap them once so the
|
|
||||||
// closure identity doesn't change per render.
|
|
||||||
const allMemberIdsRef = useRef(allMemberIds)
|
|
||||||
allMemberIdsRef.current = allMemberIds
|
|
||||||
const handleKeepBest = useCallback(
|
|
||||||
(discardIds: string[]) => discardMutation.mutate(discardIds),
|
|
||||||
[discardMutation]
|
|
||||||
)
|
|
||||||
const handlePreviewMember = useCallback(
|
|
||||||
(memberId: string) => openPreview(memberId, allMemberIdsRef.current),
|
|
||||||
[openPreview]
|
|
||||||
)
|
|
||||||
const handleSelectMember = useCallback(
|
|
||||||
(memberId: string) => selectPhoto(memberId),
|
|
||||||
[selectPhoto]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={scrollRef} className="h-full overflow-auto bg-bg p-4 pb-20">
|
<div ref={scrollRef} className="h-full overflow-auto bg-bg p-4 pb-20">
|
||||||
<div className="mb-4 flex items-center gap-2 text-xs text-text-muted">
|
<div className="mb-4 flex items-center gap-2 text-xs text-text-muted">
|
||||||
|
|||||||
Reference in New Issue
Block a user