diff --git a/client/src/components/Planner/PlaceInspector.test.tsx b/client/src/components/Planner/PlaceInspector.test.tsx index e1577d0da..8790ca3e8 100644 --- a/client/src/components/Planner/PlaceInspector.test.tsx +++ b/client/src/components/Planner/PlaceInspector.test.tsx @@ -1280,4 +1280,18 @@ describe('PlaceInspector', () => { await waitFor(() => expect(onUploadImage).toHaveBeenCalledWith(place.id, file)); }); + it('FE-PLANNER-INSPECTOR-098: deselecting and reselecting a place survives a rerender', async () => { + // The component bails out with `if (!place) return null`. Any hook below that + // line runs only while a place is selected, so clearing the selection changes + // the hook count and React tears the whole tree down. + const { rerender } = render(); + expect(screen.getByText('Eiffel Tower')).toBeTruthy(); + + rerender(); + expect(screen.queryByText('Eiffel Tower')).toBeNull(); + + rerender(); + expect(screen.getByText('Eiffel Tower')).toBeTruthy(); + }); + }); diff --git a/client/src/components/Planner/PlaceInspector.tsx b/client/src/components/Planner/PlaceInspector.tsx index 5c20c14f2..5efe20a39 100644 --- a/client/src/components/Planner/PlaceInspector.tsx +++ b/client/src/components/Planner/PlaceInspector.tsx @@ -231,6 +231,31 @@ export default function PlaceInspector({ }) }, [place, openSavePicker]) + // Sits above the `if (!place)` bail-out below: a hook after an early return is + // only reached while a place is selected, so deselecting one mid-session + // changes the hook count and React tears the tree down. + const placeId = place?.id + const handleFileUpload = useCallback(async (e) => { + const selectedFiles = Array.from((e.target as HTMLInputElement).files || []) + if (!selectedFiles.length || !onFileUpload || !placeId) return + setIsUploading(true) + try { + for (const file of selectedFiles) { + const fd = new FormData() + fd.append('file', file) + fd.append('place_id', String(placeId)) + await onFileUpload(fd) + } + setFilesExpanded(true) + } catch (err: unknown) { + console.error('Upload failed', err) + toast.error(translateApiError(t, err, 'files.uploadError')) + } finally { + setIsUploading(false) + if (fileInputRef.current) fileInputRef.current.value = '' + } + }, [onFileUpload, placeId, toast, t]) + const startNameEdit = () => { if (!onUpdatePlace) return setNameValue(place.name || '') @@ -283,27 +308,6 @@ export default function PlaceInspector({ const placeFiles = (files || []).filter(f => String(f.place_id) === String(place.id) || (f.linked_place_ids || []).includes(place.id)) - const handleFileUpload = useCallback(async (e) => { - const selectedFiles = Array.from((e.target as HTMLInputElement).files || []) - if (!selectedFiles.length || !onFileUpload) return - setIsUploading(true) - try { - for (const file of selectedFiles) { - const fd = new FormData() - fd.append('file', file) - fd.append('place_id', String(place.id)) - await onFileUpload(fd) - } - setFilesExpanded(true) - } catch (err: unknown) { - console.error('Upload failed', err) - toast.error(translateApiError(t, err, 'files.uploadError')) - } finally { - setIsUploading(false) - if (fileInputRef.current) fileInputRef.current.value = '' - } - }, [onFileUpload, place.id, toast, t]) - return (