RORK LABJP
NATIVE — Rork Max reaches AR and LiDAR scanning, Metal-backed 3D, Dynamic Island, Siri Intents, HealthKit, NFC, App Clips, and on-device Core MLIOS27 — iOS 27 Developer Beta 4 extends Siri AI to iPhone 15 Pro and 15 Pro Max plus the 16 and 17 lines, with noticeably faster responses than the first betaDESIGN — Apple's own design kits for iOS, iPadOS, and macOS 27 are now available for Figma and Sketch, ready for when you lay out UI for the new OSCANARY — Android 17, codenamed Cinnamon Bun, retires Developer Previews in favor of continuously updated Canary builds, which changes when you schedule testingSPARK — Gemini Spark in Android 17 drives apps directly to automate multi-step errands like booking a ride or placing an orderSCALE — Rork raised $2.8M from a16z and now draws roughly 743,000 visits a monthNATIVE — Rork Max reaches AR and LiDAR scanning, Metal-backed 3D, Dynamic Island, Siri Intents, HealthKit, NFC, App Clips, and on-device Core MLIOS27 — iOS 27 Developer Beta 4 extends Siri AI to iPhone 15 Pro and 15 Pro Max plus the 16 and 17 lines, with noticeably faster responses than the first betaDESIGN — Apple's own design kits for iOS, iPadOS, and macOS 27 are now available for Figma and Sketch, ready for when you lay out UI for the new OSCANARY — Android 17, codenamed Cinnamon Bun, retires Developer Previews in favor of continuously updated Canary builds, which changes when you schedule testingSPARK — Gemini Spark in Android 17 drives apps directly to automate multi-step errands like booking a ride or placing an orderSCALE — Rork raised $2.8M from a16z and now draws roughly 743,000 visits a month
Articles/App Dev
App Dev/2026-06-21Advanced

Your List Jumps Back to the Top — Restoring Scroll Position Across Back Navigation and Process Death

How I rebuilt scroll restoration for a wallpaper grid by splitting it into two unrelated problems — back navigation and process death — covering getItemLayout, save timing, and killing the restore flicker.

Rork526FlatList9scroll positionstate restorationexpo11

Premium Article

You're swiping through a wall of wallpapers, you tap one to see it full size, and you press back. The list has snapped to the top, and your thumb goes hunting for the row you were just on. Running a few wallpaper apps as an indie developer, the usage data slowly makes it clear how much that small friction quietly costs you.

At first I thought the whole thing was one problem: "remember the scroll position on the way out." But once I started implementing it, I found the position gets lost in two situations with completely different causes. One is losing it while moving between screens. The other is losing it after the OS kills the app in the background and the user reopens it. Treat them as one problem and you only ever half-fix either.

Separate the two failures before writing any code

Draw the line first. If you start bolting on persistence without this split, you end up writing the offset to disk far too often and making scrolling janky.

SituationWhat is lostFix required
Push to detail, then back (within the stack)Nothing, in principle. Lost only on tab switches or conditional unmountsStop unmounting the list screen
Close and reopen the app (process alive)Nothing — in-memory state survivesNo work needed
OS kills it in the background → relaunchAll in-memory state is gonePersist the offset and restore it

So persistence is genuinely needed only for the third row. The first is a navigation-structure problem and is solvable with no storage at all. Confuse the two and you drift toward writing the position to disk on every frame.

Jumping to the top on "back" usually means the screen was destroyed

In a native-stack navigator, pushing the detail screen on top leaves the list screen mounted behind it. The FlatList's internal state is intact, so going back shows the same position — that is the default behavior.

When it still snaps to the top, the list screen is being unmounted somewhere. The two usual culprits:

The first is implementing a tab or segment switch with conditional rendering like condition ? <List/> : <Other/>. Every switch rebuilds <List/> from scratch and the scroll position resets to zero.

The second is swapping the whole list out while fetching with if (loading) return <Spinner/>. Each refetch destroys the list and re-renders it at the top.

The fix is simple: don't destroy the list.

// ❌ List is rebuilt on every switch
function Screen({ tab }: { tab: 'all' | 'favorites' }) {
  return tab === 'all' ? <WallpaperList /> : <FavoriteList />;
}
 
// ✅ Keep both mounted, toggle only visibility
function Screen({ tab }: { tab: 'all' | 'favorites' }) {
  return (
    <>
      <View style={{ flex: 1, display: tab === 'all' ? 'flex' : 'none' }}>
        <WallpaperList />
      </View>
      <View style={{ flex: 1, display: tab === 'favorites' ? 'flex' : 'none' }}>
        <FavoriteList />
      </View>
    </>
  );
}

Hiding with display: 'none' keeps the list mounted and preserves its scroll position. For two or three tabs this plain approach is perfectly serviceable. Do the same with the loading spinner: overlay it or render it as a list header instead of replacing the whole list, and the position holds.

That removes most of the back-navigation loss. What's left is the process-death case.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Why a lost position on back-navigation and a lost position after process death are different bugs needing different fixes
Restoring before the first paint with getItemLayout so there is no top-of-list flicker
A low-cost save schedule: sample in onScroll, commit only on the move to background
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

App Dev2026-08-05
A JSON File 19% Smaller That Ships Only 3% Smaller — Measuring Catalog Payloads Against gzip
Five payload formats measured on a 20,000-item wallpaper catalog. Key shortening saves just 3.2% after gzip, while a columnar layout cuts 26.8%. Real numbers for Brotli, paging, and JSON.parse time.
App Dev2026-07-16
Placing Native Ads in a Masonry Wallpaper Grid: Designing the Lifetime of an Ad Cell
One native ad in a masonry gallery pushed memory from 180 MB to 420 MB over twenty minutes of scrolling. Here is why cell recycling and ad object lifetime never line up, the pool-based implementation that fixed it, and how I picked the insertion interval from measured numbers.
App Dev2026-07-14
Long-Press Context Menus for a Gallery Item in a Rork Expo App
Long-pressing a wallpaper card does nothing, yet iOS users expect a preview and a menu. From why Pressable alone falls short, to a native context menu with zeego, resolving the scroll-vs-long-press conflict, wiring up save and share, and a custom overlay fallback for Android — all with working code.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →