Appearance
React.js Mastery
Architect, test, and deploy production-grade Single-Page Applications (SPAs) using modern React, Hooks, React Router, Zustand, React Hook Form, and Vitest.
14Weeks
5Stages
65+Lessons
9Projects
3Prerequisites
Download Official 2026 Curriculum PDF
Programme Overview
Manual DOM manipulation scales poorly once an application grows to dozens of interactive views, modals, filters, authenticated routes, and shared client states. In Course 4 of the Tech Minds Web & Full-Stack Engineering sequence, you transition from imperative DOM scripting to declarative component engineering—mastering the exact React ecosystem used by top product startups and remote engineering teams across Nigeria, Europe, and North America.
| Attribute | Details |
|---|---|
| Course Position | Course 4 of 5 · Web & Full-Stack Engineering Track (Frontend Specialisation) |
| Duration | 14 Weeks · 5 Progressive Stages · 65+ Mentor-Led Lessons |
| Prerequisites | HTML Mastery + CSS Mastery + JavaScript Fundamentals |
| Delivery Format | On-Campus (G5 Plaza, Sabon-Gari Road, Bwari, Abuja) & Live Interactive Online |
| Tech Stack | React 19, Vite, React Router, Zustand, Immer, React Hook Form, Vitest, React Testing Library, Vercel |
| Portfolio Output | 9 Weekly React Projects + 1 Tested, Production-Deployed Multi-Route React SPA Capstone |
IMPORTANT
You must be comfortable with ES6 arrow functions, array .map() / .filter() / .reduce(), object destructuring, async/await, and ES Modules (import/export) before starting Week 1. Review JavaScript Fundamentals if any of those feel rusty.
Curriculum Rationale
Why React?
React powers the vast majority of modern frontend job openings globally and across African fintech, logistics, SaaS, and AI companies. More importantly, React's mental model—UI as a pure function of state (UI = f(state)), composable components, and unidirectional data flow—transfers effortlessly to React Native mobile development, Next.js full-stack apps, and modern UI engineering as a whole.
Why Zustand over Redux?
Legacy courses spend three weeks drowning beginners in Redux boilerplate (action types, action creators, reducers, store slices, providers, and thunks) before students understand when global state is even needed. In 2026 production teams, local useState, useReducer, and useContext handle 80% of component state, while Zustand delivers fast, ergonomic, hook-based global stores (complete with localStorage persistence middleware) in a fraction of the code—without unnecessary provider wrappers or wasted re-renders.
Why include automated testing?
Self-taught React developers almost universally skip testing, which immediately disqualifies them during mid-level and international technical interviews. In Stage 5, you write real user-centric unit and integration tests using Vitest and React Testing Library (render, screen.getByRole, userEvent, and waitFor). Testing accessibility roles rather than implementation details makes your components both resilient and accessible by default.
What comes after this course?
Graduating from React.js Mastery positions you for junior-to-mid Frontend Engineer roles and unlocks full-stack, cloud, and AI-native engineering:
- Full-Stack Completion: Pair your React SPA skills with Node.js & Backend Development so you can build, secure, and connect your own Express + PostgreSQL/MongoDB APIs end-to-end.
- AI Product Engineering: Advance to Agentic Software Engineering & MCP to build streaming AI chat interfaces, tool-calling dashboards, and agent workflows.
- Production Infrastructure: Explore Cloud Computing & DevOps to containerise full-stack apps with Docker and automate CI/CD pipelines with GitHub Actions.
Complete 14-Week Syllabus & Code Reference
Stage 1: React Foundations (Weeks 1–3)
Stage 1 replaces imperative DOM mutations with declarative JSX components, typed/default props, reactive useState updates, controlled form inputs, and shared parent state.
| Week | Core Topics & Lab Focus | Weekly Deliverable |
|---|---|---|
| Week 1 | Why component-driven UIs win; scaffolding with Vite (npm create vite@latest my-app -- --template react); project anatomy (index.html, main.jsx, App.jsx); JSX rules (className, htmlFor, single root / <>...</> Fragments, JavaScript expressions in {}); composing functional components. | Project 1: Developer Intro & Portfolio Card — Modular profile card composed from Avatar, Bio, SkillBadge, and SocialLinks components. |
| Week 2 | Passing data top-down via Props; prop destructuring and default parameter values; children prop for wrapper layouts; prop immutability (why components never mutate their own props); styling patterns (CSS Modules & utility classes). | Project 2: Reusable Product Catalogue Cards — Renders varied e-commerce items, discount ribbons, and stock badges cleanly via props. |
| Week 3 | Reactive state with useState; state snapshots & functional updater form setCount((prev) => prev + 1); immutable array/object state updates; controlled <input> and <select> elements; lifting state up to a common parent and passing event callbacks down. | Project 3: Interactive Shopping Cart — Add/remove items, increment quantities immutably, apply promo codes, and compute live Naira subtotals. |
Annotated Reference: Reusable Props, Immutable useState & Lifting State Up
jsx
// src/components/CartItemRow.jsx
export function CartItemRow({ item, onIncrement, onDecrement, onRemove }) {
const lineTotal = item.priceNgn * item.qty;
return (
<li className="cart-row">
<div>
<strong>{item.name}</strong>
<span> — ₦{item.priceNgn.toLocaleString('en-NG')}</span>
</div>
<div className="cart-controls">
<button type="button" onClick={() => onDecrement(item.id)} aria-label={`Decrease ${item.name}`}>
−
</button>
<span>{item.qty}</span>
<button type="button" onClick={() => onIncrement(item.id)} aria-label={`Increase ${item.name}`}>
+
</button>
<strong>₦{lineTotal.toLocaleString('en-NG')}</strong>
<button type="button" onClick={() => onRemove(item.id)}>
Remove
</button>
</div>
</li>
);
}jsx
// src/components/ShoppingCart.jsx — Parent owns state and passes callbacks down
import { useState } from 'react';
import { CartItemRow } from './CartItemRow';
const INITIAL_ITEMS = [
{ id: 'item-1', name: 'USB-C Mechanical Keyboard', priceNgn: 68000, qty: 1 },
{ id: 'item-2', name: 'Wireless Ergonomic Mouse', priceNgn: 34000, qty: 2 }
];
export default function ShoppingCart() {
const [items, setItems] = useState(INITIAL_ITEMS);
const handleIncrement = (id) => {
setItems((prev) =>
prev.map((item) => (item.id === id ? { ...item, qty: item.qty + 1 } : item))
);
};
const handleDecrement = (id) => {
setItems((prev) =>
prev
.map((item) => (item.id === id ? { ...item, qty: item.qty - 1 } : item))
.filter((item) => item.qty > 0)
);
};
const handleRemove = (id) => {
setItems((prev) => prev.filter((item) => item.id !== id));
};
// Derived state: calculate directly during render rather than syncing a second useState!
const subtotalNgn = items.reduce((sum, item) => sum + item.priceNgn * item.qty, 0);
return (
<section aria-labelledby="cart-heading">
<h2 id="cart-heading">Your Cart ({items.length} products)</h2>
{items.length === 0 ? (
<p>Your shopping cart is empty.</p>
) : (
<ul>
{items.map((item) => (
<CartItemRow
key={item.id}
item={item}
onIncrement={handleIncrement}
onDecrement={handleDecrement}
onRemove={handleRemove}
/>
))}
</ul>
)}
<p className="cart-subtotal">Subtotal: ₦{subtotalNgn.toLocaleString('en-NG')}</p>
</section>
);
}Stage 2: Core Hooks & Component Patterns (Weeks 4–6)
Stage 2 explores synchronising components with external systems (useEffect), rendering dynamic collections with stable key props, managing DOM focus/refs with useRef, and eliminating prop drilling with useContext.
| Week | Core Topics & Lab Focus | Weekly Deliverable |
|---|---|---|
| Week 4 | Synchronising side effects with useEffect; dependency array rules (every reactive value read inside must be declared); cleanup functions (clearing timers, unsubscribing listeners, cancelling stale fetch calls via AbortController); explicit status states ('idle', 'loading', 'success', 'error'). | Project 4: Live GitHub Profile & Repo Viewer — Debounced search with AbortController race-condition protection, loading skeletons, and error recovery. |
| Week 5 | Rendering lists and why array index key props break stateful children; stable entity IDs; conditional rendering idioms (? :, && pitfalls with 0, early guard returns); useRef for direct DOM focus management and mutable values that shouldn't trigger re-renders. | Project 5: Filterable Kanban Task Board — Priority/status filters, auto-focused quick-add input via useRef, and accessible empty-state illustrations. |
| Week 6 | When prop drilling hurts maintainability; creating a Context (createContext), providing values (<ThemeContext.Provider>), and consuming safely via a custom wrapper hook (useTheme); separating UI theme/auth context from high-frequency input state. | Project 6: Global Dark Mode & Currency Switcher — App-wide light/dark theme and NGN/USD preference synced via Context and document.documentElement. |
Annotated Reference: Race-Safe Data Fetching in useEffect with AbortController
jsx
import { useEffect, useState } from 'react';
export function GitHubProfileViewer({ username }) {
const [profile, setProfile] = useState(null);
const [status, setStatus] = useState('idle'); // 'idle' | 'loading' | 'success' | 'error'
const [errorMessage, setErrorMessage] = useState('');
useEffect(() => {
const cleanUser = username.trim();
if (!cleanUser) return;
const controller = new AbortController();
setStatus('loading');
setErrorMessage('');
async function loadUser() {
try {
const res = await fetch(`https://api.github.com/users/${encodeURIComponent(cleanUser)}`, {
signal: controller.signal
});
if (!res.ok) {
throw new Error(res.status === 404 ? `User "@${cleanUser}" not found.` : 'GitHub API error.');
}
const data = await res.json();
setProfile(data);
setStatus('success');
} catch (err) {
// Ignore abort errors triggered when username changes rapidly or component unmounts
if (err.name === 'AbortError') return;
setErrorMessage(err.message);
setStatus('error');
}
}
loadUser();
// Cleanup cancels in-flight HTTP request before the next effect runs
return () => controller.abort();
}, [username]);
if (status === 'loading') return <p role="status">Loading @{username}...</p>;
if (status === 'error') return <p role="alert">{errorMessage}</p>;
if (!profile) return <p>Search a GitHub username above to view their profile.</p>;
return (
<article className="gh-card">
<img src={profile.avatar_url} alt={`${profile.login} avatar`} width="80" height="80" />
<h3>{profile.name ?? profile.login}</h3>
<p>{profile.bio ?? 'No bio available.'}</p>
<p>{profile.followers} followers · {profile.public_repos} public repos</p>
</article>
);
}Stage 3: React Router & Multi-View Navigation (Weeks 7–8)
Real web applications feature shareable URLs, nested layouts, URL-driven search filters, browser back/forward history, authentication guards, and graceful 404 pages. Stage 3 teaches React Router end to end.
| Week | Core Topics & Lab Focus | Weekly Deliverable |
|---|---|---|
| Week 7 | Client-side vs full-page server navigation; configuring <BrowserRouter>, <Routes>, and <Route>; declarative navigation with <Link> and active-styled <NavLink>; dynamic URL segments (path="/posts/:slug") read with useParams(); programmatic redirects via useNavigate(). | Project 7: 4-Page Technical Blog SPA — Home, Category Archive, Dynamic Article Reader (:slug), About page, and wildcard * 404 Not Found view. |
| Week 8 | Shared shell layouts using nested routes and <Outlet />; syncing filters, pagination, and search queries directly into the URL bar with useSearchParams(); building <ProtectedRoute> wrappers that redirect unauthenticated visitors while preserving their intended destination (location.state). | Project 8: Protected Admin & Analytics Dashboard — Nested sidebar layout (<Outlet />), URL-synced filter bar, simulated login gate, and role-protected settings route. |
Annotated Reference: Nested Layouts (<Outlet />) & <ProtectedRoute> Pattern
jsx
// src/routes/ProtectedRoute.jsx
import { Navigate, Outlet, useLocation } from 'react-router-dom';
export function ProtectedRoute({ isAuthenticated }) {
const location = useLocation();
if (!isAuthenticated) {
// Redirect to /login while remembering where the user originally tried to go
return <Navigate to="/login" replace state={{ from: location.pathname }} />;
}
return <Outlet />;
}jsx
// src/App.jsx
import { BrowserRouter, Routes, Route, NavLink, Outlet } from 'react-router-dom';
import { ProtectedRoute } from './routes/ProtectedRoute';
function AppShell() {
return (
<div className="app-layout">
<header>
<nav aria-label="Main navigation">
<NavLink to="/">Home</NavLink>
<NavLink to="/articles">Articles</NavLink>
<NavLink to="/dashboard">Dashboard</NavLink>
</nav>
</header>
<main>
<Outlet />
</main>
</div>
);
}
export default function App({ currentUser }) {
return (
<BrowserRouter>
<Routes>
<Route element={<AppShell />}>
<Route index element={<h1>Welcome to Tech Minds Hub</h1>} />
<Route path="articles/:slug" element={<div>Dynamic Article View</div>} />
{/* Nested Protected Routes */}
<Route element={<ProtectedRoute isAuthenticated={Boolean(currentUser)} />}>
<Route path="dashboard" element={<div>Overview Analytics</div>} />
<Route path="dashboard/settings" element={<div>Account Settings</div>} />
</Route>
<Route path="*" element={<h1>404 — Page Not Found</h1>} />
</Route>
</Routes>
</BrowserRouter>
);
}Stage 4: Scalable State Management & Forms (Weeks 9–11)
Stage 4 equips you with predictable state transitions (useReducer + Immer), zero-boilerplate global stores (Zustand), and performant, accessible form validation (React Hook Form).
| Week | Core Topics & Lab Focus | Weekly Deliverable |
|---|---|---|
| Week 9 | Refactoring tangled useState calls into useReducer(reducer, initialState); action payload conventions ({ type: 'cart/itemAdded', payload }); simplifying deep nested state updates cleanly with Immer (useImmerReducer). | Project 9a: useReducer Checkout & Coupon Engine — Deterministic state transitions for items, shipping tiers, discount rules, and order reset. |
| Week 10 | Global client state with Zustand (create); selecting narrow slices (useNotesStore((s) => s.notes)) to prevent unnecessary re-renders; async store actions; persisting state automatically to localStorage via zustand/middleware (persist). | Project 9b: Persistent Zustand Markdown Notes App — Tagging, pinning, instant full-text search, and automatic offline localStorage persistence. |
| Week 11 | Why uncontrolled-under-the-hood forms outperform naive useState per keystroke; React Hook Form (useForm, register, handleSubmit, watch, reset, formState: { errors, isSubmitting }); inline accessible validation messages (aria-invalid), regex/custom validators, and multi-step wizard state. | Project 9: Multi-Step Validated Cohort Registration Form — 3-step onboarding wizard with field-level validation, step review summary, and async submission feedback. |
Annotated Reference: Persistent Global Store with Zustand (persist Middleware)
js
// src/store/useNotesStore.js
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export const useNotesStore = create(
persist(
(set, get) => ({
notes: [],
searchQuery: '',
setSearchQuery: (query) => set({ searchQuery: query }),
addNote: ({ title, body, tag = 'general' }) => {
const newNote = {
id: crypto.randomUUID(),
title: title.trim(),
body: body.trim(),
tag,
pinned: false,
createdAt: new Date().toISOString()
};
set((state) => ({ notes: [newNote, ...state.notes] }));
},
togglePin: (id) =>
set((state) => ({
notes: state.notes.map((n) => (n.id === id ? { ...n, pinned: !n.pinned } : n))
})),
deleteNote: (id) =>
set((state) => ({
notes: state.notes.filter((n) => n.id !== id)
})),
filteredNotes: () => {
const { notes, searchQuery } = get();
const q = searchQuery.trim().toLowerCase();
if (!q) return notes;
return notes.filter(
(n) => n.title.toLowerCase().includes(q) || n.body.toLowerCase().includes(q)
);
}
}),
{
name: 'techminds-notes-storage' // Automatically hydrated from & saved to localStorage
}
)
);Annotated Reference: Accessible Validation with React Hook Form
jsx
// src/components/RegistrationForm.jsx
import { useForm } from 'react-hook-form';
export function RegistrationForm({ onSubmitSuccess }) {
const {
register,
handleSubmit,
reset,
formState: { errors, isSubmitting }
} = useForm({
defaultValues: { fullName: '', email: '', track: 'react' }
});
const onSubmit = async (formData) => {
await new Promise((resolve) => setTimeout(resolve, 600));
onSubmitSuccess(formData);
reset();
};
return (
<form onSubmit={handleSubmit(onSubmit)} noValidate>
<div>
<label htmlFor="fullName">Full Name</label>
<input
id="fullName"
type="text"
aria-invalid={errors.fullName ? 'true' : 'false'}
{...register('fullName', {
required: 'Full name is required.',
minLength: { value: 3, message: 'Enter at least 3 characters.' }
})}
/>
{errors.fullName && <p role="alert">{errors.fullName.message}</p>}
</div>
<div>
<label htmlFor="email">Email Address</label>
<input
id="email"
type="email"
aria-invalid={errors.email ? 'true' : 'false'}
{...register('email', {
required: 'Email address is required.',
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: 'Enter a valid email address.'
}
})}
/>
{errors.email && <p role="alert">{errors.email.message}</p>}
</div>
<div>
<label htmlFor="track">Preferred Specialisation</label>
<select id="track" {...register('track', { required: true })}>
<option value="react">React.js Frontend Engineering</option>
<option value="node">Node.js Backend Engineering</option>
<option value="fullstack">Full-Stack Web Engineering</option>
</select>
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting Application...' : 'Complete Registration'}
</button>
</form>
);
}Stage 5: Performance, Custom Hooks, Testing & Capstone (Weeks 12–14)
Stage 5 elevates your code from "it works on my laptop" to senior-reviewed production quality: profiling renders, extracting reusable custom hooks, writing automated Vitest + React Testing Library suites, and shipping a live SPA to Vercel.
| Week | Core Topics & Lab Focus | Weekly Deliverable |
|---|---|---|
| Week 12 | How React renders and commits; profiling with React DevTools; when (and when not) to use useMemo, useCallback, and React.memo; code-splitting routes with React.lazy and <Suspense>; extracting clean Custom Hooks (useFetch, useLocalStorage, useDebounce). | Custom Hooks & Performance Lab — Reusable hooks/ library + route-level code splitting benchmarked in React DevTools Profiler. |
| Week 13 | Automated frontend testing philosophy: testing user-visible behaviour rather than internal state; configuring Vitest + jsdom + React Testing Library (@testing-library/react, @testing-library/user-event, @testing-library/jest-dom); querying by accessible roles (screen.getByRole, getByLabelText), simulating clicks/typing (userEvent.setup()), and asserting async updates (findByText, waitFor). | Component & Form Test Suite — Automated Vitest suite verifying shopping cart calculations, React Hook Form validation errors, and async API states. |
| Week 14 | Capstone architecture review, environment variables (import.meta.env.VITE_*), SPA rewrite rules (vercel.json), Lighthouse performance/accessibility audit, automated test pass in GitHub, Vercel production deployment, and live cohort demo. | Final Deployed React SPA Capstone (choose 1 of 3 tracks below). |
Annotated Reference: Reusable Custom Hook (useDebounce) & Vitest + React Testing Library Test
js
// src/hooks/useDebounce.js
import { useEffect, useState } from 'react';
export function useDebounce(value, delayMs = 350) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delayMs);
return () => clearTimeout(timer);
}, [value, delayMs]);
return debouncedValue;
}jsx
// src/components/__tests__/RegistrationForm.test.jsx
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, expect, it, vi } from 'vitest';
import { RegistrationForm } from '../RegistrationForm';
describe('RegistrationForm', () => {
it('displays accessible validation errors when submitted empty', async () => {
const user = userEvent.setup();
render(<RegistrationForm onSubmitSuccess={vi.fn()} />);
await user.click(screen.getByRole('button', { name: /complete registration/i }));
expect(await screen.findByText(/full name is required/i)).toBeInTheDocument();
expect(screen.getByText(/email address is required/i)).toBeInTheDocument();
});
it('submits valid form payload and resets inputs', async () => {
const user = userEvent.setup();
const handleSuccess = vi.fn();
render(<RegistrationForm onSubmitSuccess={handleSuccess} />);
await user.type(screen.getByLabelText(/full name/i), 'Adaeze Okafor');
await user.type(screen.getByLabelText(/email address/i), 'adaeze@example.com');
await user.click(screen.getByRole('button', { name: /complete registration/i }));
await waitFor(() => {
expect(handleSuccess).toHaveBeenCalledWith({
fullName: 'Adaeze Okafor',
email: 'adaeze@example.com',
track: 'react'
});
});
});
});Week 14 Capstone Project Options (Deployed to Vercel)
Every graduate architects, tests, documents, and deploys one complete multi-route Single-Page Application on Vercel:
- Tech Job Board & Applicant Tracker SPA — Multi-route React Router layout (
/listings,/jobs/:iddetail,/savedbookmarked roles,/post-jobprotected employer form), URL-synced role/location/remote filters (useSearchParams), Zustand saved-jobs store, React Hook Form job submission, and Vitest test suite. - Modern E-Commerce Storefront & Checkout SPA — Product discovery grid with debounced search & category/price filters, dynamic product detail pages, persistent Zustand cart drawer, multi-step validated React Hook Form checkout flow, order confirmation receipt, and component tests.
- Developer Social Bookmarking & Resource Hub — Curated collection manager with tag filtering, custom
useDebouncesearch, upvote/bookmark persistence via Zustand, protected collection creation & edit routes, dark/light theme Context, and automated Vitest + RTL test coverage.
Assessment & Grading Rubric
| Assessment Component | Weight | Evaluation Criteria |
|---|---|---|
| Weekly Projects (x9) | 40% | Clean component boundaries, proper key usage, zero React console warnings, immutable state updates, and responsive UI across mobile and desktop. |
| Stage Assessments (x5) | 30% | Practical coding & refactoring challenges covering Hooks (useState/useEffect cleanup), React Router nested layouts, Zustand stores, and React Hook Form validation. |
| Capstone React SPA | 30% | Multi-route React Router architecture, Zustand + React Hook Form integration, custom hooks, passing Vitest + React Testing Library suite, live Vercel deployment, and code defence. |
Learning Outcomes
Upon completing React.js Mastery, you will be able to:
- Scaffold, structure, and bundle modern React applications rapidly with Vite and idiomatic functional components.
- Manage local, shared, and side-effect lifecycles cleanly with
useState,useEffect(includingAbortControllercleanup),useRef,useContext, anduseReducer. - Build multi-page client-side navigation with React Router (
BrowserRouter, nested<Outlet />shells,useParams,useSearchParams, and<ProtectedRoute>guards). - Implement scalable global state with Zustand (
persistmiddleware) and high-performance, accessible forms with React Hook Form. - Extract reusable Custom Hooks, optimize expensive renders responsibly, write automated tests with Vitest + React Testing Library, and ship SPAs continuously to Vercel.
Tools & Learning Resources
| Tool / Platform | Role in This Course |
|---|---|
| Vite + React 19 | Instant HMR development server and optimised Rollup production bundler |
| React Developer Tools | Chrome/Edge browser extension for inspecting Component trees, Props, Hooks, and render timings |
| React Router | Declarative client-side routing, nested layouts, URL search params, and route protection |
| Zustand & Immer | Minimalist hook-based global store with localStorage persistence and ergonomic immutable updates |
| React Hook Form | Performant, accessible form state and validation library |
| Vitest & React Testing Library | Fast Vite-native test runner and user-centric DOM testing utilities |
| Vercel & GitHub | Git-connected continuous deployment with automatic preview URLs on every pull request |
Next Steps in Your Track
- Become Full-Stack: 5. Node.js & Backend Development (15 Weeks) — Build the Express, PostgreSQL/Prisma, MongoDB, and JWT authentication APIs that power your React frontends.
- Build AI Products: Agentic Software Engineering & MCP (16 Weeks) — Combine React interfaces with streaming LLM APIs, RAG, and Model Context Protocol servers.
- Master Deployment & Cloud: Cloud Computing & DevOps (12 Weeks) — Docker, AWS/Azure, CI/CD pipelines, and Terraform.
