AiTechWorlds
AiTechWorlds
A SyntheticEvent is React's cross browser wrapper around the browser's native event object, passed automatically to every event handler you write.
It behaves like a universal power adapter plug any device (browser) into it, and it presents the same interface regardless of what's underneath. For most purposes a SyntheticEvent behaves identically to the native event, but knowing it exists prevents subtle bugs around event pooling, bubbling, and asynchronous handlers.
Pass a function to the event prop not a string like in old HTML:
// Wrong: HTML-style string (doesn't work in React)
<button onclick="handleClick()">Click</button>
// Correct: React — pass the function reference
<button onClick={handleClick}>Click</button>
// Or inline arrow function
<button onClick={() => console.log("clicked")}>Click</button>
// With an argument
<button onClick={() => handleDelete(item.id)}>Delete</button>The function receives a SyntheticEvent as its first argument:
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
e.preventDefault();
console.log(e.target, e.currentTarget);
}// Mouse events
<div onClick={handleClick}>
<div onDoubleClick={handleDoubleClick}>
<div onMouseEnter={() => setHovered(true)} onMouseLeave={() => setHovered(false)}>
<div onMouseDown={handleDragStart} onMouseUp={handleDragEnd}>
// Keyboard events
<input onKeyDown={handleKeyDown} onKeyUp={handleKeyUp} onKeyPress={handleKeyPress} />
// Focus events
<input onFocus={() => setFocused(true)} onBlur={() => setFocused(false)} />
// Form events
<form onSubmit={handleSubmit}>
<input onChange={handleChange} />
<select onChange={handleChange}>
// Drag events
<div draggable onDragStart={handleDragStart} onDrop={handleDrop} onDragOver={e => e.preventDefault()}>
// Touch events (mobile)
<div onTouchStart={handleTouchStart} onTouchEnd={handleTouchEnd}>The most common event reading input values:
function ContactForm() {
const [form, setForm] = useState({ name: "", email: "", message: "" });
// Generic handler for all text inputs
function handleChange(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
const { name, value } = e.target;
setForm(prev => ({ ...prev, [name]: value }));
}
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault(); // prevent page reload
// form.name, form.email, form.message are ready
submitForm(form);
}
return (
<form onSubmit={handleSubmit}>
<input name="name" value={form.name} onChange={handleChange} />
<input name="email" type="email" value={form.email} onChange={handleChange} />
<textarea name="message" value={form.message} onChange={handleChange} />
<button type="submit">Send</button>
</form>
);
}Respond to specific keys:
function SearchInput() {
const [query, setQuery] = useState("");
function handleKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
if (e.key === "Enter") {
performSearch(query);
}
if (e.key === "Escape") {
setQuery("");
}
// Modifier keys
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
e.preventDefault();
openCommandPalette();
}
}
return (
<input
value={query}
onChange={e => setQuery(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="Search..."
/>
);
}Events bubble up the DOM tree by default. A click on a button inside a card triggers the card's onClick too:
function CourseCard({ course, onCardClick, onEnrollClick }: Props) {
return (
<div onClick={onCardClick} className="cursor-pointer">
<h3>{course.title}</h3>
{/* This click would also trigger onCardClick! */}
<button
onClick={(e) => {
e.stopPropagation(); // stop bubbling to the parent div
onEnrollClick(course.id);
}}
>
Enroll
</button>
</div>
);
}e.stopPropagation() stops the event from reaching parent handlers.
e.preventDefault() stops the browser's default behavior (form submitting, link navigating, etc.).
They're independent you can call one, both, or neither.
React uses event delegation it attaches a single listener to the root instead of one per element. This means even a list of 10,000 items has only one event listener:
// This is fine for large lists — React handles it efficiently
function ItemList({ items }: { items: Item[] }) {
function handleClick(id: string) {
setSelected(id);
}
return (
<ul>
{items.map(item => (
// No performance problem having onClick in a large list
<li key={item.id} onClick={() => handleClick(item.id)}>
{item.name}
</li>
))}
</ul>
);
}React doesn't have custom events like the DOM does instead, pass callback props:
// "Events" in React are just callback functions
function SearchBar({ onSearch }: { onSearch: (query: string) => void }) {
const [query, setQuery] = useState("");
return (
<form onSubmit={e => { e.preventDefault(); onSearch(query); }}>
<input value={query} onChange={e => setQuery(e.target.value)} />
<button type="submit">Search</button>
</form>
);
}
function CoursesPage() {
const [results, setResults] = useState<Course[]>([]);
async function handleSearch(query: string) {
const data = await fetchCourses({ search: query });
setResults(data);
}
return (
<div>
<SearchBar onSearch={handleSearch} />
<CourseGrid courses={results} />
</div>
);
}// This looks fine but has a problem
<button onClick={async () => {
setLoading(true);
await submitForm(data);
setLoading(false);
}}>
Submit
</button>
// If the user clicks multiple times quickly, you can get race conditions
// Better: disable while loading
<button
disabled={loading}
onClick={async () => {
setLoading(true);
try {
await submitForm(data);
} finally {
setLoading(false);
}
}}
>
{loading ? "Submitting..." : "Submit"}
</button>For drag and drop and touch interfaces:
function DraggableCard({ id, children }: { id: string; children: React.ReactNode }) {
const [isDragging, setIsDragging] = useState(false);
return (
<div
draggable
onDragStart={(e) => {
e.dataTransfer.setData("text/plain", id);
setIsDragging(true);
}}
onDragEnd={() => setIsDragging(false)}
className={`cursor-grab ${isDragging ? "opacity-50" : ""}`}
>
{children}
</div>
);
}
function DropZone({ onDrop }: { onDrop: (id: string) => void }) {
const [isOver, setIsOver] = useState(false);
return (
<div
onDragOver={e => { e.preventDefault(); setIsOver(true); }}
onDragLeave={() => setIsOver(false)}
onDrop={e => {
e.preventDefault();
setIsOver(false);
const id = e.dataTransfer.getData("text/plain");
onDrop(id);
}}
className={`border-2 border-dashed rounded-xl p-8 text-center transition-colors ${
isOver ? "border-blue-500 bg-blue-50" : "border-gray-300"
}`}
>
Drop here
</div>
);
}Next lesson: Context API and useContext sharing state across components without prop drilling.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises