Skip to main content

Swipe History Infinite Loop Fix Documentation

Problem Summary

The swipe history screen was experiencing infinite rendering loops caused by improper React Query and Zustand integration patterns.

Root Causes Identified

1. Multiple React Query Hooks Running Simultaneously

Problem:

// 🚨 BAD: All hooks run on every render regardless of activeFilter
const allSwipesQuery = useSwipeHistory({page: currentPage, ...})
const likedQuery = useLikedMovies(currentPage, 20)
const dislikedQuery = useDislikedMovies(currentPage, 20)
const skippedQuery = useSkippedMovies(currentPage, 20)

const query = getActiveQuery() // Select one but all still execute

Impact:

  • All 4 React Query hooks executed on every render
  • Filter/page changes triggered all hooks to re-run
  • Caused cascading state updates and infinite loops

2. Zustand Selector Object Recreation

Problem:

// 🚨 BAD: Creates new object on every render
export const useSwipeHistoryActions = () =>
useSwipeHistoryUIStore((state) => ({
setFilter: state.setFilter, // New object every time!
setPage: state.setPage,
incrementPage: state.incrementPage,
resetPage: state.resetPage,
}))

Impact:

  • Broke React's memoization (new object reference each render)
  • Triggered "getSnapshot should be cached" warnings
  • Caused infinite re-renders and "Maximum update depth exceeded" errors

Solutions Implemented

1. Conditional React Query Hook Usage

Solution:

// ✅ GOOD: Only ONE hook with conditional parameters
const getFilterParams = () => {
const baseParams = { page: currentPage, limit: 20, sortBy: "timestamp", sortOrder: "desc" }

switch (activeFilter) {
case "liked":
return { ...baseParams, liked: true }
case "disliked":
return { ...baseParams, liked: false }
case "skipped":
return { ...baseParams, skipped: true }
default:
return baseParams
}
}

const query = useSwipeHistory(getFilterParams()) // Only one hook call!

Benefits:

  • Single React Query hook execution per render
  • Conditional parameters instead of multiple hooks
  • Eliminated cascading hook executions

2. Individual Zustand Action Selectors

Solution:

// ✅ GOOD: Individual selectors (no object recreation)
export const useSwipeHistorySetFilter = () => useSwipeHistoryUIStore((state) => state.setFilter)
export const useSwipeHistoryIncrementPage = () => useSwipeHistoryUIStore((state) => state.incrementPage)

Benefits:

  • Stable function references (proper memoization)
  • No object recreation on every render
  • Eliminates "getSnapshot should be cached" warnings

3. Clean Architecture Implementation

Final Structure:

📁 Implementation:
├── swipehistory.tsx # Main screen component
├── useSwipeHistoryScreen.ts # Clean integration hook
├── useSwipeHistoryUIStore.ts # UI state only (filter, page)
└── useSwipeHistory.ts # React Query data fetching

Removed Files:

  • useSwipeHistoryWithStore.ts (problematic integration hook)
  • useSwipeHistoryStore.ts (complex store with mixed concerns)
  • Duplicate/backup files

Key Patterns & Best Practices Applied

Tom's Zustand Best Practices

  1. Separate actions from state - UI store only contains filter/page state
  2. Use granular selectors - Individual selectors instead of object-creating ones
  3. Avoid over-subscription - Specific state slices instead of whole store

React Query Integration

  1. Conditional hook usage - Single hook with dynamic parameters
  2. Server state separation - React Query for data, Zustand for UI state
  3. Proper dependency management - Stable parameters to prevent unnecessary re-fetches

Performance Optimizations

  1. Stable references - Function selectors don't recreate objects
  2. Memoization friendly - All selectors return stable references
  3. Minimal re-renders - Only affected components re-render on state changes

Error Messages Resolved

  • ✅ "Warning: The result of getSnapshot should be cached to avoid an infinite loop"
  • ✅ "Error: Maximum update depth exceeded"
  • ✅ Infinite React Query re-fetching loops

Testing Results

  • ✅ Filter changes work without infinite loops
  • ✅ Pagination works correctly
  • ✅ No console warnings or errors
  • ✅ Smooth user experience maintained

Date: July 14, 2025

Status: ✅ RESOLVED