RORK LABJP
TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the roundTOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
Articles/Dev Tools
Dev Tools/2026-05-17Intermediate

Rork FlatList Crashes from Direct State Mutation – Defensive Copy Fix

FlatList crashes in Rork-generated code are often caused by direct array mutation. Learn the defensive copy pattern that eliminated 50+ crashes in 28 days from a 50M-download app's v2.1.0 update.

FlatList9crash7state management5React Native209debugging13defensive copy

After releasing v2.0.0 of Beautiful HD Wallpapers — an iOS/Android app I've been developing since 2014 with over 50 million cumulative downloads — I opened Google Play Console 28 days later to find the same crash pattern affecting more than 50 users. The stack trace was consistent. The cause turned out to be straightforward: I was mutating the array passed to the list component directly.

This exact problem surfaces in React Native FlatList just as readily as it does in Android's native RecyclerView. If your Rork-generated app crashes with Inconsistency detected or Cannot read properties of undefined during fast scrolling or rapid UI interactions, this is very likely what's happening. Here's what I found and how I fixed it in v2.1.0.

Why FlatList Breaks Under Direct Mutation

In JavaScript, arrays and objects are passed by reference. Writing const newList = items does not create a new array — it creates a new variable that points to the same object in memory.

// Looks harmless, but this is dangerous
const handleCategoryChange = (newCategory) => {
  const filtered = allWallpapers; // reference copy, not a new array
  filtered.sort((a, b) => a.category.localeCompare(b.category));
  setWallpapers(filtered); // React may skip the update — same reference detected
};

FlatList internally tracks item counts and indices to manage efficient rendering. When the array contents change but the reference stays the same, FlatList's internal snapshot drifts out of sync with the actual data. Fast scrolling or rapid taps force a reconciliation that exposes this inconsistency as a crash.

What makes this pattern particularly difficult to track down is that it rarely reproduces in development. You'll see it in production, on real devices, during real usage patterns. Device performance, network latency, and user interaction speed all affect the timing. You can end up in a situation where you see no crashes in Simulator but Play Console is accumulating them steadily.

Rork-generated code is well-structured out of the box. The problem typically enters when you edit or extend that code — adding an API call, implementing a delete feature, or sorting data after fetching it.

The Fix: Always Create a New Array

The rule is simple: before calling a State setter, always produce a new array reference. There are three main ways to do this: using the spread operator, using methods that return new arrays naturally (like filter and map), or using the functional update form of the setter.

// ✅ Spread operator creates a new array reference
const handleCategoryChange = (newCategory) => {
  const filtered = [...allWallpapers].sort(
    (a, b) => a.category.localeCompare(b.category)
  );
  setWallpapers(filtered); // New reference — React detects the change reliably
};
 
// ✅ Functional update receives the latest State value at call time
const addItem = async () => {
  const newItem = await fetchItem();
  setItems(prev => [...prev, newItem]);
};
 
// ✅ filter and map return new arrays — safe to use directly
const removeItem = (targetId) => {
  setItems(prev => prev.filter(item => item.id !== targetId));
};

One important distinction: [...array].sort() and array.sort() behave differently. .sort() sorts the original array in place. Always spread first, then sort. The same applies to splice and push — both modify the original array and should be avoided in favor of their non-destructive equivalents.

Three Patterns That Slip Into Rork-Generated Code

When editing code that Rork generates, these three patterns are easy to introduce. They're the React Native equivalents of what caused the crash in my wallpaper app.

Pattern 1: Destructive operations with splice or push

When adding a delete feature, splice is intuitive but problematic.

// ❌ splice mutates the original array
const deleteItem = (index) => {
  const newItems = items; // same reference
  newItems.splice(index, 1); // original array is modified
  setItems(newItems); // React sees no reference change; UI may not update
};
 
// ✅ Use filter to produce a new array
const deleteItem = (index) => {
  setItems(prev => prev.filter((_, i) => i !== index));
};

Using filter returns a brand-new array containing only the elements that pass the condition. The original array is untouched. The same approach applies to sorting, reordering, and any other transformation.

Pattern 2: Shallow copy leaks with Object.assign

When updating a specific field inside a list item, Object.assign modifies the original object even though it looks like it's creating something new.

// ❌ Object.assign modifies the target object in place
const updateItem = (id, changes) => {
  setItems(prev => prev.map(item =>
    item.id === id ? Object.assign(item, changes) : item
  ));
};
 
// ✅ Spread creates a new object without touching the original
const updateItem = (id, changes) => {
  setItems(prev => prev.map(item =>
    item.id === id ? { ...item, ...changes } : item
  ));
};

{ ...item, ...changes } copies every property from item into a fresh object, then overlays the properties from changes. The original item object is never touched. This is the standard React immutable update pattern and should be the default approach whenever you update fields inside list items.

Pattern 3: Stale closure in async operations

When fetching data asynchronously and appending it to an existing list, directly referencing the items variable captures its value at the time the async function began — not when it resolves.

// ❌ items is the value from when the async function started
// Any update that happened during the fetch is lost
const loadMore = async () => {
  const result = await fetchNextPage();
  setItems([...items, ...result.data]);
};
 
// ✅ Functional update always receives the latest State value
const loadMore = async () => {
  const result = await fetchNextPage();
  setItems(prev => [...prev, ...result.data]);
};

By passing a function to the setter, React calls it with the current State value at the moment of the update — not a captured earlier value. For any async operation that touches State, this form should be the default.

Results After the Fix and How to Scan Your Project

After applying the defensive copy pattern throughout Beautiful HD Wallpapers v2.1.0, the crashes that had been hitting 50+ users over 28 days dropped to near zero within the first week post-release. The Android Vitals graph for user-impacting crashes was flat for the entire post-release measurement window.

To scan for the same issue in your Rork project:

# Find array variables assigned by reference before being passed to a setter
grep -rn "const new.*= items\|const new.*= data\|const new.*= list" src/ --include="*.ts" --include="*.tsx"
 
# Find destructive array methods used in component files
grep -rn "\.splice(\|\.push(\|\.sort(" src/ --include="*.tsx" | grep -v "// ✅"

Work through the results and check whether any found code path modifies the array before calling a State setter. Where you find a problem, the fix is either to spread the array first or switch to the functional setter form.

Related Articles

For performance issues after fixing crashes, Migrating from FlatList to FlashList v2 in Rork covers the next optimization step.

If State updates are not reflecting on screen even after fixing mutation issues, State Not Updating or Not Re-rendering in Rork addresses the closely related problem of React's shallow comparison and when it fails to trigger a re-render.

One Thing to Do Now

Open your current Rork project and search for State setter calls that don't use setXxx([... or setXxx(prev =>. Each one is a candidate crash site.

Once this pattern is internalized, it becomes a natural checkpoint in every code review. The same defensive copy habit that prevents FlatList crashes also eliminates the stale closure bugs that are notoriously difficult to trace in async-heavy React Native code. The two problems share a root cause: treating mutable references as if they were values.

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-06-21
Add Long-Press Drag Reordering to Your Rork Favorites List Without the Jank
A practical walkthrough for retrofitting long-press drag reordering onto a Rork-generated favorites list: keeping re-renders down, respecting the worklet boundary, persisting the order, and avoiding ghost cards and scroll conflicts.
Dev Tools2026-06-20
Why Your Rork List Starts Duplicating and Dropping Rows as It Grows — Cursor Pagination and Resilient Refetch State
The naive offset pagination Rork scaffolds for you quietly breaks the moment your list changes underneath the user. Here is how to move to a cursor contract, fold every fetch state into one usePaginatedList hook, and recover failed page loads with exponential backoff — implementation first.
Dev Tools2026-06-19
When Rork-Built Lists Stutter: Designing Image Caching and Prefetch
A FlatList from Rork starts stuttering once the images pile up. Here is how I restore smoothness with expo-image caching, recyclingKey, prefetch, and a move to FlashList, with the device numbers I measured.
📚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 →