# Pull-to-refresh gesture works smoothly (if implemented)

- **Pattern:** `ab-001960` (`mobile-ux-patterns.touch-gestures.pull-to-refresh`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001960
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001960)

## Why it matters

When a feed of dynamic data has no `RefreshControl`, users have no way to force fresh content — they back out of the screen and re-enter just to trigger a fetch, or they assume the app is broken. A stuck `refreshing={true}` that never resets is worse: the spinner burns indefinitely, users wait, then force-quit. Both patterns inflate perceived load time and trigger abandonment before the real content loads.

## Severity rationale

Low because the app still functions, but the friction drives repeat navigation and degrades perceived freshness.

## Remediation

Add `RefreshControl` to every `FlatList` or `ScrollView` rendering dynamic data, and reset the `refreshing` flag in a `finally` block so network errors do not strand the spinner:

```tsx
const [refreshing, setRefreshing] = useState(false);
const onRefresh = async () => {
  setRefreshing(true);
  try { await fetchData(); } finally { setRefreshing(false); }
};
<FlatList refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />} />
```

## Detection

- **ID:** `pull-to-refresh`
- **Severity:** `low`
- **What to look for:** Count all scrollable lists that display dynamic data (FlatList, SectionList, ScrollView with fetched content). For each, check whether `RefreshControl` is configured with an `onRefresh` handler and `refreshing` state.
- **Pass criteria:** At least 100% of scrollable lists with dynamic data have `RefreshControl` configured with both `refreshing` state and `onRefresh` handler. Pull-to-refresh provides clear visual feedback (spinner, progress) and completes the refresh action without UI freeze. Report the ratio: "X of Y scrollable lists have RefreshControl."
- **Fail criteria:** Any scrollable list with dynamic data is missing `RefreshControl`, or it exists but has janky animations or the `refreshing` state is never reset (spins indefinitely).
- **Skip (N/A) when:** The app has no scrollable lists with dynamic data — all content is static or the app has no list views.
- **Detail on fail:** `"Pull-to-refresh missing from main feed — users cannot refresh without leaving and returning to the screen."` or `"RefreshControl exists but spins indefinitely after release."`
- **Remediation:** Add RefreshControl to ScrollView or FlatList with clear feedback:

  ```tsx
  const [refreshing, setRefreshing] = useState(false);

  const onRefresh = async () => {
    setRefreshing(true);
    await fetchData();
    setRefreshing(false);
  };

  <FlatList
    data={items}
    refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
    renderItem={({ item }) => <ItemRow item={item} />}
  />
  ```

---

Taxons: user-experience

HTML version: https://auditbuffet.com/patterns/ab-001960
