RORK LABJP
MAX — Rork Max builds native Swift apps instead of React Native, targeting the whole Apple ecosystemDEVICES — From one description it spans iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessageNATIVE — It unlocks AR and LiDAR scanning, 3D games with Metal, Home Screen widgets, Dynamic Island, and on-device Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, joined by Peak XV and a16z SpeedrunPAPERLINE — Rork acquired the app builder Paperline and plans to stay acquisitive for engineering talentMARKET — With 743,000+ monthly visits, and Gartner expects 75% of new apps to be low-code or no-code by end of 2026MAX — Rork Max builds native Swift apps instead of React Native, targeting the whole Apple ecosystemDEVICES — From one description it spans iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessageNATIVE — It unlocks AR and LiDAR scanning, 3D games with Metal, Home Screen widgets, Dynamic Island, and on-device Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, joined by Peak XV and a16z SpeedrunPAPERLINE — Rork acquired the app builder Paperline and plans to stay acquisitive for engineering talentMARKET — With 743,000+ monthly visits, and Gartner expects 75% of new apps to be low-code or no-code by end of 2026
Articles/Dev Tools
Dev Tools/2026-07-15Intermediate

A Yellow Warning in Dev, a Crash on Resume — Functions in Route Params

Rork generated navigation code that put a function and a Date into route params. In development it was only a warning. After the OS reclaimed the process, resuming the app crashed with params.onFavorite is not a function. Here is the cause and the fix.

Expo Router5React Navigation2Troubleshooting38State RestorationRork510

An unfamiliar crash was sitting at the top of Crashlytics during my usual morning check on the wallpaper apps.

TypeError: params.onFavorite is not a function

I had never seen it on my own device. I open that detail screen every day. Still, 19 occurrences the previous day — all Android, all clustered in the moments right after someone reopened the app.

A crash you cannot reproduce is a heavy thing to carry around. Once I traced this one, though, the answer turned out to be a yellow warning I had been politely ignoring for weeks.

The symptom — it only breaks on the way back

Laid out plainly:

SituationResult
List → detail navigationFine
Tapping the favorite button on detailFine
Background the app, return immediatelyFine
Background the app for 30+ minutes, then returnCrash
Development (Expo Go / dev build)Warning only

That fourth row is the whole story.

While the app sits in the background, the OS reclaims the process. To the user, nothing has happened — the app is still there in the task switcher. But the app died, and on resume the OS tries to put the user back on the screen they left.

What gets rebuilt at that moment is the saved navigation state.

Reproducing it — one developer option

You do not have to wait 30 minutes. Open the Android developer options and turn on:

  • Don't keep activities

Now the process is destroyed the instant you press Home. Return to the app and the crash appears immediately. Mine reproduced on the very first try.

iOS behaves the same way in principle, but the timing depends on memory pressure, so reproducing it by hand takes patience. Android was the faster route.

The cause — navigation state is serialized to JSON

Here is what Rork generated, more or less untouched:

// The generated code, as I had been shipping it
const openDetail = (item: Wallpaper) => {
  router.push({
    pathname: "/wallpaper/detail",
    params: {
      item,                          // the whole object
      publishedAt: item.publishedAt, // a Date instance
      onFavorite: () => toggleFavorite(item.id), // a function
    },
  });
};

It reads well. The detail screen just calls params.onFavorite(). Nothing about it caused trouble in development.

The catch is that navigation state gets converted to JSON so it can be persisted.

JSON.stringify drops functions silently. Dates become strings. So after restoration, params actually held this:

// What was really there after resume
{
  item: "[object Object]",                 // stringified into nothing useful
  publishedAt: "2026-07-01T00:00:00.000Z", // a string, not a Date
  onFavorite: undefined,                   // gone
}

onFavorite is undefined, and calling it throws. Anything doing publishedAt.getTime() breaks the same way.

And the warning I had been seeing all along:

Non-serializable values were found in the navigation state.
Check: wallpaper/detail > params.onFavorite

React Navigation had been telling me from day one. I had filed it under "it works, I'll look later." I had simply never connected it to state restoration.

The fix — put nothing but identifiers in params

There are several ways through this, but the ordering is not ambiguous.

Fix 1: only identifiers in route params (recommended)

The dullest option, and the one that actually works. Decide that params may hold strings, numbers, and booleans — nothing else.

// Navigating
const openDetail = (item: Wallpaper) => {
  router.push({
    pathname: "/wallpaper/detail",
    params: { id: item.id },   // that's all
  });
};
// Receiving: app/wallpaper/detail.tsx
import { useLocalSearchParams } from "expo-router";
import { useWallpaperStore } from "@/store/wallpaper";
 
export default function WallpaperDetail() {
  const { id } = useLocalSearchParams<{ id: string }>();
  const item = useWallpaperStore((s) => s.byId[id]);
  const toggleFavorite = useWallpaperStore((s) => s.toggleFavorite);
 
  // Right after restoration the store may not be hydrated yet
  if (!item) return <DetailSkeleton />;
 
  return (
    <DetailView
      item={item}
      publishedAt={new Date(item.publishedAt)}
      onFavorite={() => toggleFavorite(id)}
    />
  );
}

Look the object back up from the ID. Keep the callback in the store. Now, however violently the process dies, one surviving string is enough to rebuild the screen.

The reason identifiers are safe is that an identifier can live in a URL. If a deep link hits /wallpaper/detail?id=zen-042 from outside, the screen still works. That shape and the restoration-proof shape are the same shape. Once that clicked, the decision stopped being a judgment call.

Do not skip the if (!item) return <DetailSkeleton /> line. Store hydration can lag a restore, and without that guard you simply move the crash to item.uri. I forgot it on my first pass and briefly celebrated a crash that had merely changed its name.

Fix 2: pass Dates as ISO strings and rebuild them

If a timestamp genuinely has to travel in params, send a string.

router.push({
  pathname: "/wallpaper/detail",
  params: { id: item.id, publishedAt: item.publishedAt.toISOString() },
});
 
// Receiving
const { publishedAt } = useLocalSearchParams<{ publishedAt: string }>();
const date = new Date(publishedAt);

Both the outbound and the restored path go through the same conversion, so the value survives.

Fix 3: make the warning impossible to ignore

The root problem was that the warning stayed yellow. In development, I promoted it to an exception.

// app/_layout.tsx
if (__DEV__) {
  const origWarn = console.warn;
  console.warn = (...args: unknown[]) => {
    const msg = String(args[0] ?? "");
    if (msg.includes("Non-serializable values were found")) {
      throw new Error(`[navigation] ${msg}`);
    }
    origWarn(...args);
  };
}

Blunt, but decisive. From then on, the moment generated code reaches for that pattern, it stops on my machine. Production is untouched because __DEV__ is false there.

Prevention — run generated code through a resume before accepting it

None of this is Rork's fault. The generated code is a reasonable, readable way to express the intent, and it does work while you are building. What the generator cannot know is that the process will die and the screen will be rebuilt from a JSON blob. Handling that is the receiving side's job, and I prefer to treat it that way.

I added one line to my device checklist:

  • Walk the main screens with "Don't keep activities" left on

About 3 minutes per app. Under 20 minutes across all six. Since adding it, resume crashes have stopped showing up in Crashlytics.

Pinning down the types at the navigation boundary helps too — deviations in generated code surface immediately.

// Declare what's allowed, and let the compiler hold the line
type SerializableParams = Record<string, string | number | boolean>;
 
export const pushDetail = (params: { id: string }) =>
  router.push({ pathname: "/wallpaper/detail", params });

Funnel navigation through functions like this, and the moment generated code tries to hand over an object, you get a type error instead of a Crashlytics entry.

A triage table

When something similar turns up, this is the order I check:

SymptomWhere to look
is not a function, only on resumeA function is riding in params
getTime is not a function, only on resumeA Date in params became a string
Blank screen, only on resumeYou are not re-fetching by ID
[object Object] after resumeAn object is riding in params
Crashes every timeDifferent problem — restoration is not involved

If the words "only on resume" fit, it is almost certainly this family.

What to do next

Start by checking your development console for Non-serializable values were found. Every screen it names is a candidate for crashing on resume.

Then reshape those navigation calls to carry an ID and nothing more. Those two steps closed the case for me.

If you are reworking the navigation structure itself, the Expo Router design walkthrough is a good foundation. For state that has to survive a process death, the piece on wallpaper grids jumping back to the top covers the save-and-restore timing in more depth.

Quiet work — clearing one warning. But watching an unreproducible crash disappear was a good feeling.

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 →

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-04-27
Why the Android Back Button Stops Working in Your Rork App — A BackHandler Field Guide
When testers tap the Android back button, your Rork app exits unexpectedly, jumps to the wrong screen, or refuses to dismiss a modal. Here are the five most common BackHandler pitfalls in Expo Router projects, with working code for each.
Dev Tools2026-06-17
Your Rork App Is Stuck on "Processing" in App Store Connect — A Field Guide to Getting It Unstuck
You uploaded a Rork-built app to App Store Connect, but the build sits on Processing for hours and never shows up in TestFlight or for submission. Here is the exact diagnostic order I follow, plus the pre-upload checks that stop it from happening again — with working code.
Dev Tools2026-05-29
Diagnosing 'Network request failed' That Only Hits Android Emulator in Rork
Your fetch returns fine in the iOS simulator but throws 'Network request failed' the moment you switch to Android. Here is the diagnosis order I use to separate localhost, cleartext, certificate, and proxy issues, with code that actually compiles.
📚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 →