Transfer the reducer pattern
A notification model has records with id and read fields. Write reduceInbox(state, action) supporting markRead by id and clearRead. markRead returns a new list with the chosen record read; a missing id changes no record values. clearRead removes read records. Unknown actions throw an error. The input state is an array of already-valid records.
Write checks for unchanged input, repeated markRead, a missing id, empty input, and clearing an all-read inbox. Keep printing, storage, and network requests outside the reducer. Decide which unchanged records may remain shared.
A reducer is not tied to a particular framework. The useful habit is to make state transitions visible and testable. Spend this block implementing the model, then explain how a controller could translate a button click into an action without putting DOM code into the reducer.
Predict and trace
Write the output before running. Follow the values through each call; if your prediction differs, identify the first step that surprised you.
const before = [{ id: "a", read: false }];
const after = before.map(item => ({ ...item, read: true }));
console.log(before[0].read);
console.log(after[0].read);Check your prediction
false
trueCopying the changed record preserves the earlier snapshot.
Acceptance cases before you code
Write a small test plan before implementing reducer preservation. These cases extend the brief; they are not a second worked solution. Predict the expected behavior independently, then test through the public function. A good ordinary example and a good boundary example should catch different possible mistakes.
| Case to try | What your result should demonstrate |
|---|---|
| Mark a matching record read | New state has the updated flag and old state does not |
| Clear read items | Retain unread records and unrelated state |
| Unknown id | Apply the documented no-match policy without editing another record |
After the core task passes, deliberately break one rule and identify the case that notices it. Restore the implementation and explain the failure in terms of the contract, not the number of assertions. If a case requires a concept you have not recalled, revisit the relevant lesson before opening the solution.
Build it yourself
Checkpoint: Notifications: practise
Implement reduceInbox. Mark a read, clear read items, and print the original first flag and remaining id.
const inbox = [
{ id: "a", read: false },
{ id: "b", read: false }
];Hint 1: the idea
One action changes a record; the other changes membership.
Hint 2: a first step
Use map for markRead and filter for clearRead; reject the default case.
Show one solution
function reduceInbox(state, action) {
switch (action.type) {
case "markRead":
return state.map(item =>
item.id === action.id ? { ...item, read: true } : item
);
case "clearRead":
return state.filter(item => !item.read);
default:
throw new Error("Unknown action");
}
}
const inbox = [
{ id: "a", read: false },
{ id: "b", read: false }
];
const marked = reduceInbox(inbox, { type: "markRead", id: "a" });
const remaining = reduceInbox(marked, { type: "clearRead" });
if (inbox[0].read !== false) {
throw new Error("input changed");
}
console.log(inbox[0].read);
console.log(remaining[0].id);false
bWrite the remaining cases from the brief. If you persist the inbox, design a decoder for restored records instead of trusting parsed JSON.
Test a changed requirement
Write the remaining cases from the brief. If you persist the inbox, design a decoder for restored records instead of trusting parsed JSON.
Explain before moving on
Which action is safe to repeat? What tests would expose mutation and an accidental removal of unread records?
Completion is saved in this browser only. It does not measure mastery.