Skip to content
React Course1.0.0 beta
CSEN
React Course1.0.0 beta
CSEN
🏠 Home🧭 Where to start?🔁 Review📄 Cheat sheets📰 What's newℹ️ About the course
0. JavaScript0/22

Basics

  • 1. A quick review of the basics0/3
  • 2. State and events (useState)0/3

Hooks in depth

  • 3. Effects (useEffect)0/3
  • 4. Refs (useRef)0/3
  • 5. More complex state (useReducer)0/3
  • 6. Context0/3
  • 7. Custom hooks0/3

Forms

  • 8. Forms and React 19 Actions0/3

Performance

  • 9. Performance and rendering0/3

Patterns

  • 10. Component design patterns0/3

Ecosystem

  • 11. Where the type goes (TypeScript syntax)0/3
  • 12. TypeScript with React0/3
  • 13. Routing (React Router)0/3
  • 14. Data fetching0/3
  • 15. Application state management0/3

Project

  • 16. Final project: Kanban0/2

Summary

  • 17. Summary: the principles of React

Your account

Privacy

Lesson 16 · Project

Final project: Kanban

Put everything together into one app.

Loading lesson…

💬 Found a mistake, something unclear, or have an idea? Let me know.
← Application state managementSummary: the principles of React →

Before we start: a house is built from the foundations up

Nobody builds a house by hanging the curtains first and then looking for where to put the walls. The order is the opposite: a plan (where the rooms will be), the foundations, the shell, then the wiring and plumbing – and the furniture last. Each phase stands on the previous one, and when the foundations are done well, the upper floors hold up on their own.

An app is built the same way:

  1. The plan = types. What data exists and what does it look like? A card, a column, a priority, an action.
  2. The foundations = logic without UI. A reducer that can add, edit, move and delete a card. You can try it out without a single component.
  3. The shell = static UI. Columns and cards that only display data – no clicking yet.
  4. The wiring = interactivity. Handlers, forms, a modal, persistence.
  5. The furniture = improvements. Drag & drop, undo, performance – only once the foundation stands.

Now it's time to put everything together. Build a Kanban board – a small but real app that uses most of what you've learned. Work through the milestones and tick each one off.

Warm-up

Challenge

A task card

Warm-up

Before you tackle the whole kanban, warm up on its smallest part – a single card.

Task (the same text is in the comment at the top of the file)
CHALLENGE: A task card

The task list and the delete function are done. The TaskCard component doesn't show anything yet.

1. In TaskCard, render the task title: {task.title}
2. Make the "×" button call onDelete(task.id)

This is the smallest piece of the kanban you'll build in the project below.
🧩 Where do the things not defined here come from
CSS classes like row, stack, card, list, btn, input or muted are the course's ready-made styles in src/styles.css (row = side by side, stack = stacked, card = bordered box, muted = grey text).

Loading the interactive part…

Milestones

Architecture decisions

Before you start writing, think about these questions. Each one matches an image from the course: a notebook (state), a bank account (reducer), a loudspeaker (Context), a drawer (ref), the light in a room (effect). The reference solution picks the answers in the right-hand column – yours may differ.

QuestionReference solutionWhy
Where do the cards live?useReducer + ContextMany different actions on the same data (the bank account), and several components need it (the loudspeaker). State and dispatch in separate contexts.
Do I store the column in the card, or the cards in the columns?column in the card, a flat arrayMoving = changing one value, no copying between arrays. The card counts in the columns are simply computed.
Where is the search text?Local useState in BoardA thing in one component's pocket. In an app with a router it would belong in the address (the wish list for a room).
A card edit in progress?A local draft in the modalPapers on the desk: it goes to the "bank" only after Save, so Cancel doesn't have to undo anything.
How does undo work?A history in the reducerThe reducer is the only place where changes happen – the bank statement comes for free.
Persistence?An effect syncs cards → localStoragelocalStorage is the outside world (the light in the room); the reducer's lazy initialization loads it on start.
✨ How to proceed
  1. Start with the types and the reducer – no UI. You can try the reducer out in the console.
  2. Then the static UI with hard-wired data. Interactivity only after that.
  3. Split the code into files in the folder src/lessons/16-project/challenges/kanban/.
  4. Run npm run typecheck as you go.
  5. When you're done, compare your solution with the reference – and feel free to invent your own improvements.
❓ How do I split the code into several files?

Mark whatever should be visible in another file with export. In the other file you take it with import, using a path relative to the current file (without the extension):

// kanban/types.ts
export type ColumnId = 'todo' | 'doing' | 'done'
export interface Card { id: string; title: string; column: ColumnId }
// kanban/boardReducer.ts
import type { Card } from './types' // "import type" = type only, it disappears after compilation
export function boardReducer(…) { … }
// Kanban.tsx (one folder up)
import { boardReducer } from './kanban/boardReducer'

A named export (export function X) is imported in curly braces under the same name. export default can appear only once per file and is imported without braces under any name. That's how the challenge files imported by the lesson work. One component per file is a good rule; the types and the reducer get their own files.

❓ How does native drag & drop work (the bonus milestone)?

The browser can handle dragging by itself; you just need to tell it three things:

// 1) WHAT is dragged: the element needs draggable and "packs" its data at the start
<li draggable onDragStart={(e) => e.dataTransfer.setData('text/plain', card.id)}>
// 2) WHERE it may be dropped: without preventDefault the browser forbids the drop
<section onDragOver={(e) => e.preventDefault()}
// 3) WHAT happens on drop: unpack the data and send an action
onDrop={(e) => {
const id = e.dataTransfer.getData('text/plain')
dispatch({ type: 'card_moved', id, column: 'done' })
}}>

dataTransfer is a "box" that travels with the dragged element. It holds only text, which is why you put the card's ID in it, not the whole object. The reducer then performs the move just as it does for the ← → buttons; only who sends the action changes.

Challenge

A Kanban board

Hard Bonus challenge

Try the reference solution in the "Reference solution" tab – it saves to localStorage, so it survives a reload.

🔒 Bonus challenge

Medium and hard challenges are a bonus for signed-in readers. Signing up is free – just an e-mail, no password.

How we handle your data

Where to next

  • Next.js or React Router (framework mode) – Server Components, Server Actions, SSR and file-based routing.
  • Testing – Vitest + React Testing Library; reducers and hooks are the easiest to test.
  • Accessibility – React Aria or Radix for components that work with a keyboard and a screen reader.
  • React Compiler – turn it on in a project and watch how many manual memos you can delete.
  • react.dev – the official documentation is excellent; read the "Learn" and "Escape Hatches" sections in full.

Summary

  • An app is built from the foundations up: types (the plan) → reducer (the foundations) → static UI (the shell) → interactivity (the wiring).
  • Put each piece of state where it belongs: reducer/Context, local state, the URL, localStorage.
  • Derive whatever can be computed; effects only for external systems.
  • Congratulations – you've finished the whole course! 🎉