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 (