Skip to content

fix(weather-alerts): resolve iOS screen freeze on navigation#2499

Merged
mikib0 merged 6 commits into
developmentfrom
fix/weather-alerts-screen-freeze
May 26, 2026
Merged

fix(weather-alerts): resolve iOS screen freeze on navigation#2499
mikib0 merged 6 commits into
developmentfrom
fix/weather-alerts-screen-freeze

Conversation

@mikib0
Copy link
Copy Markdown
Collaborator

@mikib0 mikib0 commented May 26, 2026

Summary

Fixes #2498 — screen freeze when tapping Weather Alerts on iOS.

  • Root cause (hook): activeLocationAtom derives its value via .find(), emitting a new object reference on every evaluation. Passing the whole activeLocation object as a useEffect dependency caused useWeatherAlerts to re-fetch on every render, triggering a cascade that could freeze the UI.
  • Fix (hook): Extract primitive locationId and locationName from the atom value and use those as stable effect dependencies instead of the object reference.
  • Additional fixes:
    • Removed a dead Alert component from WeatherAlertsTile that was rendering new object/array literals (materialIcon, buttons) on every render — NativeWindUI's internal effects looped on these unstable props.
    • Changed weather-alerts screen animation from slide_from_bottom (modal idiom) to default to match its card presentation.
    • Added stale-fetch cancellation (cancelled flag) and Sentry instrumentation to useWeatherAlerts.

Test plan

  • Tap Weather Alerts from the dashboard — screen navigates without freezing
  • Content appears correctly below the header
  • Alert count updates when location changes
  • No "Maximum update depth exceeded" errors in the console

Summary by CodeRabbit

  • Bug Fixes

    • Improved weather alerts screen navigation animation.
    • Adjusted layout spacing on weather alerts display.
    • Simplified weather alerts tile interface by removing unnecessary fallback UI.
  • Improvements

    • Enhanced error tracking and monitoring for weather alerts functionality.

Review Change Stack

Three issues caused the screen to freeze on iOS when tapping Weather Alerts:

1. `presentation: 'card'` + `animation: 'slide_from_bottom'` mismatch in
   _layout.tsx. Every other slide-from-bottom screen uses `presentation: 'modal'`.
   The conflicting config caused the native iOS navigation controller to set up
   a left-edge pan gesture (card) while the screen arrived from the bottom,
   leaving the screen unresponsive. Fixed by aligning to `presentation: 'modal'`.

2. `contentInsetAdjustmentBehavior="automatic"` on the ScrollView with
   `headerShown: false` caused iOS to compute incorrect content insets (no
   native navigation bar exists to measure). Changed to `"never"`.

3. `useWeatherAlerts` depended on the whole `activeLocation` object, which is
   recreated on every Jotai atom evaluation via `.find()`. Extracted primitive
   `locationId`/`locationName` as dependencies to prevent spurious re-fetches.
   Added a `cancelled` cleanup flag to discard stale async results on unmount.
   Added Sentry breadcrumb and error capture per instrumentation convention.

Closes #2498
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 26, 2026

Walkthrough

This PR fixes a screen freeze when navigating to Weather Alerts by simplifying the WeatherAlertsTile component to remove problematic UI fallback rendering, adding async cleanup in useWeatherAlert to prevent post-unmount state updates, adjusting the screen's animation behavior and layout spacing, and integrating Sentry instrumentation for error monitoring.

Changes

Weather Alerts Navigation and Stability

Layer / File(s) Summary
Navigation animation and screen spacing
apps/expo/app/(app)/_layout.tsx, apps/expo/app/(app)/weather-alerts.tsx
Animation option for weather-alerts stack changed from slide_from_bottom to default; ScrollView top margin (mt-20) removed to adjust layout beneath the header.
WeatherAlertsTile component cleanup
apps/expo/features/weather/components/WeatherAlertsTile.tsx
Removed the Alert fallback UI and associated ref wiring (AlertMethods, useRef). Component now renders only the ListItem tile with icon, label, and loading-aware count text.
useWeatherAlert hook cleanup and monitoring
apps/expo/features/weather/hooks/useWeatherAlert.ts
Sentry breadcrumb logging and exception capture added for alert fetches. Effect dependencies refactored to use primitive values (locationId, locationName) instead of the full activeLocation object. Cancellation flag introduced to prevent state updates after unmount and guard against dangling async operations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PackRat-AI/PackRat#2378: Adds Sentry instrumentation to weather-fetch flows; this PR extends monitoring to the useWeatherAlert hook with similar breadcrumb and error-capture patterns.
  • PackRat-AI/PackRat#2004: Original weather-alerts feature implementation; this PR refines and stabilizes the same components and hooks.

Suggested labels

mobile

Suggested reviewers

  • Isthisanmol
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title clearly and concisely summarizes the main fix: resolving iOS screen freeze on navigation for weather alerts.
Linked Issues check ✅ Passed Changes address all three root causes from #2498: navigation animation mismatch, ScrollView inset miscalculation, and unstable useEffect dependencies.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the iOS freeze issue; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/weather-alerts-screen-freeze

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

mikib0 added 4 commits May 26, 2026 11:31
With presentation:'modal' the dashboard screen stays fully active behind the
modal, so WeatherAlertsTile keeps rendering. The Alert component from
NativeWindUI received new object literals for materialIcon and buttons on every
render and used them inside its own internal effects, which drove the
"Maximum update depth exceeded" loop.

The Alert and its ref were dead code — alertRef.current was never called
anywhere in the component. Removed both entirely.
…kground-screen loop

presentation:'modal' keeps the dashboard screen fully active behind the
overlay, which caused NativeWindUI components (ListItem and others on the
dashboard) to drive a re-render loop via their internal effects on unstable
prop references — surfacing as "Maximum update depth exceeded".

The original screen freeze was caused by the hook's unstable [activeLocation]
object dependency (already fixed in a prior commit), not by the presentation
type. Reverting to presentation:'card' deactivates background-screen rendering
on iOS and eliminates the loop.

Also corrected the animation from 'slide_from_bottom' (modal idiom) to
'default' (standard push-right for card) for consistency.
LargeTitleHeader from NativeWindUI creates a native iOS navigation bar.
Setting the ScrollView to "never" prevented iOS from accounting for that bar,
causing the content to overlap the header. Restored to "automatic" so iOS
adjusts the scroll inset correctly alongside the custom mt-20 offset.
… with dynamic offset

LargeTitleHeader.ios.tsx renders <Stack.Screen options={propsToScreenOptions(...)}/>
on every render. propsToScreenOptions builds a new headerLargeStyle object each
time, and React Navigation's deep-equal misses the new reference, triggering a
navigation-state update which re-renders the screen, which re-renders the header,
which triggers another navigation-state update — an infinite loop that freezes
the app.

Fix: use contentInsetAdjustmentBehavior="never" to break the cycle. Since
LargeTitleHeader occupies no space in the React layout (it configures the native
nav controller), manually offset the ScrollView by safeAreaTop + 44 (compact
nav bar) + 52 (large-title row) using useSafeAreaInsets() so the content clears
the header correctly on all device sizes.
@mikib0 mikib0 changed the base branch from main to development May 26, 2026 11:17
…entBehavior automatic

Remove manual safe-area offset calculation; iOS handles header clearance
correctly via contentInsetAdjustmentBehavior="automatic" on this screen.
Copy link
Copy Markdown
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/expo/features/weather/hooks/useWeatherAlert.ts`:
- Around line 246-248: When locationId becomes falsy or a fetch errors the hook
currently leaves prior alerts visible; update the early-return branch that
checks locationId (and the error branch around lines 275–281) to explicitly
clear the alerts state before returning — e.g., call the hook's alert-state
setter to empty the list (setAlerts([]) or setWeatherAlerts([]) depending on the
existing state variable) and then setLoading(false) and return, and do the same
inside the fetch catch/failure path so stale alerts are never shown for the
wrong location.
- Around line 241-293: Refactor the hook useWeatherAlert to replace manual
useState/useEffect logic with TanStack React Query: create a useQuery keyed by
locationId (only enabled when locationId is truthy) that calls
getWeatherData(locationId), use the query's status/error/data to derive loading
and error, and use the query's select option (or map the data) to call
generateAlerts({ data: data as WeatherApiData, activeLocation: { name:
locationName } }) so setAlerts is no longer needed; ensure Sentry
breadcrumb/exception reporting is invoked inside the queryFn/ onError handlers
and return alerts, isLoading, and error from the hook.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1d9f7185-382e-44db-873e-d9334f09048a

📥 Commits

Reviewing files that changed from the base of the PR and between ae75102 and 7e80eac.

📒 Files selected for processing (4)
  • apps/expo/app/(app)/_layout.tsx
  • apps/expo/app/(app)/weather-alerts.tsx
  • apps/expo/features/weather/components/WeatherAlertsTile.tsx
  • apps/expo/features/weather/hooks/useWeatherAlert.ts

Comment on lines 241 to 293
const [alerts, setAlerts] = useState<WeatherAlert[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
if (!activeLocation?.id) {
if (!locationId) {
setLoading(false);
return;
}

const locationId = activeLocation.id;
let cancelled = false;

async function fetchAlerts() {
setLoading(true);
setError(null);

Sentry.addBreadcrumb({
category: 'weather',
message: 'Fetching weather alerts',
level: 'info',
data: { locationId, locationName },
});

try {
const data = await getWeatherData(locationId);
if (cancelled) return;

const formatted = generateAlerts({
// safe-cast: getWeatherData returns WeatherApiForecastResponse; WeatherApiData is a structural subset used only by this alert generator.
data: data as unknown as WeatherApiData,
activeLocation,
activeLocation: { name: locationName },
});
setAlerts(formatted);
} catch (err) {
if (cancelled) return;
console.error('Weather alerts error:', err);
setError('Failed to fetch weather alerts');
Sentry.captureException(err, {
tags: { feature: 'weatherAlerts', action: 'fetchAlerts' },
extra: { locationId, locationName },
});
} finally {
setLoading(false);
if (!cancelled) setLoading(false);
}
}

fetchAlerts();
}, [activeLocation]);

return () => {
cancelled = true;
};
}, [locationId, locationName]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify this hook is using manual server-state management instead of TanStack Query.
fd -a "useWeatherAlert.ts" | xargs -I{} rg -n "useQuery|`@tanstack/react-query`|useEffect\\(|useState\\(|getWeatherData\\(" "{}"

Repository: PackRat-AI/PackRat

Length of output: 197


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file and print the relevant section with line numbers
FILE="apps/expo/features/weather/hooks/useWeatherAlert.ts"
if [ ! -f "$FILE" ]; then
  echo "Missing: $FILE" >&2
  exit 1
fi

echo "=== Imports (first 120 lines) ==="
sed -n '1,120p' "$FILE" | nl -ba

echo "=== Hook implementation (220-320) ==="
sed -n '220,320p' "$FILE" | nl -ba

echo "=== Search within file for react-query usage ==="
rg -n "`@tanstack/react-query`|useQuery|useMutation|useInfiniteQuery" "$FILE" || true

echo "=== Search within file for manual fetch lifecycle ==="
rg -n "useEffect\\(|useState\\(|getWeatherData\\(|cancelled|setLoading\\(|setError\\(" "$FILE"

Repository: PackRat-AI/PackRat

Length of output: 158


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file and print the relevant section with line numbers
FILE="apps/expo/features/weather/hooks/useWeatherAlert.ts"
if [ ! -f "$FILE" ]; then
  echo "Missing: $FILE" >&2
  exit 1
fi

echo "=== Imports (first 120 lines) ==="
sed -n '1,120p' "$FILE" | nl -ba

echo "=== Hook implementation (220-320) ==="
sed -n '220,320p' "$FILE" | nl -ba

echo "=== Search within file for react-query usage ==="
rg -n "`@tanstack/react-query`|useQuery|useMutation|useInfiniteQuery" "$FILE" || true

echo "=== Search within file for manual fetch lifecycle ==="
rg -n "useEffect\\(|useState\\(|getWeatherData\\(|cancelled|setLoading\\(|setError\\(" "$FILE"

Repository: PackRat-AI/PackRat

Length of output: 158


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/expo/features/weather/hooks/useWeatherAlert.ts"
test -f "$FILE" || { echo "Missing: $FILE" >&2; exit 1; }

echo "=== Imports (first 140 lines) ==="
sed -n '1,140p' "$FILE" | cat -n

echo "=== Hook implementation (200-330) ==="
sed -n '200,330p' "$FILE" | cat -n

echo "=== react-query usage in file ==="
rg -n "`@tanstack/react-query`|useQuery|useMutation|useInfiniteQuery" "$FILE" || true

echo "=== manual lifecycle usage in file ==="
rg -n "useEffect\\(|useState\\(|getWeatherData\\(|cancelled|setLoading\\(|setError\\(" "$FILE"

Repository: PackRat-AI/PackRat

Length of output: 8565


Refactor useWeatherAlerts to use TanStack React Query for server-state lifecycle
In apps/expo/features/weather/hooks/useWeatherAlert.ts (lines 241-293), useWeatherAlerts manually manages server state with useState/useEffect + a cancelled flag while calling getWeatherData. Replace this with useQuery keyed by locationId (enabled only when locationId exists) and derive alerts, loading, and error from the query result (optionally selectgenerateAlerts(...)).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/expo/features/weather/hooks/useWeatherAlert.ts` around lines 241 - 293,
Refactor the hook useWeatherAlert to replace manual useState/useEffect logic
with TanStack React Query: create a useQuery keyed by locationId (only enabled
when locationId is truthy) that calls getWeatherData(locationId), use the
query's status/error/data to derive loading and error, and use the query's
select option (or map the data) to call generateAlerts({ data: data as
WeatherApiData, activeLocation: { name: locationName } }) so setAlerts is no
longer needed; ensure Sentry breadcrumb/exception reporting is invoked inside
the queryFn/ onError handlers and return alerts, isLoading, and error from the
hook.

Comment on lines +246 to 248
if (!locationId) {
setLoading(false);
return;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear stale alerts when location is missing or fetch fails.

The hook can keep showing previous-location alerts when locationId becomes falsy or when a fetch errors.
That exposes incorrect alert data to users.

Suggested fix
   useEffect(() => {
     if (!locationId) {
+      setAlerts([]);
+      setError(null);
       setLoading(false);
       return;
     }
@@
       } catch (err) {
         if (cancelled) return;
         console.error('Weather alerts error:', err);
+        setAlerts([]);
         setError('Failed to fetch weather alerts');
         Sentry.captureException(err, {
           tags: { feature: 'weatherAlerts', action: 'fetchAlerts' },
           extra: { locationId, locationName },
         });

Also applies to: 275-281

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/expo/features/weather/hooks/useWeatherAlert.ts` around lines 246 - 248,
When locationId becomes falsy or a fetch errors the hook currently leaves prior
alerts visible; update the early-return branch that checks locationId (and the
error branch around lines 275–281) to explicitly clear the alerts state before
returning — e.g., call the hook's alert-state setter to empty the list
(setAlerts([]) or setWeatherAlerts([]) depending on the existing state variable)
and then setLoading(false) and return, and do the same inside the fetch
catch/failure path so stale alerts are never shown for the wrong location.

@mikib0 mikib0 merged commit 59cddce into development May 26, 2026
1 check passed
@mikib0 mikib0 deleted the fix/weather-alerts-screen-freeze branch May 26, 2026 11:34
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.

[BUG] Screen Freeze Upon Tapping on Weather Alerts

1 participant