ЁЯПл The SchoolтА║ЁЯУМ UIтА║ЁЯза рдзрдбрд╛ 05 тАФ State рдЖрдгрд┐ components: рд╕рддреНрдпрд╛рдЪрд╛ рдПрдХрдЪ рд╕реНрд░реЛрдд
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯза рдзрдбрд╛ 05 тАФ State рдЖрдгрд┐ components: рд╕рддреНрдпрд╛рдЪрд╛ рдПрдХрдЪ рд╕реНрд░реЛрдд

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 05 ┬╖ рдорд╛рдЧреЗ: lesson-04-javascript-events ┬╖ рдкреБрдвреЗ: lesson-06-accessibility


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ04, рдЖрдгрд┐ рддреА рдХрд▓реНрдкрдирд╛ рдЬреА automate рдХрд░рдгреНрдпрд╛рд╕рд╛рдареАрдЪ рдкреНрд░рддреНрдпреЗрдХ framework рдЕрд╕реНрддрд┐рддреНрд╡рд╛рдд рдЖрд╣реЗ: state (рдореБрдЦреНрдп рдпрд╛рджреА), components (data рдкрд╛рд╕реВрди DOM рдмрдирд╡рдгрд╛рд░реА functions), рдЖрдгрд┐ рдПрдХрдЪ рдирд┐рдпрдо тАФ UI = render(state).

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рд╣рд╛ рдЖрд╣реЗ рд╕рд╛рдкрд│рд╛. рдлрд▓рдХрд╛рд╡рд░ рдкрд╛рдЪ рд╡рд┐рджреНрдпрд╛рд░реНрдереА рджрд┐рд╕рддрд╛рдд. рдПрдХрд╛ рдирд╡реАрди рд╡рд┐рджреНрдпрд╛рд░реНрдереНрдпрд╛рдЪреЗ рдирд╛рд╡ рдиреЛрдВрджрд╡рд▓реЗ рдЬрд╛рддреЗ, рдореНрд╣рдгреВрди рддреБрдореНрд╣реА рдПрдХ card рдЬреЛрдбрддрд╛. рдкрдг footer рдореНрд╣рдгрддреЗ "5 students on the board" тАФ рддреЗрд╣реА рдмрджрд▓рд╛. рдЖрдгрд┐ filter 3B рд╡рд░ рдЖрд╣реЗ, рдореНрд╣рдгреВрди рдХрджрд╛рдЪрд┐рдд рддреЗ card рджрд┐рд╕рд╛рдпрд▓рд╛рдЪ рдирдХреЛ. рдЖрдгрд┐ рд░рд┐рдХрд╛рдореНрдпрд╛ рдпрд╛рджреАрдЪрд╛ рд╕рдВрджреЗрд╢ рдЕрдЬреВрди рд▓рдкрд▓реЗрд▓рд╛рдЪ рдЖрд╣реЗтАж рдЖрддрд╛ рддреАрди рдард┐рдХрд╛рдгрд╛рдВрдирд╛ рд╕рддреНрдпрд╛рдЪрд╛ рдПрдХ-рдПрдХ рддреБрдХрдбрд╛ рдорд╛рд╣реАрдд рдЖрд╣реЗ, рдЖрдгрд┐ рдкреБрдврдЪреНрдпрд╛ рдЖрдард╡рдбреНрдпрд╛рдд рддреНрдпрд╛рдВрдЪреНрдпрд╛рдкреИрдХреА рдПрдХ рдЕрд╕рд╣рдордд рдЕрд╕реЗрд▓.

рдпрд╛рд╡рд░ рдЙрдкрд╛рдп рдореНрд╣рдгрдЬреЗ рдПрдХрд╛рдЪ object рдордзреНрдпреЗ рдареЗрд╡рд▓реЗрд▓реА рдореБрдЦреНрдп рдпрд╛рджреА ЁЯза:

const state = { students: [], filter: "", loading: true, error: null };

рдЗрддрд░ рдХрд╛рд╣реАрд╣реА рдХрд╛рд╣реАрдЪ рд▓рдХреНрд╖рд╛рдд рдареЗрд╡рдд рдирд╛рд╣реА. рдХрд╛рд╣реАрддрд░реА рдШрдбрд▓реЗ рдХреА рддреБрдореНрд╣реА state рдмрджрд▓рддрд╛ рдЖрдгрд┐ рдПрдХрдЪ function рдмреЛрд▓рд╡рддрд╛ тАФ render() тАФ рдЬреЗ рд╕рдзреНрдпрд╛рдЪреНрдпрд╛ state рд╡рд░реВрди рд╕рдВрдкреВрд░реНрдг рдлрд▓рдХ рдХрд╛рдврддреЗ: cards, status рдУрд│, рд╕рдВрдЦреНрдпрд╛. рдлрд▓рдХ рдХрдзреАрдЪ рд╕реНрд╡рддрдГрд╢реА рдЕрд╕рд╣рдордд рд╣реЛрдК рд╢рдХрдд рдирд╛рд╣реА, рдХрд╛рд░рдг рд╕рддреНрдпрд╛рдЪрд╛ рд╕реНрд░реЛрдд рдПрдХрдЪ рдЖрд╣реЗ.

рдЖрдгрд┐ рдХрд╛рдврдгреНрдпрд╛рдЪреЗ рдХрд╛рдо components ЁЯзй рдордзреНрдпреЗ рд╡рд┐рднрд╛рдЧрд▓реЗрд▓реЗ рдЖрд╣реЗ тАФ рдЫреЛрдЯреА functions рдЬреА data рдЪрд╛ рдПрдХ рддреБрдХрдбрд╛ DOM рдЪреНрдпрд╛ рдПрдХрд╛ рддреБрдХрдбреНрдпрд╛рдд рдмрджрд▓рддрд╛рдд: StudentCard(s) рдПрдХ рд╡рд┐рджреНрдпрд╛рд░реНрдереА рдШреЗрддреЗ рдЖрдгрд┐ рдПрдХ card рдкрд░рдд рджреЗрддреЗ. рддреЗрдЪ input, рддреЗрдЪ output, рдХреЛрдгрддреЗрд╣реА рдЖрд╢реНрдЪрд░реНрдп рдирд╛рд╣реА.

UI = render(state). рд╣реЗ рдПрдХрд╛ sticky note рд╡рд░ рд▓рд┐рд╣реВрди рдареЗрд╡рд╛. Lesson 07 рдордзрд▓рд╛ рдкреНрд░рддреНрдпреЗрдХ framework рдиреЗрдордХреЗ рд╣реЗрдЪ рдХрд░рдгреНрдпрд╛рдЪрд╛ рдПрдХ рд╣реБрд╢рд╛рд░, рдЬрд▓рдж рдорд╛рд░реНрдЧ рдЖрд╣реЗ.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    ev["ЁЯСЖ event"] --> st["ЁЯза state = {students, filter, loading, error}<br/>the ONLY source of truth"]
    st --> r["ЁЯФБ render(state)"]
    r --> c1["ЁЯзй StudentCard(s) тЖТ card"]
    r --> c2["ЁЯзй status line"]
    r --> c3["ЁЯзй count in the footer"]
    c1 & c2 & c3 --> dom["ЁЯУД the DOM"]
    note["never edit the DOM outside render()"]

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг "рдХрд╛рдЙрдВрдЯрд░ 5 рдореНрд╣рдгрд╛рд▓рд╛ рдкрдг рдорд▓рд╛ 6 рджрд┐рд╕рддрд╛рдд" рд╣рд╛ UI bugs рдЪрд╛ рд╕рд░реНрд╡рд╛рдд рд╕рд╛рдорд╛рдиреНрдп рдкреНрд░рдХрд╛рд░ рдЖрд╣реЗ, рдЖрдгрд┐ рддреЛ рдПрдХрдЪ рдЧреЛрд╖реНрдЯ рдПрдХрд╛рдкреЗрдХреНрд╖рд╛ рдЬрд╛рд╕реНрдд рдард┐рдХрд╛рдгреА рд▓рдХреНрд╖рд╛рдд рдареЗрд╡рд▓реНрдпрд╛рдореБрд│реЗ рд╣реЛрддреЛ. рд╣рд╛ рдПрдХрдЪ рдирд┐рдпрдо рддреЛ рд╕рдВрдкреВрд░реНрдг рдкреНрд░рдХрд╛рд░рдЪ рдХрд╛рдвреВрди рдЯрд╛рдХрддреЛ тАФ рдЖрдгрд┐ рдкреБрдврдЪреНрдпрд╛ рдзрдбреНрдпрд╛рдд React, Vue рдХрд┐рдВрд╡рд╛ Svelte рдордзреНрдпреЗ рддреБрдореНрд╣реА рдиреЗрдордХреЗ рд╣реЗрдЪ рдорд╛рдирд╕рд┐рдХ рдЪрд┐рддреНрд░ рдШреЗрдКрди рдЬрд╛рд▓.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

ui/app.js рдЪреЗ рднрд╛рдЧ 1 рдЖрдгрд┐ 2: рд╕рд░реНрд╡рд╛рдд рд╡рд░ state, рддреНрдпрд╛рдЦрд╛рд▓реА StudentCard(s) рдЖрдгрд┐ render(). рд▓рдХреНрд╖рд╛рдд рдШреНрдпрд╛ рдХреА рдХреЛрдгрддреА cards рдХрд╛рдврд╛рдпрдЪреА рд╣реЗ рдард░рд╡рдгреНрдпрд╛рд╕рд╛рдареА render() state.filter рд╡рд╛рдЪрддреЗ рдЖрдгрд┐ status рдордЬрдХреВрд░ рд╡ footer рдордзрд▓реА рд╕рдВрдЦреНрдпрд╛ рд▓рд┐рд╣рд┐рддреЗ тАФ рддреАрди рджреГрд╢реНрдпреЗ, рдПрдХрдЪ рд╕рддреНрдп.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

python3 ui/mock_api.py
# 1) prove the rule holds: enrol a student while the filter is on 3A тАФ the count updates, the card appears only if it matches
# 2) break it on purpose: in the submit handler, replace `state.students.push(s); render();`
#    with `document.querySelector('#student-list').appendChild(StudentCard(s));`
#    then change the filter тАФ your student vanishes, and the footer count is wrong. That is the bug this lesson kills.
# 3) put it back, then add real state: a `sort` field ('name' | 'grade'), a <select> that sets it, and one line in render()
# 4) in the Console:  state   (it is inside the IIFE тАФ so expose it temporarily with window.state = state to inspect)

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

рдирд┐рдпрдо рдкрд╛рд│рд▓реНрдпрд╛рд╡рд░: state рдордзрд▓рд╛ рдкреНрд░рддреНрдпреЗрдХ рдмрджрд▓ рдПрдХрд╕рдВрдз рдлрд▓рдХ рдкреБрдиреНрд╣рд╛ рд░рдВрдЧрд╡рддреЛ. рд╣рд╛рддрд╛рдиреЗ рдмрджрд▓ рдХреЗрд▓реНрдпрд╛рд╡рд░: card DOM рдордзреНрдпреЗ рдЕрд╕рддреЗ рдкрдг state рдордзреНрдпреЗ рдирд╕рддреЗ, рдореНрд╣рдгреВрди рдкреБрдврдЪреЗ render() рддреЗ рдкреБрд╕реВрди рдЯрд╛рдХрддреЗ рдЖрдгрд┐ рд╕рдВрдЦреНрдпрд╛ рдХрдзреАрдЪ рдмрд░реЛрдмрд░ рдирд╡реНрд╣рддреА. рддреБрдордЪреНрдпрд╛ рдирд╡реАрди sort control рд▓рд╛ render() рдордзреНрдпреЗ рдиреЗрдордХреА рдПрдХ рдУрд│ рд▓рд╛рдЧрддреЗ, рдЖрдгрд┐ рдЗрддрд░ рдХреЛрдгрддрд╛рд╣реА рдмрджрд▓ рд▓рд╛рдЧрдд рдирд╛рд╣реА.

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рдПрдХрд╛ object рдиреЗ рд╕рддреНрдп рдзрд░реВрди рдареЗрд╡рд▓реЗ, рдПрдХрд╛ function рдиреЗ рддреЗ рдХрд╛рдврд▓реЗ, рдЖрдгрд┐ рдирд╡реАрди feature рдЬреЛрдбрдгреЗ рдореНрд╣рдгрдЬреЗ state рдЖрдгрд┐ render() рдмрджрд▓рдгреЗ тАФ рд╕рдВрдЦреНрдпрд╛ рджрд╛рдЦрд╡рдгрд╛рд░реА рдкреНрд░рддреНрдпреЗрдХ рдЬрд╛рдЧрд╛ рд╢реЛрдзрдд рдмрд╕рдгреЗ рдирд╡реНрд╣реЗ.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: React рдЪреЗ useState, Vue рдЪреЗ ref рдЖрдгрд┐ Svelte рдЪреЗ $state рдореНрд╣рдгрдЬреЗ diffing engine рдЬреЛрдбрд▓реЗрд▓рд╛ рд╣рд╛рдЪ рдзрдбрд╛. рдЬреНрдпрд╛ teams рдирд╛ рд╣реЗ рд╕рдордЬрддреЗ рддреНрдпрд╛ frameworks debug рдХрд░рддрд╛рдд; рдЬреНрдпрд╛рдВрдирд╛ рд╕рдордЬрдд рдирд╛рд╣реА рддреНрдпрд╛ frameworks рд╢реА рднрд╛рдВрдбрдд рдмрд╕рддрд╛рдд.

тПня╕П рдкреБрдвреЗ

рдлрд▓рдХ рдПрдХрд╕рдВрдз рдЖрд╣реЗ тАФ рдкрдг рдкреНрд░рддреНрдпреЗрдХрдЬрдг рддреЛ рд╡рд╛рдкрд░реВ рд╢рдХрддреЛ рдХрд╛? Accessibility: keyboard, labels, contrast, focus рдЖрдгрд┐ рдШреЛрд╖рдгрд╛.

git checkout lesson-06-accessibility

ЁЯза Lesson 05 тАФ State & components: one source of truth

ЁЯУН You are here: Lesson 05 of 12 ┬╖ Previous: lesson-04-javascript-events ┬╖ Next: lesson-06-accessibility


ЁЯУж What's in this branch

Lessons 01тАУ04, plus the idea every framework exists to automate: state (the master list), components (functions from data to DOM), and the one rule тАФ UI = render(state).

ЁЯзТ Explain like I'm 5

Here is the trap. The board shows five students. A new one is enrolled, so you append a card. But the footer says "5 students on the board" тАФ update that too. And the filter is on 3B, so maybe the card should not show at all. And the empty-state message is still hiddenтАж Three places now know a piece of the truth, and next week one of them will disagree.

The cure is a master list ЁЯза kept in one object:

const state = { students: [], filter: "", loading: true, error: null };

Nothing else remembers anything. When something happens you change state and call one function тАФ render() тАФ which draws the whole board from the current state: the cards, the status line, the count. The board can never disagree with itself, because there is only one source of truth.

And the drawing is split into components ЁЯзй тАФ small functions that turn one piece of data into one piece of DOM: StudentCard(s) takes a student and returns a card. Same input, same output, no surprises.

UI = render(state). Write it on a sticky note. Every framework in lesson 07 is a clever, fast way of doing exactly this.

ЁЯЧ║я╕П Diagram

flowchart LR
    ev["ЁЯСЖ event"] --> st["ЁЯза state = {students, filter, loading, error}<br/>the ONLY source of truth"]
    st --> r["ЁЯФБ render(state)"]
    r --> c1["ЁЯзй StudentCard(s) тЖТ card"]
    r --> c2["ЁЯзй status line"]
    r --> c3["ЁЯзй count in the footer"]
    c1 & c2 & c3 --> dom["ЁЯУД the DOM"]
    note["never edit the DOM outside render()"]

тЭУ What

ЁЯдФ Why

Because "the counter said 5 but I see 6" is the most common class of UI bug, and it is caused by more than one thing remembering the same fact. This one rule removes the whole category тАФ and it is the exact mental model you will carry into React, Vue or Svelte next lesson.

ЁЯФз How (in this repo)

ui/app.js sections 1 and 2: state at the top, StudentCard(s) and render() below it. Notice render() reads state.filter to decide which cards to draw and writes the status text and the footer count тАФ three views, one truth.

ЁЯзк Try it

python3 ui/mock_api.py
# 1) prove the rule holds: enrol a student while the filter is on 3A тАФ the count updates, the card appears only if it matches
# 2) break it on purpose: in the submit handler, replace `state.students.push(s); render();`
#    with `document.querySelector('#student-list').appendChild(StudentCard(s));`
#    then change the filter тАФ your student vanishes, and the footer count is wrong. That is the bug this lesson kills.
# 3) put it back, then add real state: a `sort` field ('name' | 'grade'), a <select> that sets it, and one line in render()
# 4) in the Console:  state   (it is inside the IIFE тАФ so expose it temporarily with window.state = state to inspect)

тЬЕ Verify тАФ what you should see

With the rule: every change to state repaints a consistent board. With the hand-edit: the card exists in the DOM but not in state, so the next render() erases it and the count was never right. Your new sort control needs exactly one line in render() and no other changes.

ЁЯПБ What you just proved

One object held the truth, one function drew it, and adding a feature meant touching state and render() тАФ not hunting for every place that shows a number.

тЪая╕П Common mistakes

ЁЯПн Why this matters in production: React's useState, Vue's ref and Svelte's $state are this lesson with a diffing engine attached. Teams that understand it debug frameworks; teams that do not fight them.

тПня╕П Next

The board is consistent тАФ but can everyone use it? Accessibility: keyboard, labels, contrast, focus and announcements.

git checkout lesson-06-accessibility
тЖР Previousjavascript eventsNext тЖТaccessibility

This page is the lesson's README from the lesson-05-state-components branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.