React Native Runtimes
Guides

Performance tips

Practical advice for getting the most out of threaded runtimes and shared state.

Prewarm what the user will see next

Prewarming a runtime is cheap if it's already warm and one-time-expensive if it isn't. The best time to start a runtime is while the user is doing something else — reading a list, scrolling an inbox, scrubbing onboarding.

useEffect(() => {
  for (const runtimeName of visibleConversationRuntimes) {
    void ThreadedRuntime.prewarm(runtimeName);
  }
}, [visibleConversationRuntimes]);

For app-lifetime runtimes (background, sync engines), prewarm from native startup so the runtime exists before any JS asks for it.

Keep props small

Every prop change crosses the threaded boundary as JSON. Pass ids, cursors, and config values — not full datasets. Use shared state for anything large or mutable.

A 1 MB prop is a 1 MB serialization cost on every change

And every change re-serializes it. Push large data through a shared path and pass only its identity through props.

Prefer update over set for derived writes

When a write depends on the current value, use update. It reads under the native lock and writes back atomically — no two-runtime race.

await messages.update(current => [...(current ?? []), newMessage]);

Pre-declare hot paths

For state that the app touches early, list it in createSharedStore({ subtrees: [...] }) so it hydrates on store creation — no first-touch latency.

Avoid runtime-per-mount

Pick runtime names by logical owner (conversation id, feed type, business unit), not by component instance. Reusing the runtime across mounts means the JS heap, cached requires, and registered loaders all stay warm.

Tear down what you don't need

Long-running runtimes hold JS heap. For runtimes that own a single transient screen, use destroyOnUnmount on ThreadedScreen so they release when the user leaves.

<ThreadedScreen
  component={ConversationScreen}
  destroyOnUnmount
  props={{ conversationId }}
  runtimeName={`conversation-${conversationId}-runtime`}
/>

App-lifetime runtimes (background, sync) should not be destroyed.

Measure both sides

When evaluating "does threading help", compare:

  • The main-runtime baseline (LegendList on main).
  • The threaded version (LegendList on its own runtime).

The sample app has both screens for exactly this purpose.

On this page