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-04-23Intermediate

State Updated in Rork But UI Won't Re-render? Five Patterns and Fixes

setState is firing in your Rork-generated code but the UI refuses to update. Five common root causes — mutation, stale closures, Zustand selectors, missed dependencies, unmounted updates — each with Before/After code.

state management5re-renderuseState2Zustand4troubleshooting65Rork515React Native209

You call setState, the value changes in console.log, but the screen just sits there unchanged. If you have spent an afternoon staring at a Rork-generated app wondering why your updates do not land on the UI, you already know this particular flavour of frustration. Infinite loops are at least loud — this failure mode is quiet, and quiet bugs tend to eat hours.

I once lost half a day on a prototype before remembering that React compares state by reference, not by value. Once that clicked, the symptom made perfect sense. There are only a handful of patterns that produce the "setState did nothing" bug, and once you recognise them, the fix becomes a reflex.

Cause 1: You Mutated an Array or Object In Place

This is by far the most common one. Ask Rork to "append an item to the list" and every so often you will see code that uses .push():

// ❌ The UI never updates
const [items, setItems] = useState<string[]>([]);
 
const addItem = (text: string) => {
  items.push(text);      // mutates the existing array
  setItems(items);       // same reference, React sees no change
};

React compares the previous and next state with Object.is. Because items.push() mutates the same array instance, the reference is unchanged and React decides nothing happened. The log shows the new contents, but no re-render is scheduled.

// ✅ Produce a new array
const addItem = (text: string) => {
  setItems((prev) => [...prev, text]);
};

Objects follow the same rule: user.name = 'new' is invisible to React. Use setUser({ ...user, name: 'new' }) instead. When prompting Rork, adding "update immutably" to your instructions is usually enough to steer the output away from mutation.

Cause 2: A Stale Closure Has Your State Frozen in Time

Any function defined inside a component — effect callbacks, timers, event handlers — captures the state values that existed the moment it was created. If dependencies are wrong, that closure holds onto an old value forever.

// ❌ count is always 0
useEffect(() => {
  const id = setInterval(() => {
    setCount(count + 1);
  }, 1000);
  return () => clearInterval(id);
}, []);

With an empty dependency array, the interval callback captures count === 0 once and keeps calling setCount(0 + 1) every second. The state flips from 0 to 1 and sits there.

// ✅ Read the latest value via the updater form
useEffect(() => {
  const id = setInterval(() => {
    setCount((prev) => prev + 1);
  }, 1000);
  return () => clearInterval(id);
}, []);

The functional form of setState receives the current state at call time, so closures are no longer a concern. This is the mirror image of the trap covered in Why useEffect Loops Infinitely in Rork-Generated Code — two sides of the same closure problem.

Cause 3: A Zustand Selector That Returns a Fresh Object Every Time

When Rork generates Zustand code, you will occasionally see this shape:

// ❌ New object on every render — unstable subscriptions
const { user, setUser } = useUserStore((s) => ({
  user: s.user,
  setUser: s.setUser,
}));

The selector returns a brand new object on each call. With Zustand's default shallow equality, this can either trigger unnecessary re-renders or — depending on what else changes in the store — mask real updates. The component ends up either flickering or frozen, and both look like "state is broken" from the outside.

// ✅ Pull primitives and functions one at a time
const user = useUserStore((s) => s.user);
const setUser = useUserStore((s) => s.setUser);

Alternatively, pass shallow as the equality comparator when you really do need to return an object. For deeper structural patterns, Rork App State Management Patterns works through the architectures that keep large apps stable.

In practice I prefer keeping every Zustand subscription as narrow as possible — one primitive per useStore call — and only widening the selector when profiling shows it matters. That default keeps re-renders predictable, and it makes AI-generated store code easier to review: a one-liner selector is obvious, while a destructured object selector always needs a second look.

Cause 4: Missing Dependencies in React.memo or useMemo

When you reach for memoisation for performance reasons, an incomplete dependency array quietly freezes computed values at their first result.

// ❌ filteredList never recomputes, even when items changes
const filteredList = useMemo(
  () => items.filter((i) => i.active),
  []
);

Enabling the react-hooks/exhaustive-deps ESLint rule the moment you scaffold a Rork project catches almost every instance of this at edit time. It is the single highest-leverage lint rule in React Native work.

// ✅ Dependencies declared honestly
const filteredList = useMemo(
  () => items.filter((i) => i.active),
  [items]
);

A related trap: wrapping a component in React.memo only helps if the props you pass in are stable. If the parent rebuilds onPress={() => doThing(id)} on every render, the memoised child still re-renders. Stabilise those callbacks with useCallback on the parent side.

Cause 5: setState After the Component Has Unmounted

If you kick off a fetch, navigate away before it resolves, and then call setState in the resolved promise, the update targets a component that no longer exists. You will often see a yellow warning like Can't perform a React state update on an unmounted component in Metro — that is your clue.

useEffect(() => {
  let alive = true;
  (async () => {
    const data = await fetch('/api/items').then((r) => r.json());
    if (!alive) return;             // bail out after unmount
    setItems(data);
  })();
  return () => { alive = false; };
}, []);

AbortController is an even cleaner option if your fetch layer supports it, because you cancel the request itself. For list-rendering symptoms that can look similar, FlatList Renders Blank in Rork? walks through the blank-list debugging path.

A Three-Minute Triage Order

Rather than guessing, step through these checks in order:

  • 1. Did the value actually change? Log prev and next before the update. If prev === next by reference, you are in Cause 1.
  • 2. Is there a stale closure? Scan useEffect, setTimeout, and inline handlers for direct state reads; swap them for the updater form of setState.
  • 3. Is the component re-rendering at all? Drop a console.log('render', props) at the top of the component. If it never fires, the problem is upstream — memo, selector, or store subscription.
  • 4. Parent or child state? Make sure the update is hitting the owner of the state, not a local copy in a child.
  • 5. Platform-specific? iOS-only or Android-only reproductions deserve their own investigation; cross-platform behaviour differences are a distinct bucket of bugs.

The order matters. Skipping ahead to React DevTools or the profiler before confirming that the value itself changed has burned me more than once. Start with the cheapest check (a console.log) and only reach for heavier tooling when the simple signal is ambiguous.

Small Habits That Prevent the Next Occurrence

A few routines save me from the same bug twice:

Turn on react-hooks/exhaustive-deps on day one of any Rork project. Fix the warnings all at once while the codebase is small; coming back later to untangle them is much slower.

Adopt the rule that any state update that depends on the previous state uses the functional form — setX((prev) => …). Even when you think you do not need it, writing it consistently eliminates an entire class of closure bugs.

When using Zustand or Context, audit every selector. Pull primitives individually, and only return an object with shallow when you have a real reason to.

Run your current bug through the three-minute triage order from the top — nine times out of ten you will land on Cause 1 or Cause 2. Going from "lost afternoon" to "ten-minute fix" is the kind of workflow shift that changes how it feels to build with Rork.

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-05-13
Switching from Context API to Zustand v5 in Rork Apps: What Changed and Why It Worked
Context API caused cascading re-renders in a growing Rork app. Here's how migrating to Zustand v5 solved it — with practical patterns for auth state and async logic in React Native.
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-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.
📚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 →