Skip to main content

React & Next.js Fundamentals

React vs Vue

You already know Vue -- React solves the same problems with a different philosophy. Here's the key mapping:

ConceptVueReact
Template<template> HTML-basedJSX (JS returns markup)
Two-way bindingv-modelControlled components
Conditionalv-if, v-show&&, ternary, early return
List renderingv-for.map()
Events@clickonClick
Reactivityref(), reactive()useState()
Computedcomputed()useMemo()
Side effectswatch(), onMounted()useEffect()
ComposablesuseXxx() composableCustom hook useXxx()
Slots<slot>children prop
State managementPiniaContext, Zustand

The biggest difference: Vue uses HTML-based templates with special directives (v-if, v-for, v-model). React uses JSX -- you write markup directly in JavaScript. There are no directives -- everything is plain JavaScript expressions.

Why a framework?

Plain React usage is not recommended anymore (although possible). Most apps eventually build solutions for routing, data fetching, code-splitting, and server-side rendering.

Framework options:

  • Next.js -- the most popular, used in this course
  • Remix
  • Gatsby
  • Expo (mobile)

Next.js Setup

Use create-next-app with App Router (the default since Next.js 13.4):

npx create-next-app@latest

Choose your options:

  • App Router (default, yes)
  • TypeScript (yes)
  • ESLint (yes)
  • Tailwind or Bootstrap? (Bootstrap -- since ASP.NET course uses it too)

Project structure

src/app/
├── layout.tsx # Root layout (wraps all pages)
├── page.tsx # Home page (/)
├── globals.css # Global styles
└── dashboard/
└── page.tsx # Dashboard page (/dashboard)

Clean up initial code

  • Empty globals.css and page.module.css
  • Clean out page.tsx
export default function Home() {
return (
<main>
<h1>Hello, World!</h1>
</main>
);
}

Add Bootstrap support

npm install bootstrap

Modify layout.tsx:

import type { Metadata } from "next";
import "bootstrap/dist/css/bootstrap.css";
import { Inter } from "next/font/google";
import "./globals.css";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};

export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}

Modify page.tsx to test Bootstrap:

export default function Home() {
return (
<main>
<h1 className="text-danger">Hello Bootstrap</h1>
</main>
);
}

Bootstrap JS support

Bootstrap JS components (accordion, dropdown, modal) need JavaScript. Create src/components/BootstrapActivation.tsx:

"use client";

import { useEffect } from "react";

export default function BootstrapActivation() {
useEffect(() => {
import("bootstrap/dist/js/bootstrap.bundle.min.js");
}, []);

return null;
}

Add it to layout.tsx:

import type { Metadata } from "next";
import "bootstrap/dist/css/bootstrap.css";
import { Inter } from "next/font/google";
import "./globals.css";
import BootstrapActivation from "@/components/BootstrapActivation";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};

export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en">
<body className={inter.className}>
{children}
<BootstrapActivation />
</body>
</html>
);
}

Test with an accordion in page.tsx:

export default function Home() {
return (
<main>
<div className="text-center mt-4 col-md-6 mx-auto">
<h1 className="text-danger">Hello Bootstrap</h1>

<div className="accordion" id="accordionExample">
<div className="accordion-item">
<h2 className="accordion-header" id="headingOne">
<button
className="accordion-button"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseOne"
aria-expanded="true"
aria-controls="collapseOne"
>
Accordion Item #1
</button>
</h2>
<div
id="collapseOne"
className="accordion-collapse collapse show"
aria-labelledby="headingOne"
data-bs-parent="#accordionExample"
>
<div className="accordion-body">
<strong>This is the first accordion body.</strong>
</div>
</div>
</div>
<div className="accordion-item">
<h2 className="accordion-header" id="headingTwo">
<button
className="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseTwo"
aria-expanded="false"
aria-controls="collapseTwo"
>
Accordion Item #2
</button>
</h2>
<div
id="collapseTwo"
className="accordion-collapse collapse"
aria-labelledby="headingTwo"
data-bs-parent="#accordionExample"
>
<div className="accordion-body">
<strong>This is the second accordion body.</strong>
</div>
</div>
</div>
</div>
</div>
</main>
);
}

JSX

React uses a markup syntax called JSX -- HTML written directly in JavaScript. It compiles to JavaScript function calls.

function About() {
return <h1>About</h1>;
}

Compiles to (you never write this manually):

// Automatic JSX transform (React 17+)
import { jsx as _jsx } from "react/jsx-runtime";
function About() {
return _jsx("h1", { children: "About" });
}

You can see this in action at babeljs.io.

babeljs.io playground

JSX Properties

Since JSX compiles to JavaScript, some HTML attribute names conflict with JS reserved words:

  • class -> className
  • for -> htmlFor
  • Attributes are camelCased: tabindex -> tabIndex, onclick -> onClick

Expressions in JSX

Use {} to embed any JavaScript expression (not statements!) in JSX:

const name = "World";
return <h1>Hello {name}!</h1>;

Inline styles

Inline styles use a JavaScript object (double curly braces -- outer {} for expression, inner {} for the object):

function About() {
return (
<h1
style={{
color: "red",
height: 20,
fontSize: "2rem",
}}
>
About
</h1>
);
}
Vue comparison

In Vue, you write <h1 :style="{ color: 'red' }">. In React, it's <h1 style={{ color: "red" }}>. Same concept, slightly different syntax.

Conditional rendering

In Vue you use v-if and v-show. In React, you use plain JavaScript:

// Ternary operator (like v-if / v-else)
{isLoggedIn ? <Dashboard /> : <Login />}

// Logical AND (like v-if without else)
{showError && <ErrorMessage />}

// Early return (common pattern for loading states)
function Page() {
if (isLoading) return <Spinner />;
if (error) return <ErrorAlert message={error} />;
return <Content data={data} />;
}

List rendering

In Vue you use v-for. In React, you use .map():

const items = ["Apple", "Banana", "Cherry"];

return (
<ul>
{items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
);
danger

Every element in a .map() must have a unique key prop. Use IDs from your data, not array indices.

Components

In modern React, components are functions that return JSX. There are two equivalent syntaxes:

Function component

function About() {
return <h1>About</h1>;
}

Arrow function component

const About = () => <h1>About</h1>;

Both are equivalent. Use whichever style your team prefers.

Component rules

  • Component names must start with a capital letter. JSX assumes lowercase = HTML element, uppercase = React component.
  • Use parentheses around multiline JSX: return ( <div>...</div> );
  • JSX can only have one top-level element. Use fragments <>...</> to wrap multiple elements without adding extra DOM nodes.
// Fragment -- avoids unnecessary wrapper divs
const Page = () => (
<>
<Header />
<Main />
<Footer />
</>
);
Vue comparison

Vue SFCs have separate <template>, <script>, <style> blocks. React components put everything in one .tsx file -- the function IS the component.

Component example

const Privacy = () => <h1>Privacy</h1>;
export default Privacy;
const Home = () => <h1>Home</h1>;
export default Home;

Components example

When converting HTML from ASP.NET, remember: class -> className.

danger

class -> className

const Header = () => (
<header>
<nav className="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div className="container">
<a className="navbar-brand" href="/">
WebApp
</a>
<button
className="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target=".navbar-collapse"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span className="navbar-toggler-icon"></span>
</button>
<div className="navbar-collapse collapse d-sm-inline-flex flex-sm-row-reverse">
<ul className="navbar-nav flex-grow-1">
<li className="nav-item">
<a className="nav-link text-dark" href="/">
Home
</a>
</li>
<li className="nav-item">
<a className="nav-link text-dark" href="/privacy">
Privacy
</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
);

export default Header;

Data in React

React uses one-way data binding (unlike Vue's v-model which provides two-way binding).

Data lives in two places:

  • Props -- passed down from parent (immutable in the receiving component)
  • State -- owned by the component (mutable via setter functions)
Vue comparison

Props work the same as Vue's defineProps. State is like Vue's ref() / reactive(). The difference: React has no built-in v-model -- you manually wire value + onChange.

Props

Props are the React equivalent of Vue's defineProps. They pass data from parent to child.

Untyped (avoid this)

const Privacy = (props: any) => <h1>Privacy {props.message}</h1>;

Typed with interface (preferred)

interface PrivacyProps {
message: string;
}

const Privacy = (props: PrivacyProps) => <h1>Privacy {props.message}</h1>;

Destructured props (cleanest)

interface PrivacyProps {
message: string;
}

const Privacy = ({ message }: PrivacyProps) => <h1>Privacy {message}</h1>;

Usage

<Privacy message="is important!" />

Optional props and defaults

interface PrivacyProps {
message: string;
showIt?: boolean;
}

const Privacy = ({ message, showIt = false }: PrivacyProps) => (
<h1>Privacy {showIt ? message : "is disabled"}</h1>
);

The children prop

React's equivalent of Vue's <slot>. Any JSX nested inside a component is passed as children:

interface CardProps {
title: string;
children: React.ReactNode;
}

const Card = ({ title, children }: CardProps) => (
<div className="card">
<div className="card-header">{title}</div>
<div className="card-body">{children}</div>
</div>
);

// Usage
<Card title="Welcome">
<p>This content goes into the children prop.</p>
<button>Click me</button>
</Card>
Vue comparison

Vue uses named slots (<slot name="header">). React passes everything as children, or you can pass JSX as regular props for "named slot" behavior.

Key prop

Keys help React identify which items have changed, are added, or removed in lists.

  • Use a unique ID from your data as the key
  • Keys must be unique among siblings (not globally)
  • Do not use array index as key if items can be reordered or filtered
  • The key prop goes on the outermost element returned by .map()
{items.map((item) => (
<ItemRow key={item.id} item={item} />
))}

Server Components vs Client Components

In Next.js App Router, components are Server Components by default. They run on the server and send HTML to the browser.

Server Components (default):

  • Can directly access databases, file system, environment variables
  • Cannot use hooks (useState, useEffect, etc.)
  • Cannot use browser APIs (window, document, etc.)
  • Cannot use event handlers (onClick, onChange, etc.)

Client Components (add "use client" at the top):

  • Can use hooks and browser APIs
  • Can handle user interactions
  • Ship JavaScript to the browser
"use client";

import { useState } from "react";

export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}

Rule of thumb: Start with Server Components. Add "use client" only when you need interactivity (hooks, event handlers, browser APIs).

Vue comparison

Vue/Nuxt also supports server-side rendering, but all components are "universal" by default. In Next.js, you explicitly choose server vs client per component.

Class components (historical note)

info

Before React 16.8 (2019), class components were the only way to use state and lifecycle methods. After hooks were introduced, function components became the standard. Class components still work but are not recommended for new code. The only remaining use case is error boundaries (covered in lecture 16).