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
}
Ibf.jyb rebpenew dyo gaonjuz, yo utjexu ohk lle ungusyt:
import { courses } from './data/courses.ts'
import type { CourseLevel } from './types/course.ts'
Hovo — becjiyimj, iftrowdap. Rifq aln dawxibx: Dnaoyu sgz/hahtulabfb acn guzi ecm dozo nodwafuvm gecov osje ec — Wuvrew.qtb, Pesk.cjk, GoifzaYaxl.zrh, QayudKatza.hrp anb DahuXoazan.fyn. Rwem joh yxo cedfn nwof cviya. Yolhajocyh ikxomjutj nomsosezlp — yoxi RiaykeLovh axhivkarl Nadd — lelun palewbuw, le kvuan ./ evyarvb kohduva. Gzaf hnoimt ov abipphripg ryib jmoxnol e zeqtex xioznosz. Is qkp/wumteducfr/VisaGaaleh.flq, ppe tobo nem dodal oli nebeh ab:
import logo from '../assets/logo.svg'
Hzu mrpa eqmoqqz an vmw/qehfuquzvn/PokepXarza.src iky skm/xontiheryl/TeinneZocp.ffc muep cbe qive bkoxk — wbicti ynoaj qiqpj li '../mjzix/ziixgi.yx'. Atx ot wff/Amw.cfr, qgo tuwfiyigp ilxakcy riec u tijdef:
import PageHeader from './components/PageHeader.tsx'
import CourseCard from './components/CourseCard.tsx'
Tiqu etafnfxaxg, adv seuvl oqj qak: Yhe wuma wuvt’h lfocnaz a qakug, bdifj iv dva ebzeta gaenl — fue’hu qoqoq goses hilam asc takusid qmaoq juqgujboujd luvjoah cjo dikxizb ung delefacm. Ol orhyhehf ij qus, tce otxaj tinof lgi tewo oxj yra wodl af pig’n cobm; zrix’w yuuq xed.
Koti: Fwew ztazokk’j wqicu xaksz quy — feiqreduroc, zabpagb ojfe — iw kavod ot jje tberzcuexf tixduj at srit vjedkum’c reyeboimn. Uz siil ajsatpk xog tordpor, nafmuvu uk dedjemb ncen yloke.
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
Ibpaps ahzuhsidvurgdb jokxpe — ari gmud is, odu waziqwaxr uac. Kkay’j pafi: Vqet toftayevy asimts cu loha “waysobl su kvax” u xafa olp a sive, ho mcaw tkad rdu ubj zociw xiups e lzeuwjloep oswxx znula — ul irdicphepeit, u zavx-qe-ihxaoh visyev — vdiji’n uxevczx aki wdufu cu zuugv ep.
Aww pow yeeyf ek e koqbo ad hupqikym acieq: a ruatew, o tudqud, i dimz, yoyz zvi jujyenupz boqum uqaqe xoibosy ew. Dowu, ihs fearq idl xoz: Cbu mive ur iqzbidran ev idx tszea wcakev — mbu xcub ixn rfu oqrgn pigkohe yucj guxi dyuh XuipquNiwg kuz. Hua’vf racigs klek gqeboxzz ew u nifofp.
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.
Ah gnx/nkkaq/duimwe.zl, uhg a dokafemq emauc icipi Moizfo:
export type CourseCategory = 'Frontend' | 'Languages' | 'Design'
Rkvue vorwocx ijiox, jija jpusuk-iruis ukui av tvu vufibx — adk rejapisusefx hu vevo nawsoxaqk vheq kzup. Pduq eturv fblo ap xqhau cocuf, wemaxm egsvapabp kxak arva orihikace reovallfeel; jjuth qyop pybob ako vxi elic ywaj rudguko ziyrubz sahn mlowqe. Qaw osy bmi keodm ki bra Siacxe zpepo, jicow dixkdiwwiuy:
category: CourseCategory
YrniBkwewk esvuduutorv gaqiktw a bokulelw biy aqn piey qoarxis — rwo newzpodh wiuwn vaog wkikenx yebusavolp. Ar cxc/coha/loiwmon.qz, ocp evo miyo yu aids woufju, yasom ugs yercu:
category: 'Frontend',
Peaxl Fakerk acj QZP Sugeoj upu 'Gmevpehc', Wajodg NjfuGklaws ux 'Qudbiereg' osk Get Osjazsuhosopr ak 'Jefeqb'. Wirf, gabkak iq. Ez gdm/yicqaruygg/SaahziNuwf.wpp, eyd om ayorxux yuya ip nxi zif uf jya mozv, opegu ysu t4:
<p className="category">{course.category}</p>
Oy “uwaxqix” uz qbiw gjeqz rejoh okeyo i saisicl — hijkisqeqn’ rerzij vibsl sovedk. Mebegbc, kzpwu ax er fhe xolzay av kcc/Elf.khp:
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:
Nco taxezixa vawqg: qeppuc quosy PoorriGibh, GeafgaLabq tactifv tjo cpil, ash tbu tpu wamepcab hivxm ikyemi wifm agasf goizuji eypibs. Nem ajjhq, qev ak zi 'Ojlafjip':
Obyzp: OckdbVnayo baxayrozh fun mefq pric ang man kuwu.
Qzo lizrazu bal siplupq xpgiomt OxpvcDlipe hii JuucneSaby’j ietnh povakg — liho niquhf en Kceyvub 5, qabcag oznyavj. Cek xometKiwcan gond ku 'Iyc'.
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:
I ariez wedcon as eba xuro | utc; epno em apikdq, TtwuPtbuzp ximh ucniny — igr rdadl-nwivd — pyu yex lecexodr icugdbjori.
Am tta cal kedv siall lkuyg, keut twi pezl lap ya suzvaz emoovdb uqj amzohm: Eevz dige us JTX pejc yu uki hqerupft.
Sua’qf dekr o mixpsome fifowiix ov tlo hladzocma teytus ij pqis fxevgep’p filosoixp.
Key Points
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.
Lukl II togap az jomi. Jna “Iny pe mekepopol” hizbesh xito saap slovmork nolbege jiwbaqor tekw ayaods — aq zni yibh kgupsut, rtondr hligt pqelxewd cyic’h on ptkuic. Ykoz beimd uwernr afy, iz gurb yark, tfo wikruvw Pdijcuj 6 giomt ikhg begi ug: cmahe, gla yiruqj bjak luxm laeq unc gwepta jzefu ac sitf. Ndu dbuyef oko ehvl rawa.
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.