State and Event Handling in React
State and Event Handling
~10 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026
So far our app has been static – it always displays the same values. Real apps need to remember and react to change: a cart counter, a favorite heart, an expanded menu. That's what useState is for.
Event handling: from onclick to onClick
In HTML/vanilla JS you react to clicks with onclick="..." (as a string) or element.addEventListener('click', ...). In React you instead use camelCase props like onClick, which you give a real JavaScript function:
| Vanilla JS/HTML | React |
|---|---|
<button onclick="buttonClicked()"> | <button onClick={{buttonClicked}}> |
element.addEventListener('click', fn) | onClick={{fn}} directly as a prop |
| Event object shape varies by browser | event object is unified ("SyntheticEvent"), works the same everywhere |
function ClickCounter() {
function handleClick() {
console.log('Clicked!');
}
return <button onClick={handleClick}>Click me</button>;
}Achtung: Common beginner mistake: onClick={{handleClick()}} (with parentheses) instead of onClick={{handleClick}} (without). With parentheses, the function runs IMMEDIATELY during rendering, not on click – you're not passing React the function itself, but its return value.
useState() basics
React never directly changes what's displayed – you change a state, and React automatically updates everything that depends on it. Comparable to a spreadsheet cell that automatically recalculates when another cell it references changes.
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Current value: {count}</p>
<button onClick={() => setCount(count + 1)}>+ 1</button>
</div>
);
}useState(0) creates a state value that starts at 0 and returns an array with two elements: the current value (count) and a function to change it (setCount). You NEVER call count = 5 directly – always through the setter function. Every call to setCount(...) makes React redraw ("re-render") the component with the new value.
| Vanilla JS | React |
|---|---|
let count = 0; + manual DOM update everywhere it's used | const [count, setCount] = useState(0); – React updates automatically |
Global state e.g. via a global variable or localStorage | state stays local by default, inside the component that created it |
Extending ProductCard.jsx with favorite state
Now let's combine both: a click handler AND useState. Replace the content of src/components/ProductCard.jsx with this extended version:
import { useState } from 'react';
function ProductCard({ name, price, imageUrl, onSelect }) {
const [isFavorite, setIsFavorite] = useState(false);
function handleFavoriteClick(event) {
event.stopPropagation(); // prevents the card's onSelect from also firing
setIsFavorite(!isFavorite);
}
return (
<div className="product-card" onClick={onSelect}>
<img src={imageUrl} alt={name} className="product-card__image" />
<div className="product-card__info">
<h3 className="product-card__name">{name}</h3>
<p className="product-card__price">${price.toFixed(2)}</p>
</div>
<button className="product-card__favorite" onClick={handleFavoriteClick}>
{isFavorite ? '♥' : '♡'}
</button>
</div>
);
}
export default ProductCard;event.stopPropagation() matters here and is a difference from React Native: in the browser, click events "bubble" upward through nested elements by default ("event bubbling") – without this line, clicking the heart icon would ALSO trigger the outer card's onClick. {{isFavorite ? '♥' : '♡'}} is a "ternary operator", the compact shorthand for if/else inside an expression.
Achtung: Every component instance has its OWN, independent state. Once we display several <ProductCard /> at once starting in chapter 8, each has its own isFavorite state – tapping the heart on one card doesn't affect the others.