Choosing State Structure
Choosing State Structure
An attendance page stores the students, the present count and the absent count separately. A teacher changes one student and forgets to update one count, leaving a contradictory report.
State structure is the shape of the information a component remembers. Keep the smallest complete set of changing facts, and calculate values that already follow from those facts while rendering.
Understand
Distinguish a stored fact from a calculated summary.
Build
Derive attendance counts from a student list.
Check
Avoid contradictions and duplicated student records.
Facts produce summaries
Remember facts
Keep each student and their present flag.
Calculate
Count present entries during rendering.
Describe
Show counts consistent with the same list.
Store facts, derive consequences
A present flag is an independent fact: the teacher can change it. The present count follows from the flags, and the absent count follows from the class size minus the present count. Storing all three creates more obligations whenever attendance changes. Deriving the counts means there is only one attendance record to update.
Derived means calculated from information you already have. It does not mean a special React API. Ordinary filter and subtraction are enough for this small class. If a selected student is needed, store a stable student ID and find the matching entry instead of storing another copy of the entire student object.
Calculate a consistent attendance summary
The class contains Asha and Ravi. Only the list belongs in state. Each render calculates present and absent from the list. Mark Ravi present replaces the matching object with a new object, preserving Asha’s record. The labels then follow automatically from the updated list.
React · JavaScript with JSX
import { useState } from 'react';
export default function App() {
const [students, setStudents] = useState([
{ id: 1, name: 'Asha', present: true },
{ id: 2, name: 'Ravi', present: false }
]);
const present = students.filter(student => student.present).length;
const absent = students.length - present;
return <main><h1>Attendance facts</h1>
<p>Present: {present}; Absent: {absent}</p>
<ul>{students.map(student => <li key={student.id}>
{student.name}: {student.present ? 'present' : 'absent'}
</li>)}</ul>
<button onClick={() => setStudents(current => current.map(student =>
student.id === 2 ? { ...student, present: true } : student
))}>Mark Ravi present</button>
</main>;
}Expected screen and behavior
Initially Present: 1 and Absent: 1. Mark Ravi present changes the counts to 2 and 0.One update changes several descriptions
React · Calculate a consistent attendance summary
import { useState } from 'react';
export default function App() {
const [students, setStudents] = useState([
{ id: 1, name: 'Asha', present: true },
{ id: 2, name: 'Ravi', present: false }
]);
const present = students.filter(student => student.present).length;
const absent = students.length - present;
return <main><h1>Attendance facts</h1>
<p>Present: {present}; Absent: {absent}</p>
<ul>{students.map(student => <li key={student.id}>
{student.name}: {student.present ? 'present' : 'absent'}
</li>)}</ul>
<button onClick={() => setStudents(current => current.map(student =>
student.id === 2 ? { ...student, present: true } : student
))}>Mark Ravi present</button>
</main>;
}On the first render, filter includes only Asha, so present is 1. There are two students, so absent is also 1. Clicking the button gives the updater the current list. map produces the next list, replacing Ravi’s present flag without mutating the old object.
React renders the component with that next list. filter now includes both entries and the same expression returns 2. The subtraction returns 0. There is no timing gap between a list update and a separate count update because the counts are computed from the very list used to describe this screen.
Choose shapes that prevent impossible cases
When a form has idle, sending and sent stages, one status string is often clearer than independent isSending and isSent booleans. Separate booleans can accidentally allow both to be true. A single status describes one stage at a time, and the UI derives button labels from that stage.
Flatten deeply nested information when frequent changes require many levels of copying. Group fields that must change together, but keep unrelated values independent when that improves clarity. There is no rule that every page needs one huge state object; the goal is making valid updates easy to express and invalid combinations harder to create.
Try the school task
React · starting example
import { useState } from 'react';
export default function App() {
const [students, setStudents] = useState([
{ id: 1, name: 'Asha', present: true },
{ id: 2, name: 'Ravi', present: false }
]);
const present = students.filter(student => student.present).length;
const absent = students.length - present;
return <main><h1>Attendance facts</h1>
<p>Present: {present}; Absent: {absent}</p>
<ul>{students.map(student => <li key={student.id}>
{student.name}: {student.present ? 'present' : 'absent'}
</li>)}</ul>
<button onClick={() => setStudents(current => current.map(student =>
student.id === 2 ? { ...student, present: true } : student
))}>Mark Ravi present</button>
</main>;
}Show a percentage of present students without adding percentage state. The classroom can be empty, so choose an explicit display rule for zero students. For the two students here, one present should display 50% and both present should display 100%. Keep the percentage consistent with the count.
Worked answer
The answer derives a percentage and guards division by zero. Math.round is the chosen display policy; other rounding policies are possible. Notice that the answer still has only one changing source, students. The button changes a fact, while the percentage is calculated again on the next render.
React · JavaScript with JSX
import { useState } from 'react';
export default function App() {
const [students, setStudents] = useState([
{ id: 1, present: true }, { id: 2, present: false }
]);
const present = students.filter(student => student.present).length;
const percent = students.length === 0 ? 0 : Math.round(present / students.length * 100);
return <main><h1>Attendance percentage</h1><p>Present: {percent}%</p>
<button onClick={() => setStudents(current => current.map(student =>
({ ...student, present: true })
))}>Mark all present</button>
</main>;
}Expected screen and behavior
The initial percentage is 50%. Mark all present changes it to 100%. An empty list follows the chosen 0% policy.Mistakes and boundaries
Adding an Effect to maintain the count creates an unnecessary extra update and another place to make mistakes. Calculate inexpensive display values during rendering. An expensive calculation may justify measurement and an optimization later, but it still does not become an independent school fact.
A selected object copied into state can become stale after its original list entry changes. Keep selectedId and look up the current record. Use stable IDs rather than positions when list ordering can change, and handle a missing ID explicitly after a student is removed.
Check your reasoning
React · Calculate a consistent attendance summary
import { useState } from 'react';
export default function App() {
const [students, setStudents] = useState([
{ id: 1, name: 'Asha', present: true },
{ id: 2, name: 'Ravi', present: false }
]);
const present = students.filter(student => student.present).length;
const absent = students.length - present;
return <main><h1>Attendance facts</h1>
<p>Present: {present}; Absent: {absent}</p>
<ul>{students.map(student => <li key={student.id}>
{student.name}: {student.present ? 'present' : 'absent'}
</li>)}</ul>
<button onClick={() => setStudents(current => current.map(student =>
student.id === 2 ? { ...student, present: true } : student
))}>Mark Ravi present</button>
</main>;
}Choose the answer that follows the data and the interaction, then read the explanation for each alternative.
Carry the idea forward
Prefer state that expresses the underlying school facts once. Calculate summaries from those facts and represent exclusive stages with a single status. Continue with preserving and resetting state to see how React associates remembered values with component identity.
Read the primary reference: React: choosing the state structure.
Reaching the recap and completing the quizzes are separate milestones.