React xử lý sự kiện (event) bằng các props đặt tên theo sự kiện ở dạng camelCase, như onClick, onChange và onSubmit. Bạn truyền vào một hàm, và React gọi hàm đó kèm một đối tượng sự kiện khi người dùng click, gõ phím hoặc gửi form.
Mỗi lần click sẽ chạy handleClick, hàm này cập nhật số đếm và ghi log ra console bên dưới phần xem trước. Thử dùng onDoubleClick thay cho onClick: giờ chỉ double click mới được tính.
Truyền hàm, đừng gọi hàm
Prop này nhận một hàm, và React sẽ gọi nó sau. Một lỗi rất phổ biến là thêm dấu ngoặc tròn, khiến hàm bị gọi ngay lập tức trong lúc render, và React nhận giá trị trả về của nó thay vì chính hàm.
Console hiện "Hi, during render" trước khi bạn chạm vào bất cứ thứ gì: React đã chạy sayHi khi render nút đầu tiên, và vì sayHi trả về undefined nên nút đó hoàn toàn không có handler. Click Wrong không làm gì cả. Click Right ghi ra "Hi, Ada", vì arrow function chỉ chạy khi click.
Arrow function cũng là cách để truyền đối số. onClick={() => removeItem(item.id)} tạo một hàm nhỏ cho mỗi phần tử, và hàm đó gọi hàm của bạn với đúng ID. Nếu handler không nhận đối số, hãy truyền nó theo tên: onClick={handleClick}.
Với hàm set state, lỗi này còn tệ hơn: onClick={setCount(count + 1)} đặt state trong lúc render, việc đó gây render lại, lần render đó lại đặt state, và React dừng lại với lỗi "Too many re-renders".
Đối tượng sự kiện
React truyền cho mọi handler một đối số, đó là đối tượng sự kiện. Nó có cùng các thuộc tính như sự kiện của trình duyệt: e.target là phần tử nơi sự kiện xảy ra, e.key là phím vừa được nhấn, e.preventDefault() hủy hành động mặc định của trình duyệt. React bọc sự kiện gốc trong thứ mà nó gọi là synthetic event, để sự kiện hoạt động giống nhau trên mọi trình duyệt; sự kiện gốc nằm ở e.nativeEvent nếu bạn cần đến.
Hai thuộc tính bạn dùng nhiều nhất là e.target.value trong onChange và e.key trong onKeyDown.
Gõ hooks rồi nhấn Enter: thẻ được thêm vào và ô nhập được xóa trống. Console ghi lại mọi phím, kể cả Shift, Backspace và Enter, theo tên trong e.key. Nhấn Escape khi đang gõ dở một từ để bỏ từ đó đi.
onChange của React kích hoạt ở mỗi lần gõ phím. Trong HTML thuần, sự kiện change trên ô văn bản chỉ kích hoạt khi ô mất focus, nên đây là một khác biệt có chủ đích: nó giúp ô nhập và state khớp nhau theo từng ký tự. Checkbox cho bạn e.target.checked thay vì value. Trang form nói về mọi loại ô nhập.
Form và preventDefault
Khi một form được gửi, hành động mặc định của trình duyệt là gửi dữ liệu đi và tải một trang mới. Trong ứng dụng React, gần như lúc nào bạn cũng muốn tự xử lý dữ liệu, nên handler gửi form bắt đầu bằng e.preventDefault().
Đặt onSubmit trên <form>, đừng đặt onClick trên nút: khi đó nhấn Enter trong ô nhập cũng gửi form, và các bước kiểm tra của chính trình duyệt (ở đây là required và type="email") chạy trước. Xóa dòng e.preventDefault() rồi gửi lại: một trang thật lúc này sẽ chuyển sang trang mới và mất lời cảm ơn. Phần xem trước chặn việc đó và hiển thị dữ liệu form sẽ gửi đi, email= không có giá trị, vì setEmail('') đã xóa ô nhập trước khi form được đọc.
React 19 còn cho phép truyền một hàm vào <form action={fn}>, hàm này nhận dữ liệu form và tự đặt lại các ô nhập uncontrolled cho bạn. Trang form trình bày cả hai cách.
Nổi bọt và stopPropagation
Hầu hết sự kiện đều nổi bọt (bubbling): sau khi chạy trên phần tử bạn click, chúng chạy trên phần tử cha, rồi đến cha của cha, cho đến tận trên cùng. Handler của React đi theo đúng thứ tự đó. Vì vậy một cú click vào nút nằm trong một thẻ có thể click được cũng tính là click vào thẻ đó.
Click Bubbles up và console ghi lần lượt nút, khung bên trong, rồi đến thẻ bên ngoài. Click Stops here thì chỉ có nút ghi log: e.stopPropagation() chặn sự kiện leo tiếp. Click vào chữ của chính khung bên trong sẽ ghi log khung đó và thẻ bên ngoài.
Hãy dùng stopPropagation một cách tiết kiệm. Một trường hợp hợp lý thường gặp là nút xóa nằm trong một hàng danh sách có thể click, khi click xóa thì không nên mở luôn hàng đó. Nếu phần tử cha cần biết cú click có đến từ phần tử con hay không, hãy kiểm tra e.target thay vì chặn sự kiện. Để chạy handler trên đường đi xuống, trước khi phần tử con nhận sự kiện, React có các phiên bản capture như onClickCapture.
preventDefault và stopPropagation là hai công cụ khác nhau. Cái đầu hủy việc trình duyệt định làm (mở liên kết, gửi form, đánh dấu checkbox). Cái sau ngăn các handler khác nhận được sự kiện. Gọi cái này không làm thay việc của cái kia.
Các sự kiện thường gặp khác
Cùng một cách viết áp dụng cho mọi sự kiện React hỗ trợ. Vài sự kiện bạn sẽ dùng thường xuyên:
onFocusvàonBlurkích hoạt khi ô nhập nhận và mất focus, tiện để hiện thông báo kiểm tra sau khi người dùng rời một ô.onMouseEntervàonMouseLeavekích hoạt khi con trỏ di vào và rời khỏi một phần tử. Khác với hầu hết sự kiện, chúng không nổi bọt, nên di con trỏ từ phần tử cha vào phần tử con không kích hoạt lạionMouseEntercủa cha.onPointerDown,onPointerMovevàonPointerUphoạt động với chuột, cảm ứng và bút như nhau, nên là lựa chọn tốt hơn cho thao tác kéo.onScrollkích hoạt khi một phần tử cuộn. Trong React, sự kiện này không nổi bọt.
Mỗi handler nhận một đối tượng sự kiện với các thuộc tính phù hợp: e.clientX cho sự kiện con trỏ, e.key cho sự kiện bàn phím, e.target cho tất cả.
Truyền handler qua props
Component có thể nhận handler từ component cha như mọi prop khác. Đặt tên chúng là onSomething theo việc đã xảy ra, và đặt tên hàm xử lý là handleSomething. Cách này giống với các sự kiện có sẵn và cho thấy rõ prop nào là callback.
Stepper không sở hữu con số; nó chỉ báo lại các cú click và component cha quyết định ý nghĩa của chúng. Sửa handleDecrement để dừng ở 1 thay vì 0 và stepper làm theo mà không cần sửa gì. Giữ state ở component cha và truyền handler xuống là cách làm được mô tả trong đưa state lên trên.
Prop handler tự đặt chỉ là cái tên. onIncrement trên component của bạn không làm gì cho đến khi component đó truyền nó vào onClick của một phần tử thật, và bạn có thể truyền cho nó bất kỳ đối số nào, ví dụ onSelect(item.id).
Câu hỏi thường gặp
Làm sao để truyền tham số cho handler onClick?
Bọc lời gọi trong một arrow function: onClick={() => deleteItem(item.id)}. React gọi arrow function đó khi click, rồi nó gọi hàm của bạn kèm đối số.
Vì sao onClick của tôi chạy ngay khi trang vừa tải?
Bạn đã gọi hàm thay vì truyền nó. onClick={handleClick()} chạy handleClick trong lúc render và truyền giá trị trả về của nó. Hãy viết onClick={handleClick} hoặc onClick={() => handleClick(id)}.
onChange trong React khác gì onChange trong HTML?
Trong HTML, sự kiện change trên ô nhập văn bản chỉ kích hoạt khi ô mất focus. onChange của React kích hoạt ở mỗi lần gõ phím, giống sự kiện input gốc, nên state có thể theo kịp văn bản ngay khi gõ.
Làm sao ngăn form tải lại trang trong React?
Gọi e.preventDefault() ở đầu handler onSubmit. Đặt handler trên <form>, không phải trên nút gửi, để nhấn Enter trong một ô nhập cũng gửi được form.
Synthetic event trong React là gì?
Đó là đối tượng sự kiện React truyền cho các handler của bạn. Nó bọc sự kiện gốc của trình duyệt với cùng các thuộc tính và phương thức (target, key, preventDefault(), stopPropagation()) và hoạt động giống nhau trên mọi trình duyệt. Sự kiện gốc nằm ở e.nativeEvent.