React Fundamentals for Next.js: Complete Beginner's Guide
1. What Are React Fundamentals?
React fundamentals are the core building blocks of every React application — and therefore every Next.js application. Before you can master Server Components, App Router, or Server Actions, you must understand how React thinks about UI:
- Components — Reusable pieces of UI
- Props — Data passed into components
- State — Data that changes over time
- Conditional Rendering — Showing different UI based on conditions
- Lists — Rendering arrays of data
- Events — Responding to user interactions
Next.js is a React framework. It adds routing, rendering, and API capabilities on top of React. If React is the engine, Next.js is the car — but you still need to know how the engine works.
2. Why Learn React Before Next.js?
Next.js 15 with the App Router introduces powerful concepts like Server Components and Server Actions. But here's the truth:
Every Server Component eventually renders React elements. Every interactive UI falls back to Client Components. React is always underneath.
Without solid React fundamentals, you'll struggle with:
- Understanding when to use
'use client' - Building reusable UI components
- Managing form state and user input
- Debugging why your UI isn't updating
This module covers only what you need for modern Next.js development. No class components. No legacy patterns. Just the React that matters today.
3. Basic Syntax: JSX
JSX (JavaScript XML) lets you write HTML-like code inside JavaScript:
1// This is JSX — it looks like HTML but it's JavaScript 2export default function Greeting() { 3 return <h1>Hello, Tech3Space!</h1>; 4}
JSX Rules:
- Must return a single parent element (or use
<>fragments) - Use
classNameinstead ofclass - Use
htmlForinstead offor - JavaScript expressions go inside
{}
1export default function UserCard() { 2 const name = "Alice"; 3 const isOnline = true; 4 5 return ( 6 <div className="p-4 border rounded-lg"> 7 <h2 className="text-xl font-bold">{name}</h2> 8 <p>{isOnline ? "🟢 Online" : "🔴 Offline"}</p> 9 </div> 10 ); 11}
4. Components
A component is a function that returns JSX. Components let you split your UI into independent, reusable pieces.
Simple Example
1// components/ui/Button.tsx 2interface ButtonProps { 3 label: string; 4 onClick: () => void; 5} 6 7export function Button({ label, onClick }: ButtonProps) { 8 return ( 9 <button 10 onClick={onClick} 11 className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700" 12 > 13 {label} 14 </button> 15 ); 16}
Next.js Example
In Next.js App Router, most files are Server Components by default — they can be async and fetch data directly:
1// app/courses/page.tsx — Server Component 2import { CourseCard } from "@/components/courses/CourseCard"; 3 4export default async function CoursesPage() { 5 const courses = await fetch("https://api.example.com/courses").then((r) => 6 r.json() 7 ); 8 9 return ( 10 <main className="max-w-6xl mx-auto px-4 py-12"> 11 <h1 className="text-3xl font-bold mb-8">All Courses</h1> 12 <div className="grid grid-cols-1 md:grid-cols-3 gap-6"> 13 {courses.map((course) => ( 14 <CourseCard key={course.id} course={course} /> 15 ))} 16 </div> 17 </main> 18 ); 19}
5. Props
Props (properties) are how you pass data from parent to child components. They are read-only — a child cannot modify its own props.
Basic Syntax
1interface GreetingProps { 2 name: string; 3 role?: string; // Optional prop 4} 5 6export function Greeting({ name, role = "Student" }: GreetingProps) { 7 return ( 8 <div className="p-4"> 9 <h2>Hello, {name}!</h2> 10 <p className="text-gray-500">Role: {role}</p> 11 </div> 12 ); 13} 14 15// Usage 16<Greeting name="Alice" role="Instructor" /> 17<Greeting name="Bob" /> // role defaults to "Student"
Next.js Example: Passing Data to Components
1// components/courses/CourseCard.tsx 2import Image from "next/image"; 3import Link from "next/link"; 4 5interface Course { 6 id: string; 7 slug: string; 8 title: string; 9 description: string; 10 thumbnail: string; 11 level: "Beginner" | "Intermediate" | "Advanced"; 12} 13 14interface CourseCardProps { 15 course: Course; 16} 17 18export function CourseCard({ course }: CourseCardProps) { 19 return ( 20 <Link 21 href={`/courses/${course.slug}`} 22 className="block border rounded-xl overflow-hidden hover:shadow-lg transition" 23 > 24 <div className="relative h-48"> 25 <Image 26 src={course.thumbnail} 27 alt={course.title} 28 fill 29 className="object-cover" 30 /> 31 </div> 32 <div className="p-5"> 33 <span className="text-xs font-medium text-blue-600 uppercase"> 34 {course.level} 35 </span> 36 <h3 className="text-lg font-semibold mt-1">{course.title}</h3> 37 <p className="text-gray-500 text-sm mt-2 line-clamp-2"> 38 {course.description} 39 </p> 40 </div> 41 </Link> 42 ); 43}
6. State
State is data that changes over time and triggers a re-render when updated. In Next.js, state lives in Client Components ('use client').
Basic Syntax: useState
1"use client"; 2 3import { useState } from "react"; 4 5export function Counter() { 6 const [count, setCount] = useState(0); 7 8 return ( 9 <div className="p-6 border rounded-xl text-center"> 10 <p className="text-3xl font-bold mb-4">{count}</p> 11 <div className="flex gap-3 justify-center"> 12 <button 13 onClick={() => setCount(count - 1)} 14 className="px-4 py-2 bg-gray-200 rounded-lg" 15 > 16 − 17 </button> 18 <button 19 onClick={() => setCount(count + 1)} 20 className="px-4 py-2 bg-blue-600 text-white rounded-lg" 21 > 22 + 23 </button> 24 </div> 25 </div> 26 ); 27}
Next.js Example: Search Input
1// components/search/SearchBar.tsx 2"use client"; 3 4import { useState } from "react"; 5 6export function SearchBar() { 7 const [query, setQuery] = useState(""); 8 const [isSearching, setIsSearching] = useState(false); 9 10 const handleSearch = async (e: React.FormEvent) => { 11 e.preventDefault(); 12 setIsSearching(true); 13 14 // Search logic here 15 await fetch(`/api/search?q=${encodeURIComponent(query)}`); 16 17 setIsSearching(false); 18 }; 19 20 return ( 21 <form onSubmit={handleSearch} className="relative max-w-md"> 22 <input 23 type="text" 24 value={query} 25 onChange={(e) => setQuery(e.target.value)} 26 placeholder="Search courses..." 27 className="w-full px-4 py-2 pr-20 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" 28 /> 29 <button 30 type="submit" 31 disabled={isSearching || query.trim() === ""} 32 className="absolute right-1 top-1 px-4 py-1.5 bg-blue-600 text-white text-sm rounded-md disabled:opacity-50" 33 > 34 {isSearching ? "..." : "Search"} 35 </button> 36 </form> 37 ); 38}
7. Conditional Rendering
Show different UI based on conditions — loading states, auth status, empty states.
Basic Syntax
1function UserStatus({ isLoggedIn, user }: { isLoggedIn: boolean; user?: { name: string } }) { 2 if (!isLoggedIn) { 3 return <a href="/login" className="text-blue-600">Sign In</a>; 4 } 5 6 return <span>Welcome, {user?.name}!</span>; 7}
Ternary Operator (Inline)
1function Badge({ level }: { level: "Beginner" | "Intermediate" | "Advanced" }) { 2 return ( 3 <span 4 className={`px-3 py-1 rounded-full text-xs font-medium ${ 5 level === "Beginner" 6 ? "bg-green-100 text-green-700" 7 : level === "Intermediate" 8 ? "bg-yellow-100 text-yellow-700" 9 : "bg-red-100 text-red-700" 10 }`} 11 > 12 {level} 13 </span> 14 ); 15}
Next.js Example: Auth-Aware Navigation
1// components/layout/Navbar.tsx 2import Link from "next/link"; 3import { getSession } from "@/lib/auth/session"; 4 5export async function Navbar() { 6 const session = await getSession(); 7 8 return ( 9 <header className="flex items-center justify-between px-6 h-16 border-b"> 10 <Link href="/" className="text-xl font-bold text-blue-600"> 11 Tech3Space 12 </Link> 13 <nav className="flex items-center gap-6"> 14 <Link href="/courses">Courses</Link> 15 <Link href="/about">About</Link> 16 17 {session ? ( 18 <div className="flex items-center gap-4"> 19 <Link href="/dashboard" className="text-sm font-medium"> 20 {session.user.name} 21 </Link> 22 <form action={logout}> 23 <button type="submit" className="text-sm text-red-600"> 24 Logout 25 </button> 26 </form> 27 </div> 28 ) : ( 29 <Link 30 href="/login" 31 className="px-4 py-2 bg-blue-600 text-white rounded-lg text-sm" 32 > 33 Sign In 34 </Link> 35 )} 36 </nav> 37 </header> 38 ); 39}
8. Lists
Render arrays of data using map(). Always provide a unique key prop.
Basic Syntax
1const courses = [ 2 { id: 1, title: "Next.js" }, 3 { id: 2, title: "React" }, 4 { id: 3, title: "Python" }, 5]; 6 7export function CourseList() { 8 return ( 9 <ul className="space-y-2"> 10 {courses.map((course) => ( 11 <li key={course.id} className="p-3 border rounded-lg"> 12 {course.title} 13 </li> 14 ))} 15 </ul> 16 ); 17}
Next.js Example: Course Grid
1// components/courses/CourseGrid.tsx 2import { CourseCard } from "./CourseCard"; 3 4interface Course { 5 id: string; 6 slug: string; 7 title: string; 8 description: string; 9 thumbnail: string; 10 level: string; 11} 12 13interface CourseGridProps { 14 courses: Course[]; 15} 16 17export function CourseGrid({ courses }: CourseGridProps) { 18 if (courses.length === 0) { 19 return ( 20 <div className="text-center py-20"> 21 <p className="text-gray-500">No courses found.</p> 22 </div> 23 ); 24 } 25 26 return ( 27 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> 28 {courses.map((course) => ( 29 <CourseCard key={course.id} course={course} /> 30 ))} 31 </div> 32 ); 33}
9. Events
Events let components respond to user interactions — clicks, input changes, form submissions.
Basic Syntax
1"use client"; 2 3export function SubscribeForm() { 4 const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { 5 e.preventDefault(); 6 const formData = new FormData(e.currentTarget); 7 const email = formData.get("email"); 8 console.log("Subscribing:", email); 9 }; 10 11 return ( 12 <form onSubmit={handleSubmit} className="flex gap-2"> 13 <input 14 name="email" 15 type="email" 16 placeholder="Enter your email" 17 className="px-4 py-2 border rounded-lg flex-1" 18 /> 19 <button 20 type="submit" 21 className="px-6 py-2 bg-blue-600 text-white rounded-lg" 22 > 23 Subscribe 24 </button> 25 </form> 26 ); 27}
Event Types Reference
| Event | Handler Type | Example |
|---|---|---|
| Click | React.MouseEvent<HTMLButtonElement> | onClick={handleClick} |
| Change | React.ChangeEvent<HTMLInputElement> | onChange={handleChange} |
| Submit | React.FormEvent<HTMLFormElement> | onSubmit={handleSubmit} |
| Key Down | React.KeyboardEvent<HTMLInputElement> | onKeyDown={handleKey} |
| Focus | React.FocusEvent<HTMLInputElement> | onFocus={handleFocus} |
Next.js Example: Like Button
1// components/ui/LikeButton.tsx 2"use client"; 3 4import { useState } from "react"; 5import { Heart } from "lucide-react"; 6 7interface LikeButtonProps { 8 initialLikes: number; 9 courseId: string; 10} 11 12export function LikeButton({ initialLikes, courseId }: LikeButtonProps) { 13 const [likes, setLikes] = useState(initialLikes); 14 const [isLiked, setIsLiked] = useState(false); 15 16 const handleLike = async () => { 17 const newLiked = !isLiked; 18 setIsLiked(newLiked); 19 setLikes((prev) => (newLiked ? prev + 1 : prev - 1)); 20 21 // Call API 22 await fetch(`/api/courses/${courseId}/like`, { 23 method: "POST", 24 body: JSON.stringify({ liked: newLiked }), 25 }); 26 }; 27 28 return ( 29 <button 30 onClick={handleLike} 31 className={`flex items-center gap-2 px-4 py-2 rounded-lg transition ${ 32 isLiked 33 ? "bg-red-50 text-red-600" 34 : "bg-gray-100 text-gray-600 hover:bg-gray-200" 35 }`} 36 > 37 <Heart className={`w-5 h-5 ${isLiked ? "fill-current" : ""}`} /> 38 <span>{likes}</span> 39 </button> 40 ); 41}
10. Common Mistakes
❌ Mistake 1: Modifying Props Directly
1// WRONG — props are read-only 2export function Counter({ count }: { count: number }) { 3 count = count + 1; // ❌ Error: Cannot assign to read-only property 4 return <div>{count}</div>; 5}
1// CORRECT — lift state up to parent 2"use client"; 3import { useState } from "react"; 4 5export function Parent() { 6 const [count, setCount] = useState(0); 7 return <Counter count={count} onIncrement={() => setCount((c) => c + 1)} />; 8} 9 10export function Counter({ 11 count, 12 onIncrement, 13}: { 14 count: number; 15 onIncrement: () => void; 16}) { 17 return <button onClick={onIncrement}>Count: {count}</button>; 18}
❌ Mistake 2: Missing key in Lists
1// WRONG — no key, causes rendering bugs 2{courses.map((course) => ( 3 <CourseCard course={course} /> 4))}
1// CORRECT — unique key 2{courses.map((course) => ( 3 <CourseCard key={course.id} course={course} /> 4))}
Never use array index as key if the list can reorder or items can be deleted.
❌ Mistake 3: Calling useState Conditionally
1// WRONG — hooks must be called in the same order every render 2export function BadComponent({ isActive }: { isActive: boolean }) { 3 if (isActive) { 4 const [count, setCount] = useState(0); // ❌ Conditional hook 5 } 6 return <div>{count}</div>; 7}
1// CORRECT — hooks at the top level 2export function GoodComponent({ isActive }: { isActive: boolean }) { 3 const [count, setCount] = useState(0); 4 5 if (!isActive) return null; 6 7 return <div>{count}</div>; 8}
❌ Mistake 4: Using State for Derived Values
1// WRONG — redundant state 2const [fullName, setFullName] = useState(""); 3useEffect(() => { 4 setFullName(`${firstName} ${lastName}`); 5}, [firstName, lastName]);
1// CORRECT — compute during render 2const fullName = `${firstName} ${lastName}`;
❌ Mistake 5: Not Typing Event Handlers
1// WRONG — untyped, easy to make mistakes 2function handleChange(e) { 3 console.log(e.target.value); 4}
1// CORRECT — typed event 2function handleChange(e: React.ChangeEvent<HTMLInputElement>) { 3 console.log(e.target.value); 4}
11. Best Practices
| Practice | Why It Matters |
|---|---|
| Type your props | Catches bugs at compile time |
| Use interfaces | Clear contracts between components |
| Destructure props | Cleaner, more readable code |
| Lift state up | Share state between siblings |
Use key correctly | Prevents rendering bugs |
| Keep components small | Easier to test and reuse |
| Separate concerns | UI components vs. logic components |
| Prefer Server Components | Less JavaScript, better SEO |
12. Architecture Explanation
How React Fundamentals Fit into Next.js
Next.js App
├── Server Components (default)
│ ├── No state, no effects
│ ├── Fetch data directly
│ ├── Pass data as props to children
│ └── Render lists, conditionals, static UI
│
└── Client Components ('use client')
├── useState for interactivity
├── useEffect for side effects
├── Event handlers for user input
├── Browser APIs
└── Receive props from Server Components
The Pattern:
- Server Component fetches data and renders the page structure
- Props pass data down to child components
- Client Components handle interactivity where needed
- State manages user input and UI state
- Events respond to user actions
- Conditional rendering shows appropriate UI for each state
13. Practice Task
Task: Build a reusable CourseFilter component.
Requirements:
- Create
components/courses/CourseFilter.tsx - Accept
categoriesandlevelsas props - Use
useStateto track selected filters - Call
onFilterChange(filters)when selections change - Use conditional rendering to show "Clear Filters" only when filters are active
- Render categories and levels as lists with proper keys
Starter data:
1const categories = ["Web Development", "Data Science", "DevOps"]; 2const levels = ["Beginner", "Intermediate", "Advanced"];
14. Mini Project: Tech3Space Course Card with Interactions
Build a complete, interactive course card component:
components/courses/InteractiveCourseCard.tsx
1"use client"; 2 3import { useState } from "react"; 4import Image from "next/image"; 5import Link from "next/link"; 6import { Heart, Bookmark, Star } from "lucide-react"; 7 8interface Course { 9 id: string; 10 slug: string; 11 title: string; 12 description: string; 13 thumbnail: string; 14 level: "Beginner" | "Intermediate" | "Advanced"; 15 rating: number; 16 students: number; 17} 18 19interface InteractiveCourseCardProps { 20 course: Course; 21} 22 23export function InteractiveCourseCard({ course }: InteractiveCourseCardProps) { 24 const [isLiked, setIsLiked] = useState(false); 25 const [isBookmarked, setIsBookmarked] = useState(false); 26 const [isHovered, setIsHovered] = useState(false); 27 28 const levelColors = { 29 Beginner: "bg-green-100 text-green-700", 30 Intermediate: "bg-yellow-100 text-yellow-700", 31 Advanced: "bg-red-100 text-red-700", 32 }; 33 34 return ( 35 <div 36 className={`border rounded-xl overflow-hidden transition-all duration-300 ${ 37 isHovered ? "shadow-xl -translate-y-1" : "shadow-sm" 38 }`} 39 onMouseEnter={() => setIsHovered(true)} 40 onMouseLeave={() => setIsHovered(false)} 41 > 42 <Link href={`/courses/${course.slug}`} className="block"> 43 <div className="relative h-48"> 44 <Image 45 src={course.thumbnail} 46 alt={course.title} 47 fill 48 className="object-cover" 49 /> 50 <span 51 className={`absolute top-3 left-3 px-2 py-1 rounded-md text-xs font-medium ${levelColors[course.level]}`} 52 > 53 {course.level} 54 </span> 55 </div> 56 </Link> 57 58 <div className="p-5"> 59 <div className="flex items-start justify-between mb-2"> 60 <Link href={`/courses/${course.slug}`}> 61 <h3 className="text-lg font-semibold hover:text-blue-600 transition"> 62 {course.title} 63 </h3> 64 </Link> 65 <button 66 onClick={() => setIsBookmarked(!isBookmarked)} 67 className={`p-1.5 rounded-full transition ${ 68 isBookmarked ? "text-blue-600 bg-blue-50" : "text-gray-400 hover:text-gray-600" 69 }`} 70 > 71 <Bookmark className={`w-5 h-5 ${isBookmarked ? "fill-current" : ""}`} /> 72 </button> 73 </div> 74 75 <p className="text-gray-500 text-sm mb-4 line-clamp-2"> 76 {course.description} 77 </p> 78 79 <div className="flex items-center justify-between"> 80 <div className="flex items-center gap-1"> 81 <Star className="w-4 h-4 text-yellow-500 fill-current" /> 82 <span className="text-sm font-medium">{course.rating}</span> 83 <span className="text-sm text-gray-400"> 84 ({course.students.toLocaleString()} students) 85 </span> 86 </div> 87 88 <button 89 onClick={() => setIsLiked(!isLiked)} 90 className={`flex items-center gap-1.5 text-sm transition ${ 91 isLiked ? "text-red-500" : "text-gray-400 hover:text-gray-600" 92 }`} 93 > 94 <Heart className={`w-4 h-4 ${isLiked ? "fill-current" : ""}`} /> 95 {isLiked ? "Liked" : "Like"} 96 </button> 97 </div> 98 </div> 99 </div> 100 ); 101}
Summary
| Concept | Purpose | Where It Lives |
|---|---|---|
| Components | Reusable UI building blocks | Server & Client |
| Props | Pass data parent → child | Server & Client |
| State | Track changing data | Client Components only |
| Conditional Rendering | Show/hide UI based on conditions | Server & Client |
| Lists | Render arrays of data | Server & Client |
| Events | Respond to user actions | Client Components only |
What's Next?
In Module 10, we'll dive deep into React Hooks — useState, useEffect, useContext, useRef, useMemo, useCallback, and how to build custom hooks. These are the tools that power every interactive Next.js application.
Your mission: Build three components for your Tech3Space app:
- A
CourseCardcomponent that accepts course data via props - A
SearchBarcomponent withuseStatefor the search query - A
CourseListcomponent that renders an array of courses using.map()with proper keys
Test each component independently, then compose them together on your courses page!