Debugging React: Follow Props, State and Ownership
Debugging React: Follow Props, State and Ownership
An attendance counter changes internally, but the displayed list disagrees. Randomly adding Effects can make the problem harder to see; a reproducible interaction and a clear ownership trace reveal the first mismatch.
Debugging is finding why actual behavior differs from an explicit expected result. In React, inspect the data owner, rendered props and state, event updates, keys and synchronization before changing code.
Understand
Reproduce a minimal failing interaction.
Build
Trace an immutable state update to its rendered result.
Check
Use development tools without treating logs as a timing guarantee.
Find the first divergence in the data flow
Expected
One attendance toggle changes one student.
Trace
Inspect the owner and the next state produced.
Verify
Repeat the interaction and check derived counts.
Make the failure specific before fixing it
Write down the input and interaction: Maya begins absent, one click should mark her present, and the present count should become one. This turns “React is broken” into a checkable observation. Inspect whether the handler ran, whether it produced the next state and whether the rendered expression reads the correct value.
React Developer Tools can inspect component props and state. Browser tools help inspect DOM, network responses and console errors. Logging in a render shows an attempted render and may appear more than once during development checks; do not infer that every log means a separate user-visible commit.
Expose a clear state trace for attendance
The button uses map to create a new array and object rather than mutate the existing record. The summary is derived from that same array. This corrected, complete example is the reference behavior to compare against a failing implementation.
React · JavaScript with JSX
import { useState } from 'react';
export default function App() {
const [students, setStudents] = useState([{ id: 1, name: 'Maya', present: false }]);
function toggle() {
setStudents(current => current.map(student => student.id === 1 ? { ...student, present: !student.present } : student));
}
const count = students.filter(student => student.present).length;
return <main><h1>Attendance trace</h1><p>Present count: {count}</p>
<p>Maya: {students[0].present ? 'present' : 'absent'}</p><button onClick={toggle}>Toggle Maya</button></main>;
}Expected screen and behavior
Toggle Maya changes the flag and derived Present count: 1 together.Find mutation and stale reads before adding synchronization
React · Expose a clear state trace for attendance
import { useState } from 'react';
export default function App() {
const [students, setStudents] = useState([{ id: 1, name: 'Maya', present: false }]);
function toggle() {
setStudents(current => current.map(student => student.id === 1 ? { ...student, present: !student.present } : student));
}
const count = students.filter(student => student.present).length;
return <main><h1>Attendance trace</h1><p>Present count: {count}</p>
<p>Maya: {students[0].present ? 'present' : 'absent'}</p><button onClick={toggle}>Toggle Maya</button></main>;
}A broken version might assign students[0].present directly and call setStudents(students). That reuses the same array identity and mutates earlier state. React may skip the update because the provided state is equal to the old state by Object.is. The corrected source replaces both the array and the changed record.
Another failure can read a state snapshot immediately after setting it and assume it already changed. Inspect the next render or use the functional updater’s input when calculating from pending state. An Effect is not a general patch for ownership, mutation or snapshot misunderstandings.
Match debugging tools to the suspected layer
If the handler is not called, inspect its prop, disabled state and event target. If the wrong row changes, inspect the stable student ID and list key rather than just the row index. If network data is wrong, inspect status and payload shape before blaming rendering.
Reduce a complex page to the smallest example that still fails. Add a focused regression test after identifying the cause, so later changes cannot quietly restore the same bug. Keep unrelated refactoring out of the fix until the observed failure is explained.
Try the school task
React · starting example
import { useState } from 'react';
export default function App() {
const [students, setStudents] = useState([{ id: 1, name: 'Maya', present: false }]);
function toggle() {
setStudents(current => current.map(student => student.id === 1 ? { ...student, present: !student.present } : student));
}
const count = students.filter(student => student.present).length;
return <main><h1>Attendance trace</h1><p>Present count: {count}</p>
<p>Maya: {students[0].present ? 'present' : 'absent'}</p><button onClick={toggle}>Toggle Maya</button></main>;
}Add Ravi and toggle only his attendance. Verify that Maya remains absent and the derived count changes to one.
Worked answer
The condition selects Ravi by ID rather than array position. The unchanged Maya object can be retained, while Ravi gets a replacement. The same list supplies both the labels and count.
React · JavaScript with JSX
import { useState } from 'react';
export default function App() {
const [students, setStudents] = useState([{ id: 1, name: 'Maya', present: false }, { id: 2, name: 'Ravi', present: false }]);
const count = students.filter(student => student.present).length;
return <main><h1>Targeted attendance</h1><p>Present count: {count}</p>
<ul>{students.map(s => <li key={s.id}>{s.name}: {s.present ? 'present' : 'absent'}</li>)}</ul>
<button onClick={() => setStudents(current => current.map(s => s.id === 2 ? { ...s, present: !s.present } : s))}>Toggle Ravi</button></main>;
}Expected screen and behavior
Toggle Ravi makes Ravi present and count one while Maya remains absent.Mistakes and boundaries
Development StrictMode can repeat render work to reveal impurities. Do not “fix” repeated logs by mutating global counters or disabling checks before understanding the behavior.
Do not paste private student data, credentials or entire production responses into public debugging reports. Use a small synthetic reproduction with the same relevant shape.
A passing local example does not verify a deployed network, router fallback or service permission. Test the layer where the failure actually occurred.
Check your reasoning
React · Expose a clear state trace for attendance
import { useState } from 'react';
export default function App() {
const [students, setStudents] = useState([{ id: 1, name: 'Maya', present: false }]);
function toggle() {
setStudents(current => current.map(student => student.id === 1 ? { ...student, present: !student.present } : student));
}
const count = students.filter(student => student.present).length;
return <main><h1>Attendance trace</h1><p>Present count: {count}</p>
<p>Maya: {students[0].present ? 'present' : 'absent'}</p><button onClick={toggle}>Toggle Maya</button></main>;
}Choose the answer that follows the data and the interaction, then read the explanation for each alternative.
Carry the idea forward
Reproduce, trace the first mismatch, fix its cause and verify the original task. Review array updates and regression checks.
Read the primary reference: React: developer tools.
Reaching the recap and completing the quizzes are separate milestones.