Skip to content

Bug Fix: Prevent PersonImages from polluting global image state#713

Open
VasuS609 wants to merge 4 commits intoAOSSIE-Org:mainfrom
VasuS609:fix/person-images-state-pollution
Open

Bug Fix: Prevent PersonImages from polluting global image state#713
VasuS609 wants to merge 4 commits intoAOSSIE-Org:mainfrom
VasuS609:fix/person-images-state-pollution

Conversation

@VasuS609
Copy link
Contributor

@VasuS609 VasuS609 commented Dec 11, 2025

fixe:#706

Problem

PersonImages component was overwriting global Redux image state, causing the Home page gallery to display only the last viewed person's images instead of the full gallery.

Reproduction Steps

  1. Open Home → see full gallery (e.g., 100 photos)
  2. Click a face collection → see person's photos (e.g., 15 photos)
  3. Navigate back to Home → Bug: only 15 photos shown instead of 100

Solution

  • Changed from dispatch(setImages) to local useState(personImages)
  • Person-specific images now isolated to component state
  • Global Redux state remains clean for Home and search pages
  • Added loading spinner and empty state for better UX

Changes

  • src/pages/PersonImages/PersonImages.tsx - Use local state + improved loading/empty states

Testing

Home shows full gallery
Person page shows only their images
Navigating back preserves full gallery
Loading state displays during fetch
Empty state shows when no images found

##fixes:#706

Summary by CodeRabbit

  • Refactor

    • Image state moved from global store to local component state for improved performance and maintainability.
  • Improvements

    • Inline handling of loading, error, and empty states with clearer messaging and spinner.
    • Media viewer and image grid now use the local image set.
    • Simplified image editing/save flow for a more consistent UX.

✏️ Tip: You can customize this high-level summary in your review settings.

- Use local useState instead of Redux dispatch(setImages)
- Add loading spinner and empty state for better UX
- Fixes bug where Home gallery shows only person's images after navigation
- Preserves full gallery state when navigating back to Home
@github-actions
Copy link
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 11, 2025

Warning

Rate limit exceeded

@VasuS609 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 0 minutes and 0 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 2f8a088 and 64805ba.

📒 Files selected for processing (1)
  • frontend/src/pages/PersonImages/PersonImages.tsx (4 hunks)

Walkthrough

PersonImages now stores fetched images in local component state (personImages) instead of using Redux; rendering, loading/error/empty states, editing flow, and MediaView usage were updated to read from that local state.

Changes

Cohort / File(s) Summary
Local state migration & render/update
frontend/src/pages/PersonImages/PersonImages.tsx
Removed Redux image selector/dispatcher (selectImages, setImages); added useState for personImages; populate personImages on fetch success and update clusterName there; render now uses isLoading/error/personImages with spinner and "No images found" messaging; pass personImages to MediaView; removed clusterName updates from edit/save flows and inline loading/error UI handling.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • Verify initialization and TypeScript typing of personImages useState
  • Confirm fetch success/error paths update personImages and clusterName correctly
  • Ensure MediaView accepts the personImages shape and viewer behavior remains intact
  • Check that removing Redux image updates does not break other components expecting global image state

Possibly related PRs

Suggested labels

frontend, UI

Poem

🐰 A little hop from store to state,
Images snug in a local crate,
Spinners twirl while empty notes chime,
Viewer opens with the fetched batch in time,
Hoppity-hop — the UI feels great! 🥕✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately describes the main change: replacing global Redux image state usage with local component state to prevent the PersonImages component from polluting the global state.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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.

@github-actions
Copy link
Contributor

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

Copy link
Contributor

@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.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
frontend/src/pages/PersonImages/PersonImages.tsx (3)

37-38: Add user-facing error message.

The error case only hides the loader but doesn't inform users why images failed to load. This creates a poor user experience when API calls fail.

Consider adding an error state message similar to the empty state:

  } else if (isError) {
    dispatch(hideLoader());

Then in the render section (after line 123):

      ) : isError ? (
        <div className="flex items-center justify-center py-12">
          <p className="text-muted-foreground">
            Failed to load images. Please try again.
          </p>
        </div>

48-50: Remove redundant state update.

Line 49 sets clusterName to its current value, which is a no-op.

Apply this diff:

  const handleEditName = () => {
-    setClusterName(clusterName);
    setIsEditing(true);
  };

53-57: Remove redundant state update.

Line 54 sets clusterName to its current value, which is a no-op.

Apply this diff:

  const handleSaveName = () => {
-    setClusterName(clusterName);
    renameClusterMutate(clusterName);
    setIsEditing(false);
  };
🧹 Nitpick comments (2)
frontend/src/pages/PersonImages/PersonImages.tsx (2)

40-40: Avoid any type for better type safety.

The response should be properly typed instead of using any.

Apply this diff:

-      const res: any = data?.data;
+      const res = data?.data as { images?: Image[]; cluster_name?: string };

111-135: Excellent loading and empty state UX improvements!

The conditional rendering correctly handles loading, empty, and populated states with appropriate user feedback.

Optional: The loading spinner (lines 114) could be extracted to a reusable component if this pattern appears elsewhere in the codebase, but this is a low-priority optimization.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d07d817 and cbeca56.

📒 Files selected for processing (1)
  • frontend/src/pages/PersonImages/PersonImages.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/PersonImages/PersonImages.tsx (3)
frontend/src/types/Media.ts (1)
  • Image (13-22)
frontend/src/components/Media/ImageCard.tsx (1)
  • ImageCard (20-111)
frontend/src/components/Media/MediaView.tsx (1)
  • MediaView (26-200)
🔇 Additional comments (4)
frontend/src/pages/PersonImages/PersonImages.tsx (4)

7-7: LGTM!

Import changes correctly remove Redux image state dependencies while retaining selectIsImageViewOpen for modal visibility.


21-21: LGTM!

Local state declaration properly isolates person-specific images from global Redux state, addressing the core bug.


42-42: LGTM!

Correctly updates local component state instead of dispatching to Redux, which fixes the state pollution bug.


138-138: LGTM!

MediaView correctly receives personImages which matches the indices used by ImageCard components, ensuring proper image navigation in the viewer.

- Remove redundant setClusterName calls
- Add error state UI for failed image loads
- Replace 'any' type with proper interface
Copy link
Contributor

@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.

Actionable comments posted: 0

🧹 Nitpick comments (2)
frontend/src/pages/PersonImages/PersonImages.tsx (2)

34-46: Tighten loader effect to avoid potential stuck loader edge cases

Right now the loader handling is:

  • showLoader when isLoading is true.
  • hideLoader only when isError or when isSuccess && data?.data.

If the query ever reaches isSuccess === true but data?.data is falsy (e.g., API returns an unexpected shape or null), hideLoader will never be dispatched and the global loader could remain visible indefinitely. Also, if the component unmounts while loading, there’s no cleanup to ensure the loader is cleared, depending on how loaderSlice is implemented.

Consider restructuring so that:

  • showLoader is only called while isLoading is true.
  • hideLoader is always called when !isLoading (regardless of success/error/data shape).
  • Any data parsing happens after that.

For example:

 useEffect(() => {
-  if (isLoading) {
-    dispatch(showLoader('Loading images'));
-  } else if (isError) {
-    dispatch(hideLoader());
-  } else if (isSuccess && data?.data) {
-    const res = data.data as { images?: Image[]; cluster_name?: string };
-    const images = res.images || [];
-    setPersonImages(images);
-    setClusterName(res.cluster_name || 'random_name');
-    dispatch(hideLoader());
-  }
-}, [data, isSuccess, isError, isLoading, dispatch]);
+  if (isLoading) {
+    dispatch(showLoader('Loading images'));
+    return;
+  }
+
+  // Not loading anymore: ensure loader is hidden regardless of success/error/data shape
+  dispatch(hideLoader());
+
+  if (isSuccess && data?.data) {
+    const res = data.data as { images?: Image[]; cluster_name?: string };
+    const images = res.images || [];
+    setPersonImages(images);
+    setClusterName(res.cluster_name || 'random_name');
+  }
+}, [data, isSuccess, isLoading, dispatch]);

You can keep isError in the deps if your linter enforces it, but it’s no longer needed in the branching logic.


109-151: Retry via full page reload is heavy; prefer refetching the query

The new loading / error / empty states look good and clearly scoped to this page. One concern: the Retry button currently uses window.location.reload(), which is heavy-handed and loses in-memory UI state.

If usePictoQuery exposes a refetch (or similar) function, consider wiring Retry to that instead:

const { data, isLoading, isSuccess, isError, refetch } = usePictoQuery({ ... });

// ...

<Button
  variant="outline"
  onClick={() => refetch()}
  className="mt-4"
>
  Retry
</Button>

This keeps the retry localized to the data fetch without a full app reload.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cbeca56 and 440a129.

📒 Files selected for processing (1)
  • frontend/src/pages/PersonImages/PersonImages.tsx (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/PersonImages/PersonImages.tsx (3)
frontend/src/types/Media.ts (1)
  • Image (13-22)
frontend/src/components/Media/ImageCard.tsx (1)
  • ImageCard (20-111)
frontend/src/components/Media/MediaView.tsx (1)
  • MediaView (26-200)
🔇 Additional comments (1)
frontend/src/pages/PersonImages/PersonImages.tsx (1)

21-22: Local personImages state + MediaView wiring looks correct

Moving person-specific images into personImages and passing that into <MediaView images={personImages} /> cleanly fixes the global image-state pollution while keeping the viewer behavior consistent with ImageCard and MediaView expectations (index-based navigation over the provided array). No issues here.

Also applies to: 39-43, 154-155

Copy link
Contributor

@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.

Actionable comments posted: 0

🧹 Nitpick comments (2)
frontend/src/pages/PersonImages/PersonImages.tsx (2)

39-45: Ensure global loader is always hidden once the query settles

hideLoader is only called when isError is true or when isSuccess && data?.data is truthy. If the query succeeds but data?.data is unexpectedly falsy, the global loader would remain shown indefinitely even though the inline UI moves on to the empty state.

Consider restructuring the effect so the loader is hidden whenever the query leaves the loading state, independent of the data shape, e.g.:

   useEffect(() => {
-    if (isLoading) {
-      dispatch(showLoader('Loading images'));
-    } else if (isError) {
-      dispatch(hideLoader());
-    } else if (isSuccess && data?.data) {
-      const res = data.data as { images?: Image[]; cluster_name?: string };
-      const images = res.images || [];
-      setPersonImages(images);
-      setClusterName(res.cluster_name || 'random_name');
-      dispatch(hideLoader());
-    }
+    if (isLoading) {
+      dispatch(showLoader('Loading images'));
+      return;
+    }
+
+    // Query has settled (success or error) — always hide the global loader
+    dispatch(hideLoader());
+
+    if (isSuccess && data?.data) {
+      const res = data.data as { images?: Image[]; cluster_name?: string };
+      const images = res.images || [];
+      setPersonImages(images);
+      setClusterName(res.cluster_name || 'random_name');
+    }
   }, [data, isSuccess, isError, isLoading, dispatch]);

This keeps the UX robust even if the backend occasionally returns an unexpected payload.


109-151: Use query refetch instead of full page reload for Retry and review duplicate loaders

The new loading/error/empty/grid flow looks good and reads clearly, but there are two small UX nits:

  • The Retry button currently calls window.location.reload(), which tears down the whole SPA and reloads everything. If usePictoQuery exposes a refetch function, prefer wiring Retry to that instead so only this query re-runs:
-  const { data, isLoading, isSuccess, isError } = usePictoQuery({
+  const { data, isLoading, isSuccess, isError, refetch } = usePictoQuery({
     queryKey: ['person-images', clusterId],
     queryFn: async () => fetchClusterImages({ clusterId: clusterId || '' }),
   });
...
-            <Button
-              variant="outline"
-              onClick={() => window.location.reload()}
-              className="mt-4"
-            >
+            <Button
+              variant="outline"
+              onClick={() => refetch()}
+              className="mt-4"
+            >
               Retry
             </Button>
  • You now have both the global loader (via showLoader/hideLoader) and this inline spinner controlled by isLoading. If the global loader is visible over this page, users may see two loaders at once. It may be worth standardizing on either the global overlay or the inline spinner for this screen, depending on project UX conventions.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 440a129 and 2f8a088.

📒 Files selected for processing (1)
  • frontend/src/pages/PersonImages/PersonImages.tsx (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Backend Tests
  • GitHub Check: Tauri Build Check (windows-latest)
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
🔇 Additional comments (2)
frontend/src/pages/PersonImages/PersonImages.tsx (2)

21-27: Local personImages state cleanly isolates person gallery from global store

Using useState<Image[]>([]) for personImages and driving the grid from this local state (rather than Redux) aligns with the PR goal of preventing global image pollution and keeps the person gallery scoped to this page. No issues spotted with this change.


7-7: MediaView wiring correctly switches to local personImages while reusing global open state

Keeping isImageViewOpen in Redux via selectIsImageViewOpen but passing images={personImages} into <MediaView> ensures the viewer opens using the same locally-fetched set rendered in this grid, without mutating the global images slice. This is a clean way to fix the original gallery pollution bug while preserving existing open/close behavior.

Also applies to: 20-21, 154-154

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.

1 participant

Comments