●FUNDING — Rork raises a $15M seed led by Left Lane Capital●RORK MAX — Rork Max generates native Swift apps instead of React Native●PLATFORM — It targets iPhone, iPad, Watch, and Vision Pro, reaching Live Activities and Core ML●GROWTH — Traffic keeps climbing at 743K monthly visits and 85% growth●TEST — The Companion app lets you test on a real device without a paid Apple Developer account●STACK — Built on React Native and Expo for true native experiences, not web wrappers●FUNDING — Rork raises a $15M seed led by Left Lane Capital●RORK MAX — Rork Max generates native Swift apps instead of React Native●PLATFORM — It targets iPhone, iPad, Watch, and Vision Pro, reaching Live Activities and Core ML●GROWTH — Traffic keeps climbing at 743K monthly visits and 85% growth●TEST — The Companion app lets you test on a real device without a paid Apple Developer account●STACK — Built on React Native and Expo for true native experiences, not web wrappers
Adding Home-Screen Quick Actions to a Rork App — dynamic items without cold-launch drops
How to implement the long-press quick actions on a Rork (Expo) app icon. Covers static vs dynamic items, the iOS/Android differences, and the cold-launch problem where the action arrives before the router is ready — solved with a hold-and-replay design.
Long-press an app icon on the Home Screen and a menu opens beneath it: "New Entry," "Resume." These are quick actions (iOS Home Screen Quick Actions, Android App Shortcuts). They shorten a frequently used operation into a single move before opening the app — modest, but effective.
When I added this to a logging app I run as an indie developer, my first implementation behaved like this: "choosing New Entry from the icon just opens the Home Screen." When you pick an item while the app isn't running (a cold launch), the action's information arrives before the app's router — the navigation machinery — has come up, so it vanished with no one to receive it. Preventing that drop is the real body of quick-action design.
Walk through the steps first
Before the detailed code, here's the sequence to getting this into production.
Sort items into static (always shown, like New Entry) and dynamic (state-dependent, like Resume)
Set up a shared registration point for iOS and Android with expo-quick-actions
Build a receiving side that holds the cold-launch initial action and replays it after the router is ready
Suppress duplicate firing between the initial action and the listener using an ID
Clean up dynamic items to follow logout and data deletion
Of these five, step 3 is where you stumble. I underestimated it and spent a full day solving the drop. The rest goes together smoothly.
Static vs dynamic items
There are two kinds of quick actions.
Static items: a fixed menu baked into the app build. They appear even if the app has never been launched
Dynamic items: a menu you swap in via code while the app runs. You can vary them by user state
The basic split is static for things you always want shown ("New Entry"), and dynamic for state-dependent ones ("Resume X"). In my case I settled on showing only the static "New Entry" when signed out, and adding "Resume last entry" dynamically after login.
iOS displays up to 4 items. Android, too, is best kept to about 4 in practice. Registering more just hides them below where they won't be tapped, so narrowing to what's truly used works better.
✦
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
✦You'll be able to implement iOS UIApplicationShortcutItem and Android App Shortcuts through a single expo-quick-actions API
✦You'll solve the cold-launch problem where an action arrives before navigation is ready and vanishes, with a hold-then-replay design and working code
✦You'll learn dynamic updates that vary items by login state, plus the cleanup judgment that avoids leaving stale items behind
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.
A standard Rork app is generated as React Native (Expo), so expo-quick-actions lets you handle iOS UIApplicationShortcutItem and Android App Shortcuts through one API. First, registering dynamic items.
Carrying the destination route in params is the key. Rather than a switch per action type, writing "where to go" into the item itself keeps the receiving side simple as items grow.
Prevent cold-launch drops with hold-and-replay
This is the center of the design. An action arrives in two ways.
Launch state
How the action arrives
Caveat
Cold launch (app not running)
Fetched once at startup as the initial action
Arrives before the router is ready — needs holding
Warm launch (resume from background)
Delivered in real time via a listener
Safe to navigate immediately
On a cold launch the first action sits in QuickActions.initial. You read it at startup, but at that instant navigation usually isn't ready. So make it "hold, then replay once the router is ready."
// useQuickActionRouting.tsimport { useEffect, useRef } from 'react';import * as QuickActions from 'expo-quick-actions';import { router } from 'expo-router';export function useQuickActionRouting(isRouterReady: boolean) { const pending = useRef<string | null>(null); // Shared handler: hold or run immediately const handle = (action: QuickActions.Action | null) => { const route = action?.params?.route as string | undefined; if (!route) return; if (isRouterReady) { router.push(route); } else { pending.current = route; // can't navigate yet, so hold } }; // The initial action on cold launch (once) useEffect(() => { handle(QuickActions.initial ?? null); const sub = QuickActions.addListener(handle); // warm launch return () => sub.remove(); }, []); // Replay the held action the moment the router becomes ready useEffect(() => { if (isRouterReady && pending.current) { router.push(pending.current); pending.current = null; } }, [isRouterReady]);}
Pass isRouterReady something that means "safe to navigate" — auth resolved, layout mounted. Holding the cold-launch action in pending and replaying it exactly once in the readiness useEffect was the reliable way to avoid drops.
Suppress duplicate firing and double navigation
In practice, right after reading initial on a cold launch, the listener would also deliver the same action, navigating to the same screen twice. To prevent this, remember the processed action IDs.
Keying on the ID alone would ignore a deliberate second tap of the same item, so combining it with route was safer. It also handles repeated invocations on warm launch naturally.
Don't leave stale dynamic items behind
Dynamic items need updating when app state changes. Two things that paid off in practice:
Clear dynamic items on logout. If "Resume" lingers and a different account logs in, it jumps to the previous user's data. Reset to a minimal static-equivalent set in your logout flow with setItems
Track data deletion. If the record "Resume" points to is deleted, either remove that dynamic item or provide a fallback at the destination for "no longer exists"
// Reset dynamic items to the initial state on logoutasync function onLogout() { await setQuickActions(false); // drop login-dependent items}
Quick actions aren't a flashy feature, but being able to call a frequent operation in one move quietly raises how often it's used. The hard part of the implementation isn't the feature itself — it's the receiving-side design that doesn't drop the action arriving on cold launch. Build the three carefully — hold-and-replay, duplicate suppression, and state-aware cleanup — and you get a path that works reliably as a pre-launch move. I hope it helps anyone tackling the same problem.
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.