Course outline

React Native / Native descriptions and local interaction / Lesson 4

Events, state snapshots, and immutable updates

Keep user intent in handlers and derive the next interface from one authoritative state snapshot.

About 4 min readContent reviewed
On this page

Lesson goals

  • A render reads a snapshot
  • Pass a handler rather than its immediate result
  • Replace owned collections instead of mutating snapshots

A render reads a snapshot

React calls a component with the props and state for that render. Setting state requests a future render; it does not rewrite the local variable already captured by the current handler. When the next value depends on the previous value, use a functional updater so queued transitions compose correctly. Do not perform navigation, subscriptions, or storage writes while calculating JSX. A render may repeat without representing another user action. Keep the event that owns an operation visible, and derive labels or totals from current accepted state rather than storing redundant mutable copies that can diverge.

Pass a handler rather than its immediate result

The first module gives Pressable a function for onPress. Calling the function during render instead would request state changes before activation and can create repeated rendering. The functional updater expresses one increment from the latest queued count. Pressable handles interaction state, while accessibilityRole and a useful label identify the action’s meaning. A styled View with a gesture is not automatically an equivalent accessible button. The predicted initial count is zero and one activation requests count one. Native press delivery, focus, and spoken feedback remain unexecuted in this environment.

jsx Illustrative React Native module; requires the declared compatible application host.
import {useState} from 'react';
import {Pressable, Text} from 'react-native';
export default function App() {
  const [count, setCount] = useState(0);
  return <Pressable accessibilityRole="button" accessibilityLabel="Add one"
    onPress={() => setCount(previous => previous + 1)}>
    <Text>Count: {count}</Text>
  </Pressable>;
}
Expected output
Initially Count: 0; after one activation, the next description is Count: 1.

The handler requests an update rather than mutating count. This module is an illustrative native interaction, not an executed React test.

Replace owned collections instead of mutating snapshots

The second example models selection by stable identifier. It creates a fresh array when adding or removing an identifier, leaving the earlier snapshot unchanged. A React owner can use it inside a functional state updater. A shallow copy does not recursively protect mutable objects inside an array; nested updates need their own ownership policy. Keeping selection as identifiers also prevents reordering a result list from silently selecting another row at the old index. The pure program proves only these local value transitions when executed under Node, not that a native component rerenders correctly.

javascript Standalone JavaScript model with synthetic local inputs.
function toggleId(selected, id) {
  return selected.includes(id) ? selected.filter(value => value !== id) : [...selected, id];
}
const before = ['w-7'];
const after = toggleId(before, 'w-9');
console.log(JSON.stringify(before));
console.log(JSON.stringify(after));
console.log(JSON.stringify(toggleId(after, 'w-7')));
Expected output
["w-7"]
["w-7","w-9"]
["w-9"]

The source snapshot retains w-7. Adding w-9 and removing w-7 produce separate arrays with explicit membership.

Choose state lifetime by the interaction

A local disclosure flag can belong to one component occurrence, while account data may need a broader owner. Keys and tree position affect whether React preserves local state when descriptions change. Do not generate a fresh key on every render to force an update; that can reset a draft instead of repairing ownership. A cancellable editor often needs a separate draft from the saved shared record. Define Save and Cancel before connecting a binding-like update path to authoritative data. A state-management library cannot decide these lifetime and command semantics automatically.

Verify intent, transitions, and visible results separately

A useful model test checks the previous snapshot, next value, and invariants such as unique selection identifiers. A configured component test should then activate the public control and inspect the committed description, not count incidental function calls. Include rapid repeated activation and an unrelated parent rerender. If a command is asynchronous, define pending and duplicate-activation behavior rather than assuming state updates make remote operations unique. A disabled control improves interaction but does not enforce server authorization or idempotency. No React renderer or native interaction is executed for the course’s component fragments.

Common errors and fixes

onPress runs during rendering

Why it happens
The prop received a function call result instead of a callback.
How to fix it
Pass a function that performs the event action when activated.
Prevent it next time
Review handler props for accidental parentheses.

Selection changes without a reliable rerender

Why it happens
The existing array was mutated and reused as the same state reference.
How to fix it
Return a new owned collection from the updater.
Prevent it next time
Check previous snapshots remain unchanged.

A form draft disappears after a harmless update

Why it happens
A changing key replaced the component identity.
How to fix it
Use stable identity and place drafts under the intended owner.
Prevent it next time
Rerender the parent while input is present.

Check your understanding

1. Why can reading count immediately after setCount still show the old value?

Show worked solution

The handler closes over the snapshot for its current render. The setter queues a future render rather than rewriting that captured variable. Use a functional updater for dependent transitions and inspect the committed next interface in an appropriate test host.

2. Where should selection live when two list views must agree?

Show worked solution

Place one authoritative set of selected identifiers in their nearest suitable common owner or a broader application model with a defined lifetime. Pass values and commands to both views. Two independently mutable copies create synchronization obligations and can diverge after refresh or navigation.

Key takeaways

  • A render reads a snapshot
  • Verify intent, transitions, and visible results separately

Official references