ECE1724: Advanced Web Development
React Ecosystem and Modern Frameworks
Chen Ying
Assistant Professor, Teaching Stream
Department of Electrical and Computer Engineering
University of Toronto
Last Week’s Lecture
React : Declarative , component-based JavaScript library for building user interfaces
Focuses on what the UI should look like, not how to update it
Efficiently updates the UI using a Virtual DOM
Create a React Project
Use Vite : A modern build tool for React
npm create vite@latest my-app
Create a TypeScript React project
npm create vite@latest my-app -- --template react-ts
Create React App vs. Vite
Create React App
Simple, zero-configuration starting point
Vite
Much faster startup and hot reload
Simpler internal tooling
Widely used in current industry React projects
Last Week’s Lecture
Core React Concepts
Core React Concepts
Components : Reusable building blocks of a React app
Props : Read-only data passed from parent components to child components
State : Local, mutable data managed within a component that triggers UI updates
App.tsx and main.tsx
App.tsx: Root component
Defines the top-level UI structure
Renders child components
Serves as the container for the entire application
main.tsx: Application entry point
Imports the root React component (App.tsx)
Creates a React root and mounts the app into the DOM
index.html
HTML entry point
<!doctype html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" />
< link rel = "icon" type = "image/svg+xml" href = "/vite.svg" />
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" />
< title > my-app</ title >
</ head >
< body >
< div id = "root" ></ div >
< script type= "module" src = "/src/main.tsx" ></ script >
</ body >
</ html >
index.html
HTML entry point
< body >
< div id = "root" ></ div >
< script type= "module" src = "/src/main.tsx" ></ script >
</ body >
<div id="root"></div>: The mount point — React renders the entire app into this element
<script type="module" src="/src/main.tsx"></script>: Loads the entry file (main.tsx)
HMR
Hot Module Replacement : A development tool provided by Vite
Updates the app instantly when you edit code
No full page reload
App state is preserved when possible
Speeds up development and debugging
Today’s Lecture
JSX/TSX Syntax
JSX & TSX
JSX (JavaScript XML) : Syntax extension for JavaScript that allows you to write HTML-like code within JavaScript
Combines power of JavaScript + readability of HTML
TSX : JSX inside TypeScript files
type WelcomeProps = {
name : string;
};
function Welcome ({ name }: WelcomeProps) {
return <h1> Welcome to { name} 's Lecture!</h1> ;
}
Rule 1: One Parent Element
JSX must return one parent element
export default function App () {
return (
<>
<h1> Hello</h1>
<p> React + TypeScript</p>
</>
);
}
Not allowed:
export default function App () {
return (
<h1> Hello</h1>
<p> React + TypeScript</p>
);
}
Use <div>...</div>, <main>…</main>, <>...</>
Rule 2: Different Attributes
HTML Attributes: Additional information about an element
< img src = "profile.jpeg" alt = "Profile Picture" class = "profile-pic" />
JSX Attributes: May different from regular HTML attributes
const profilePic = (
<img src = "profile.jpeg" alt = "Profile Picture" className = "profile-pic" />
);
Use camelCase
Examples: className (not class), htmlFor (not for)
Rule 2: Different Attributes
HTML: for and id link the label to that specific form control
< form class = "form" >
< label for = "username" > Username: </ label >
< input id = "username" type = "text" />
</ form >
JSX
export default function UserForm () {
return (
<form className = "form" >
<label htmlFor = "username" > Username: </label>
<input id = "username" type = "text" />
</form>
);
}
Recap: JSX/TSX Rules
Rule 1: One Parent Element
JSX/TSX must return one parent element
Use <div>...</div>, <main>…</main>, <>...</>
Rule 2: Different Attributes
JSX attributes may different from HTML attributes
Examples: className (not class), htmlFor (not for)
Rule 3: Self-Closing Tags
Must have a closing slash (e.g., <br />)
Today’s Lecture
JSX/TSX Syntax
JSX/TSX Rules
JavaScript/TypeScript inside JSX/TSX
Embedding Expressions
Use curly braces {} to embed any JavaScript/TypeScript expression
const name = "Alice" ;
export default function Greeting () {
return <h1> Hello, { name. toUpperCase ()} !</h1> ;
}
Output?
export default function App () {
return (
<>
<h1> { 2 + 2 } </h1>
<h1> 2 + 2</h1>
</>
);
}
JS/TS inside JSX/TSX
Dynamic Logic
Rendering Lists
Event Handlers
Dynamic Logic
function App () {
let isLoggedIn = true ;
isLoggedIn = false ;
return <h1> { isLoggedIn ? "Welcome Back!" : "Please Log In" } </h1> ;
}
export default App;
Rendering Lists
Use JavaScript methods (e.g., map()) to render lists dynamically
src/components/ListExample.tsx
const items = ["React" , "TSX" , "Hooks" ];
export default function ListExample () {
return (
<ul>
{ items. map ((item) => (
<li key = { item} > { item} </li>
))}
</ul>
);
}
key
<ul>
{ items. map ((item) => (
<li key = { item} > { item} </li>
))}
</ul>
key is a special prop used by React
Gives each list item a stable identity
Must be unique among siblings
key lets React
Update only what changed (efficient rendering)
Preserve component state when lists change
key
<ul>
{ items. map ((item) => (
<li key = { item} > { item} </li>
))}
</ul>
key is used internally by React
It is not rendered to the DOM
Rendering List
src/components/ListExample.tsx
const items = ["React" , "TSX" , "Hooks" ];
export default function ListExample () {
return (
<ul>
{ items. map ((item) => (
<li key = { item} > { item} </li>
))}
</ul>
);
}
What the browser actually sees:
< ul >
< li > React</ li >
< li > TSX</ li >
< li > Hooks</ li >
</ ul >
Live Demo
import ListExample from "./components/ListExample" ;
function App () {
return <ListExample /> ;
}
export default App;
JS/TS inside JSX/TSX
Dynamic Logic
Rendering Lists
Event Handlers
JavaScript/TypeScript functions can be passed as event handlers in JSX/TSX
src/components/ButtonExample.tsx
export default function ButtonExample () {
const handleClick = (e : React. MouseEvent < HTMLButtonElement > ) => {
alert ("Button clicked!" );
console . log ("clicked" , e. currentTarget );
};
return (
<button type = "button" onClick = { handleClick} >
Click Me
</button>
);
}
Event Type
const handleClick = (e: React. MouseEvent < HTMLButtonElement > ) => {
alert ("Button clicked!" );
console . log ("clicked" , e. currentTarget );
};
MouseEvent: This handler responds to a mouse interaction (click, double-click, etc.)
HTMLButtonElement: The event came from a <button> element
TypeScript now knows e.currentTarget is a button
Button-specific properties are safe to use
Mistakes are caught at compile time
Event Handler
src/components/ButtonExample.tsx
export default function ButtonExample () {
const handleClick = (e : React. MouseEvent < HTMLButtonElement > ) => {
alert ("Button clicked!" );
console . log ("clicked" , e. currentTarget );
};
return (
<button type = "button" onClick = { handleClick} >
Click Me
</button>
);
}
React.MouseEvent<HTMLButtonElement> means a mouse event coming from a button
React uses its own typed event system (React.*Event)
Live Demo
import ButtonExample from "./components/ButtonExample" ;
function App () {
return <ButtonExample /> ;
}
export default App;
Recap: JSX/TSX Syntax
HTML-Like Syntax in JavaScript : Combine the structure of HTML with the logic of JavaScript for a declarative UI
Embedding JavaScript/TypeScript : Use {} to dynamically insert variables, expressions, function calls
Single Parent Element : Wrap all elements inside a single parent container (e.g. <> and </>)
Attributes : Use camelCase (e.g. className, htmlFor)
Self-Closing Tags : Must have a closing slash (e.g., <br />)
Today’s Lecture
JSX/TSX Syntax
Hooks
Functions that let you use React’s features (e.g. state) in function components
src/components/Counter.tsx
import { useState } from "react" ;
function Counter () {
const [count, setCount] = useState< number> (0 );
return (
<div>
<p> Current Count: { count} </p>
<button type = "button" onClick = { () => setCount (count + 1 )} >
Increment
</button>
</div>
);
}
export default Counter;
Hooks
Functions that let you use React’s features (e.g. state) in function components
Simplify React development
Widely used in modern React applications
useSuperPower()
useState, useEffect, useContext, …
One Rule
Only call hooks at the top level of a function component
function App () {
// This works
useHook ();
// This won't work
const oops = () => {
useHook ();
};
// This won't work
return <button onClick = { () => useHook ()} ></button> ;
}
Only exception: Custom hooks
Basic Hooks
useState
useEffect
useContext
useState
Re-render the UI when state changes
Syntax
const [state, setState] = useState (initialValue);
state: Current state value
setState: Function to update the state
useState: Example
src/components/Counter.tsx
import { useState } from "react" ;
function Counter () {
const [count, setCount] = useState< number> (0 );
const increment = () => setCount (count + 1 );
return (
<div>
<p> Current Count: { count} </p>
<button type = "button" onClick = { increment} >
Increment
</button>
</div>
);
}
export default Counter;
TypeScript usually infers state type from initial value
const [count, setCount] = useState (0 );
When You Need a Type
If initial value is null or an empty array/object, add a generic
<T>: A type parameter that allows to store any type while keeping type safety
import { useState } from "react" ;
type User = { id: number ; name: string };
export default function Example () {
const [user, setUser] = useState < User | null > (null );
// ...
return < div> {user ? user. name : "Loading..." }</ div>;
}
Live Demo
import Counter from "./components/Counter" ;
function App () {
return <Counter /> ;
}
export default App;
Can This Work?
src/components/Counter.tsx
function Counter () {
let count = 0 ;
const increment = () => {
count += 1 ;
};
return (
<div>
<p> Current Count: { count} </p>
<button type = "button" onClick = { increment} >
Increment
</button>
</div>
);
}
export default Counter;
“Super Power” of useState
When setCount(count + 1) is called, React detects a state update and re-runs the Counter function (re-renders)
src/components/Counter.tsx
import { useState } from "react" ;
function Counter () {
const [count, setCount] = useState< number> (0 );
const increment = () => setCount (count + 1 );
return (
<div>
<p> Current Count: { count} </p>
<button type = "button" onClick = { increment} >
Increment
</button>
</div>
);
}
export default Counter;
Share State Between Components
import Counter from "./components/Counter" ;
function App () {
return (
<>
<Counter />
<Counter />
</>
);
}
export default App;
Lift State Up
Keep state in the nearest common parent (e.g., App), pass values + handlers down (e.g., App → Counter)
Lift State Up: Step 1
Move the state up from Counter to App
import { useState } from "react" ;
import Counter from "./components/Counter" ;
function App () {
const [count, setCount] = useState (0 );
const increment = () => setCount (count + 1 );
return (
<>
<Counter />
<Counter />
</>
);
}
export default App;
Lift State Up: Step 2
Pass the state down from App to each Counter, together with the shared handler
import { useState } from "react" ;
import Counter from "./components/Counter" ;
function App () {
const [count, setCount] = useState (0 );
const increment = () => setCount (count + 1 );
return (
<>
<Counter count = { count} onClick = { increment} />
<Counter count = { count} onClick = { increment} />
</>
);
}
export default App;
Lift State Up: Step 3
Change Counter to read the props passed from its parent component
src/components/Counter.tsx
type CounterProps = {
count : number;
onClick : () => void ;
};
function Counter (props: CounterProps) {
return (
<div>
<p> Current Count: { props. count } </p>
<button type = "button" onClick = { props. onClick } >
Increment
</button>
</div>
);
}
export default Counter;
Props Type
Destructure
src/components/Counter.tsx
type CounterProps = {
count : number;
onClick : () => void ;
};
function Counter ({ count, onClick }: CounterProps) {
return (
<div>
<p> Current Count: { count} </p>
<button type = "button" onClick = { onClick} >
Increment
</button>
</div>
);
}
export default Counter;
Live Demo
import { useState } from "react" ;
import Counter from "./components/Counter" ;
function App () {
const [count, setCount] = useState (0 );
const increment = () => setCount (count + 1 );
return (
<>
<Counter count = { count} onClick = { increment} />
<Counter count = { count} onClick = { increment} />
</>
);
}
export default App;
Lift State Up
Each Counter’s onClick prop was set to the increment function inside App; count prop was set to 0
When a Counter’s button is clicked, the onClick handler fires, and the code inside of increment runs
increment calls setCount(count + 1), incrementing the count state variable
The new count value is passed as a prop to each Counter, so they show the same new value
Basic Hooks
useState : Manage state within a component
Re-render UI when state changes
Component Lifecycle
Describes how a component behaves over time
From the moment it appears on the screen
To the moment it is removed from the DOM
Lifecycle Phases
A React component goes through three main phases
Mounting : The component is created and added to the DOM
Updating : The component re-renders when state or props change
Unmounting : The component is removed from the DOM
These phases happen automatically
React manages the lifecycle for you
React’s Role
React renders the UI declaratively
You describe what the UI should look like
React figures out how to update the DOM
However, not all logic is about rendering UI
Side Effects
Operations that go beyond rendering the UI
Communicating with external systems
Fetching data from APIs
Setting up timers, listeners, subscriptions, …
These operations must be coordinated with the component lifecycle
Assignment 3
Develop a React-based user interface for managing papers and authors
Fetch data from Assignment 2 backend API
Display and update data based on user interactions
Side Effects & Lifecycle
React allows you to tie side effects to specific lifecycle phases
Mounting ← Fetch initial data
Updating ← Change when state or props update
Unmounting ← Clean up resources (e.g., timer) to avoid memory leaks
useEffect
Handle side effects
Run code at specific points in a component’s lifecycle
With useEffect, you can:
Run code after the component mounts
Re-run code when state or props change
Clean up resources when the component unmounts
useEffect: Syntax
useEffect (() => {
// Code runs when the component mounts
// and when dependencies change
return () => {
// Cleanup code runs before the component unmounts
// or before the effect re-runs due to dependency changes
};
}, [dependencies]);
Dependency array:
If empty ([]): Run only on mount, cleanup on unmount
If specific values ([value1, value2]): Run when the dependencies change
Dependency Array
State variables
Props (values passed from a parent component)
import { useEffect, useState } from "react" ;
function ChatRoom () {
// State that controls which chat room is active
const [roomId, setRoomId] = useState ("general" );
useEffect (() => {
// Side effect: connect to the chat room
const connection = createConnection (roomId);
connection. connect ();
// Cleanup: disconnect from the previous room
return () => {
connection. disconnect ();
};
}, [roomId]); // Re-run effect when roomId changes
return (
<div>
<p> Current room: { roomId} </p>
<button onClick = { () => setRoomId ("general" )} > General</button>
<button onClick = { () => setRoomId ("random" )} > Random</button>
</div>
);
}
Side Effect: Timer
import { useEffect, useState } from "react" ;
export function Timer () {
const [seconds, setSeconds] = useState (0 );
useEffect (() => {
const interval = setInterval (() => {
setSeconds ((prev) => prev + 1 );
}, 1000 );
return () => {
clearInterval (interval);
console . log ("Timer unmounted" );
};
}, []);
return <h1> Elapsed Time: { seconds} seconds</h1> ;
}
[]: Effect runs once on mount
Live Demo
import { Timer } from "./components/Timer" ;
function App () {
return <Timer /> ;
}
export default App;
Side Effects: Benefits
Beyond Rendering : Side effects handle operations outside React’s main rendering process
Predictable Execution : useEffect ensures side effects are explicitly and reliably managed
Lifecycle Integration : React ties side effects to component lifecycles (mount, update, unmount)
Basic Hooks
useState : Manage state within a component
useEffect : Handle side effects and lifecycle-related logic
useContext : Share values across components without passing props manually
Why useContext?
Passing props through many layers can become
Verbose
Error-prone
Hard to maintain
useContext allows components to access shared data directly, no matter how deeply nested they are
How useContext Works
Create a context
Provide a context value
Consume the value in a component
Create a Context
Use createContext() to define a context and its default value
src/contexts/UserContext.tsx
import { createContext } from "react" ;
const UserContext = createContext ("Guest" );
export default UserContext;
"Guest" is the default value
Used only if no provider is found
Provide a Context Value
A Provider makes the context value available to all child components
import WelcomeMessage from "./components/WelcomeMessage" ;
import UserContext from "./contexts/UserContext" ;
function App () {
return (
<UserContext.Provider value = "Alice" >
<WelcomeMessage />
</UserContext.Provider>
);
}
value is the data shared with consuming components
Any descendant can access it
Use Context in a Component
useContext() lets a component read the context value
src/components/WelcomeMessage.tsx
import { useContext } from "react" ;
import UserContext from "../contexts/UserContext" ;
function WelcomeMessage () {
const user = useContext (UserContext);
return <h1> Welcome, { user} !</h1> ;
}
export default WelcomeMessage;
Live Demo
import WelcomeMessage from "./components/WelcomeMessage" ;
import UserContext from "./contexts/UserContext" ;
function App () {
return (
<UserContext.Provider value = "Alice" >
<WelcomeMessage />
</UserContext.Provider>
);
}
export default App;
useContext Key Points
function WelcomeMessage () {
const user = useContext (UserContext);
return <h1> Welcome, { user} !</h1> ;
}
useContext() returns the current context value
React looks for the closest Provider above the component
If no Provider exists, the default value is used
When the context value changes, all consuming components re-render automatically
Basic Hooks
useState : Manage state within a component
useEffect : Handle side effects and lifecycle-related logic
useContext : Share values globally without passing props manually
Custom Hook
A reusable function that:
Encapsulates logic
Uses one or more React hooks
Can be shared across components
Custom Hook: Example
useToggle: Manages a boolean state and provides a toggle function
import { useState } from "react" ;
function useToggle (initialValue: boolean): [boolean, () => void ] {
const [value, setValue] = useState (initialValue);
const toggle = () => {
setValue ((prev) => ! prev);
};
return [value, toggle];
}
export default useToggle;
Custom Hook: Example
ToggleButton component uses useToggle
src/components/ToggleButton.tsx
import useToggle from "../hooks/useToggle" ;
function ToggleButton () {
const [isOn, toggleIsOn] = useToggle (false );
return (
<>
<p> The button is: { isOn ? "ON" : "OFF" } </p>
<button type = "button" onClick = { toggleIsOn} >
Toggle
</button>
</>
);
}
export default ToggleButton;
Live Demo
import ToggleButton from "./components/ToggleButton" ;
function App () {
return <ToggleButton /> ;
}
export default App;
Recap: Hooks
Functions that allow components to manage state, side effects, and lifecycle features
useState : Manage component state
useEffect : Handle side effects and lifecycle logic
useContext : Share values globally without passing props manually
Custom Hook : Reusable function that encapsulates logic using hooks
Lecture Summary
JSX/TSX Syntax
One parent element
JSX-specific attributes (className, htmlFor)
Self-closing tags
Embedding JavaScript/TypeScript with {}
Hooks
React built-in hooks (useState, useEffect, useContext)
Custom hooks