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.
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.
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>;
}
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.
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')));
["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