Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

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/HTMLReact
<button onclick="buttonClicked()"><button onClick={{buttonClicked}}>
element.addEventListener('click', fn)onClick={{fn}} directly as a prop
Event object shape varies by browserevent 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 JSReact
let count = 0; + manual DOM update everywhere it's usedconst [count, setCount] = useState(0); – React updates automatically
Global state e.g. via a global variable or localStoragestate 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:

src/components/ProductCard.jsx
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.