RORK LABJP
PLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updatesPLAY — Google Play's target API level 36 requirement took effect yesterday, August 31. From today, new apps and updates must target Android 16VISIBILITY — Apps still on API 35 stay listed but disappear for users on newer Android versions. No error is raised; new installs simply fade, which makes the change easy to missEXTENSION — If you missed the deadline, an extension through November 1, 2026 can be requested in Play Console — best filed alongside a concrete migration planAPPLE — On the Apple side, the event lands September 9 and iOS 27 is reported to ship September 14. Testing generated apps on iOS 27 hardware before release week is time well spentEXPO — Expo released expo-paste-input on August 28, a native module that brings image, GIF, and sticker paste to React Native TextInputEAS — EAS Observe reached general availability on August 20, putting crash and performance monitoring on the same EAS platform as builds and updates
Articles/Dev Tools
Dev Tools/2026-03-22Advanced

Keeping Multi-Screen Apps From Falling Apart in Rork — A Three-Stage Prompting Approach to Screens, State, and Wiring

Once a Rork app grows past four screens, a familiar failure appears: every screen works on its own, but state vanishes the moment you navigate. The cause is prompt structure, not app size. Here is the three-layer separation, typed Context, and staged prompting workflow that fixed it for me.

Rork547Vibe Coding6Architecture22Multi-ScreenState Management7

There is a moment in most Rork projects where things quietly change. Up to three screens, everything just works. Somewhere around screen four, a new kind of bug appears: each screen behaves perfectly on its own, but the moment you navigate between them, state disappears.

I ran into this while rebuilding an internal management tool for the utility apps I operate as an indie developer. Moving from the list view to a detail view and back would silently reset the active filter, every single time.

The cause turned out to have nothing to do with app size. It was the structure of my prompts.

Here is the failure pattern I kept hitting, and the design and prompting workflow that has held up since — along with the folder structure that actually shipped.

Why apps break precisely when they become multi-screen

Rork's vibe coding is remarkably accurate one screen at a time. The breakage always happens between screens.

The reason is concrete: when you ask for a revision, Rork will often regenerate the affected screen wholesale. If your API calls and state logic live inside screen components, every regeneration risks overwriting the connective tissue that other screens depend on.

So the defensive move for multi-screen apps is simple to state: keep logic in places that survive regeneration. I declare three layers up front.

  • Presentation (screens/) — UI only. Rork is free to regenerate anything here.
  • Business logic (context/, hooks/) — state management and data transforms. Explicitly excluded from regeneration.
  • Data (services/) — Firestore / Supabase / Stripe communication. Once stable, hands off.

Declaring this separation at the top of a prompt measurably changes Rork's behavior: it becomes far more likely to preserve context/ and services/ when rebuilding a screen. In my experience, that single opening paragraph visibly reduced rework.

Split prompts into three stages: structure → screens → wiring

The temptation is to describe all screens in one big prompt. Past four screens, I no longer do this. My current workflow has three stages.

Stage 1: lock the structure first.

Build this app with the following structure:
- screens/ contains UI only; do not place API calls here
- All shared state lives in context/ using Context + useReducer
- All external service communication lives in services/
Start by creating four empty screens and the navigation between them.

Stage 2: fill in screens one at a time.

For an e-commerce app, that means the product list (Firestore fetch with category filters, card layout), product detail (description, reviews, stock, an add-to-cart button), the cart (quantity controls and subtotal), and checkout (address form plus Stripe) — each as its own prompt, in that order.

Stage 3: state the wiring explicitly.

The category filter selected on the product list must persist
when the user navigates to a detail screen and back.
Keep this state in ProductContext, not in local component state.

The vanishing filter I mentioned earlier was exactly a missing stage three. If you just say "add a filter," Rork implements it with a local useState — and local state dies when the screen unmounts. Which state must outlive navigation is something the model cannot infer; you have to say it in words.

The generated folder structure reliably converges on something like this:

src/
├── screens/
│   ├── ProductList.tsx
│   ├── ProductDetail.tsx
│   ├── ShoppingCart.tsx
│   └── Checkout.tsx
├── context/
│   ├── CartContext.tsx
│   └── ProductContext.tsx
├── services/
│   ├── firestoreService.ts
│   └── stripeService.ts
└── navigation/
    └── RootNavigator.tsx

Specify shared state down to the types

Cross-screen state — auth, cart, filters — belongs in Context + useReducer. The step worth not skipping: spell out the action types in the prompt itself.

Implement CartContext with these types:

type CartItem = { productId: string; name: string; price: number; quantity: number };
type CartAction =
  | { type: "ADD_ITEM"; item: CartItem }
  | { type: "REMOVE_ITEM"; productId: string }
  | { type: "SET_QUANTITY"; productId: string; quantity: number }
  | { type: "CLEAR" };

The reducer must always return a new array. Never mutate state.items directly.

That last sentence comes from experience. I once chased a bug where quantity changes wouldn't render, and found the generated reducer doing state.items[i].quantity = n — mutating in place, so the reference never changed and React never re-rendered. Writing "never mutate" into the prompt prevents an entire class of hard-to-reproduce bugs before they exist. I've documented the diagnosis path for that symptom separately in fixing state that updates but never re-renders.

Ask for optimistic updates and rollback as one unit

Add-to-cart interactions deserve optimistic updates for perceived speed. But if you tell Rork only "make it optimistic," you may get the success path alone. Request the rollback in the same breath:

Handle "Add to Cart" in this order:
1. Reflect the item immediately in the UI and local CartContext
2. Persist to Firestore asynchronously in parallel
3. On failure, remove the item from CartContext and
   show a toast: "Could not add item"

Step 3 is what prevents the cart and Firestore from drifting apart on a weak connection. As an indie developer you are usually your own first tester — more than once I've caught this exact failure while using my own app on the subway.

The operating rules that mattered more than the architecture

In the end, what kept multi-screen apps stable wasn't the design so much as two working habits.

Scope every revision request. "Fix the detail screen layout. Do not modify context/ or services/." One extra sentence, and the odds of collateral damage to working connections drop noticeably.

Snapshot a known-good state before any wiring change. Use export or version history to secure a point where all screens connect and run, then issue the next big instruction. Not "fix it when it breaks" but "make sure you can go back when it breaks."

If you want to go deeper on the architectural patterns themselves, choosing between MVVM and Clean Architecture in Rork is a natural next step.

For now, try this with the app you're building today: write down the three pieces of state that must survive navigation, then prompt Rork to move exactly those into Context. The way your app breaks — or stops breaking — will tell you the rest.

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 $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-07-14
Designing Seams That Survive AI Regeneration in Rork
Every follow-up prompt to Rork can quietly wipe out logic you wrote by hand. Protecting it with prompts is a patch, not a fix. Here is how to separate generated code from code you own, and draw a boundary that regeneration cannot reach, with working Zustand and service-layer examples.
Dev Tools2026-08-01
Every Experiment Kept Landing on the Same Devices: Hash Choice and Salt Position, Measured Across a Million IDs
On-device experiment assignment looked fine in isolation, then collapsed the moment two experiments ran side by side. Here is what one million IDs revealed about four hash functions, why the culprit was the concatenation order rather than hash quality, and the implementation I settled on, with the measured numbers.
Dev Tools2026-07-24
Collapsing Duplicate Requests Into One: A Reference-Counted Single-Flight Layer
When several components fire the same API call at launch, you get a burst of identical requests. Here is a single-flight layer that shares one in-flight promise instead: how to build the key that decides the folding, the trap of handing out a failed promise forever, the trap of one caller's abort cancelling everyone, and the test that keeps it all from regressing, with the real network numbers alongside.
📚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 →