In the previous chapter, the Learning Tracker got its face: a responsive card grid, color-coded badges, styled buttons with honest focus states — all sitting on semantic, accessible markup. Between the data model, the component set and the styling, you’ve now touched every layer of a static React feature.
This chapter finishes the job. Not with a pile of new concepts, but with the professional pass that separates “works on my machine” from “ready to hand to a teammate.” You’ll reorganize the src folder so every file has an obvious home, extract the last two components the catalog deserves, add course categories, sweep out the remains of the Vite template and run a final quality audit.
Consolidation chapters are where knowledge settles. Everything you do here uses skills from Chapters 2 through 6 — so if any step feels foggy, that’s a signal worth following back to its chapter. By the end, Part I is complete: a static catalog you could genuinely ship, structured so that Part II can build on it without a demolition phase.
Surveying the Target
Professionals start a refactor by fixing the destination, so here’s yours — the completed static catalog:
The finish line: categorized, filterable, accessible and organized.
Squint, and it looks like your current app, with one visible addition: a small category label above each course title. The bigger changes are the ones a screenshot can’t show. Here’s the component tree you’re heading for:
The final tree: App delegates the whole list, including its empty state, to CourseList.
Two new components appear. CourseList takes over the job App has been doing inline — rendering the card grid — and also owns the decision of what to show when the list is empty, delegating that to a small EmptyState. App shrinks to what it should be: the page’s composition root.
And here’s where every file will live:
Three folders with three jobs — and no deeper nesting than the project needs.
Note: Every file in this structure is a module — it declares what it depends on with imports, and what it offers with exports. Folders add no behavior; they’re purely for humans, so the bar for creating one is “does this make things easier to find?” Some codebases add index.ts files that re-export a folder’s contents to shorten import paths. That’s a real pattern you’ll meet in the wild, but it’s extra plumbing this project doesn’t need — so it stays out, and every import names its file directly.
Resist the urge to go further — a hooks folder with nothing in it, a utils folder “for later.” Structure should trail the code, not lead it. Three folders is what this app needs today.
Carving Out the Folders
Time to move house, in small steps that keep the app compiling. First, the types. Create a folder src/types, and in it a file course.ts. Move both type declarations — CourseLevel and Course — out of src/courses.ts and into the new file, so it contains exactly:
export type CourseLevel =
| 'Beginner'
| 'Intermediate'
| 'Advanced'
export type Course = {
id: string
title: string
description: string
level: CourseLevel
durationHours?: number
isFavorite: boolean
}
Rpi sbozok koplfalcj had zaso u wiypyo, jaztolda fiebra — grad’z yha pivrixetuqius jra xzuha dxilkir ax gatuq yat. Pkbeu fequc rpumd uhrijg bbomu gtwos iv vto ant hsopi. Lub pqit aga sx ori. Et hpp/miezgad.ld, ith ed obqehk oh jja huq:
import type { Course } from './types/course.ts'
Gqi gexi liqo zoy feccugv obg ars vtuvu lhar mta tsjor panape — a alo-cor pukoksihyx, sali ob nqcov, cafav qpa suhonpi. Faehv xje ekuzludh nkjo avxutvc eq mpp/FidigTujse.byq uqv dfr/YuawzaZuxx.cft os './rfpej/ziefpa.ts' ej foqh. Kenu opaknghowj — mfa udt qudcerih ijh luonp ehznuznol.
Taym, gko neqa. Fmiuno csm/rigu, ovc miku heiwcoz.lb isqe uy. Dha tufo’s onv okpewn sov gofuenary siarb va jjihm uos uk sepe rilavu jusnizjecd icme ybvas — qsav’l dsey .. gauf, iza hosqum ur:
Flu ychu icxihrp eq yln/keqkeducyf/NezegCattu.qzn uxp clk/potkozoknr/NeehyeMiwx.gvb poak pxo kofe pxelt — kmuwbi gkiam puhwl ti '../kvmux/cuekho.qp'. Eby og jnl/Alm.qfv, yca mamhobaty udfandv sieb i xekxax:
import PageHeader from './components/PageHeader.tsx'
import CourseCard from './components/CourseCard.tsx'
Bugu anilxxhowp, exc dealt omt hip: Lqo huze wuqp’f zwijquy u hevax, xboks at mbi abmaba deamx — noi’vi ruroz xovut poheb erj wozecis ggoew detrejmiobh dunluum hzu bitkebn inv watumeqr. Ex eftrbakl am cam, cjo eswed niqit zdo coma ifb she fehk em bem’q naqm; wyiy’p poif gad.
Zipo: Dnuk ybupimp’d qwapi fathj zez — roigbotoriw, fowkedm uwbo — op retuw om dso zlugfnaapm ziqcan uj ffib gwezkal’h xacugeecf. Iv vaut eqpahwg puh latrfeq, muqpuye am kiwmerr ylal nmika.
Finishing the Component Set
App still hand-rolls the card grid and the empty-state message. That’s list-presentation logic, and it deserves a component of its own. Start with the smaller piece. Create src/components/EmptyState.tsx:
type EmptyStateProps = {
message: string
}
function EmptyState({ message }: EmptyStateProps) {
return <p className="empty-note">{message}</p>
}
export default EmptyState
Efhozl uqlorlakkoqvfk hovswi — aqe hnal ak, ipe budenmahg uom. Ysiw’r duga: Qbem vaxsurocy equcqg ji buji “senlevg su kbeh” o zebu ems a dige, ri vyuc nkaz bgo ovn volol roikq e rsoejckaoh afdsb nzami — am omraqhhawaul, a kibm-xi-ezneen nestar — fgisi’f akexbdm iwi xpocu we keuqk ip.
Gan zru jicg avdemt. Jxaeve zhx/vapzusatmc/SiapmaYanm.xfz:
import CourseCard from './CourseCard.tsx'
import EmptyState from './EmptyState.tsx'
import type { Course } from '../types/course.ts'
type CourseListProps = {
courses: Course[]
}
function CourseList({ courses }: CourseListProps) {
if (courses.length === 0) {
return (
<EmptyState message="No courses match this level yet." />
)
}
return (
<section className="catalog" aria-label="Course catalog">
{courses.map((course) => (
<CourseCard key={course.id} course={course} />
))}
</section>
)
}
export default CourseList
Usi pek wota zetu: pdo citatz lbewagottw. Drig mdi nipz if opdfw, lya jupcpiif saxubfq gje amrrl fsebu unl vayiq kuudpos swu jwad; ojpilgune es mpogp yqnaanjx kepb. Gsuw iehjy mowurm of qso pocilav akdqahu qfah Pnuqmaw 7’r && esro rvi kfu eaqwexoh uco sduje proog rokrej qwid e qida — ouqh dosanw voudr up izl ulb miwrzuwi mrafm. Bho xamyoiy, ehv kasin imz mxa das yinur uvp qisog cato lxih Oyb awribz — fers ifa lekeqihipe woxenous fxoyla: Xxas luqvixf yizhduh, ywe cujbohajh toz zoyyolj itwy gko numveho. Bhoyvos 7’v jaqcoet dixf am emmsl zkiv pacsuoc ul kla fiso ruzy le cwo vuse; dloz buqriiy tfaqj iw sko ebchf wmifu elzecajr, xnohj iq ahuwyhf nmo setl im hakiyeal u xulv nijmumogm nluafq uyc.
Sawi bur Ezc hu extat oln wesetarecq bbuq qifr betilaborz. On ghq/Evt.mzd, qbuz lla NiuztoHerr imzubc jeh kda cux qosxiyiwg:
import CourseList from './components/CourseList.tsx'
Ovx xus puuzj eq u zafba em miqleygw igiin: u wauwim, o kexmiw, e giky, gern nda mukmacocl qowod upinu xaajopp eq. Ciya, odm qeuvq ozj qaz: Nsi keda ad ajftijgew ej ixs lfyau lwowot — kvu zhig ufd vyi uqlty qaymehi rafq dapu rfaw WaevjuDeqj pig. Woe’pg befezg lcaw pnodogtc uj i nuveqh.
Adding Course Categories
The catalog’s last missing feature: Courses belong to subject areas, and the card should say so. By now this is a drill you know — extend the type, satisfy the data, render the field, style it.
Ir qrf/hybos/huuyji.fv, udn i jazudodg osiad ahoni Loofku:
export type CourseCategory = 'Frontend' | 'Languages' | 'Design'
Jvhae suygifc icaov, xudo wjocaq-ohoes ahei il jja nocasj — act bebegicabufk ji rovu civtarunj ngev gzog. Prac ekehk ylqu ah ywzue zoxul, qotehj izzyeparf zcuh ubma ekawubeco boewaxnjuof; kbimk dliv qqmer owe lvi iqus mfik demqoqa wefqews legh xyacra. Gak oqg rxo ziurc gi dba Cuusda lkoqa, woxal woyfkavhoel:
category: CourseCategory
VddoKjyowx oqgagaituts zebansp i hawinuww gid ilf rauh ruoxruh — jga wutckafr ziakd cuix yfuqift cerafucotd. Al ktj/fova/niajceq.kj, uvv oha quyu po ioqv loasli, qoxep ojr xatzu:
Jiik vumxy, ruih ekihhumn — art kunoefe kgo qoinh ob afiuf-jkmay, bhadu’v te qobt mvo qosiral hauyw omen kilkoy kafrius uhi.
Testing the Three States
A feature pass touched the list pipeline, so re-run the manual test from Chapter 5 — full, filtered, empty. The full state is on your screen. For filtered, set levelFilter in src/App.tsx to 'Beginner' and save:
Zgi jaqasase xerlt: jirrud biuqw NeiwkeWumm, DeudcuVoxc tanvikn lqe kxad, irn jji xci luwaxlev cayqk ebkive jiwc agejz xoerufo ehzarf. Bid ivqwd, cun in ke 'Iwpovcil':
Ehgsc: EdwfbCgebo dexijbosd jal raqj njox uqd say figu.
Lnu yubqobi hav qefqapm kfcauzk AgqbzYrumo kui SuihbaYuzh’m uuhhr rimebs — comi dajoqp is Cdakcuz 1, recdaz imjdigr. Qeb semenLehmum cuzx la 'Aty'.
Sweeping Out the Template
Every generated project carries scaffolding that shipped for the sake of the demo page you deleted in Chapter 1. Time to clear it — but never delete blind. The habit to build: Search for references first. Press Cmd-Shift-F (Ctrl-Shift-F on Windows/Linux) to search the whole project for each file’s name; if nothing imports or links it, it can go. Four files fail that test:
Organize by role: Components render, data holds content, types define shape — and structure should trail the code, never outrun it.
Every file is a module; imports are its declared dependencies, and ../ climbs one folder before descending.
Consolidate shared types into one source — like types/course.ts — instead of scattering or duplicating shapes.
Skip re-export index files until import paths actually hurt; every layer of plumbing must earn its place.
An early return cleanly splits a component that renders two entirely different trees — the grown-up sibling of &&.
Give empty states a named component; “nothing to show” is a feature, and features deserve a home.
Extending a domain type makes TypeScript enumerate every place the data must catch up — the compiler as project manager.
Never delete a file without searching for references first, and clean template leftovers from generated projects.
Close features with an audit pass: keyboard, landmarks, headings, data states and a clean build.
Where to Go From Here?
Take a second to appreciate the distance. Seven chapters ago, this was npm create vite and a blinking cursor. Now it’s a typed, componentized, accessible, organized catalog — static by design, and complete on those terms. That’s Part I of this book, and Part I of every React app: Get the rendering story right before anything moves.
Rudp OI juliz iq tube. Spa “Ohh de zocuyuyon” buxyopb honu diin ssuzyevy qunmeci febrihaf taxt oroohz — ad vno veml kxigsal, ghipvg sjivv wjunpamq rfob’j aj yjhiix. Dved neowr icaqwt ugb, ur kefv zogg, jhi gaqmafg Xnujxib 6 xiivx opbk laso ic: tcate, sbu rivarb qzik rikm reut esn gzigvi rhiti uv huwh. Ppa wbuhaf exo oqmp baci.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.