Phản hồi các sự kiện
React cho bạn thêm các hàm xử lý sự kiện vào JSX. Hàm xử lý sự kiện là các hàm bạn tự định nghĩa mà sẽ được kích hoạt để phản hồi lại các tương tác như nhấn chuột, hover chuột hay focus các trường input của form, và các tương tác tương tự.
You will learn
- Những cách khác nhau để viết một hàm xử lý sự kiện
- Cách truyền logic xử lý sự kiện từ một component cha
- Cách các sự kiện lan truyền và cách dừng sự lan truyền sự kiện
Thêm các hàm xử lý sự kiện
Để thêm các hàm xử lý sự kiện, bạn sẽ cần khai báo hàm rồi truyền nó như một prop tới thẻ JSX thích hợp. Ví dụ, đây là một nút hiện tại chưa có chức năng gì:
export default function Button() { return ( <button> I don't do anything </button> ); }
Bạn có thể làm nó hiển thị một lời nhắn khi người dùng nhấn vào qua các bước sau:
- Khai báo một hàm
handleClick
bên trong componentButton
của bạn - Thực thi logic bên trong hàm đó (sử dụng
alert
để hiện lời nhắn) - Thêm
onClick={handleClick}
vào thẻ JSX<button>
export default function Button() { function handleClick() { alert('You clicked me!'); } return ( <button onClick={handleClick}> Click me </button> ); }
Bạn đã định nghĩa hàm handleClick
rồi truyền nó như một prop tới <button>
. handleClick
là một hàm xử lý sự kiện. Hàm xử lý sự kiện:
- Thường được định nghĩa bên trong các component của bạn.
- Có tên bắt đầu với
handle
, theo sau đó là tên sự kiện.
Theo quy chuẩn, ta thường đặt tên các hàm xử lý sự kiện là handle
rồi đến tên sự kiện. Bạn sẽ hay thấy onClick={handleClick}
, onMouseEnter={handleMouseEnter}
, …
Cách khác, bạn có thể định nghĩa một hàm xử lý sự kiện theo kiểu inline trong JSX như sau:
<button onClick={function handleClick() {
alert('You clicked me!');
}}>
Hoặc, ngắn gọn hơn, sử dụng hàm mũi tên:
<button onClick={() => {
alert('You clicked me!');
}}>
Tất cả cách viết trên đều như nhau. Các hàm xử lý sự kiện inline sẽ tiện hơn cho các hàm ngắn.
Đọc props trong các hàm xử lý sự kiện
Vì các hàm xử lý sự kiện được khai báo trong một component, chúng có quyền truy cập vào các props của component. Đây là một nút mà khi nhấn, hiện ra một alert với prop message
của nó:
function AlertButton({ message, children }) { return ( <button onClick={() => alert(message)}> {children} </button> ); } export default function Toolbar() { return ( <div> <AlertButton message="Đang phát!"> Phát phim </AlertButton> <AlertButton message="Đang tải!"> Tải ảnh lên </AlertButton> </div> ); }
Điều này sẽ cho hai nút hiển thị hai lời nhắn khác nhau. Hãy thử thay đổi lời nhắn (message
) được truyền cho các nút.
Truyền hàm xử lý sự kiện như props
Thông thường, bạn sẽ muốn component cha chỉ định hàm xử lý sự kiện của component con. Xem xét các nút: tuỳ thuộc vào nơi bạn đang sử dụng component Button
, bạn có thể muốn thực thi một hàm khác-có thể là một nút phát phim còn một nút khác tải ảnh lên.
To do this, pass a prop the component receives from its parent as the event handler like so: Để làm được điều này, truyền một prop mà component nhận từ cha của nó như một hàm xử lý sự kiện:
function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); } function PlayButton({ movieName }) { function handlePlayClick() { alert(`Đang phát ${movieName}!`); } return ( <Button onClick={handlePlayClick}> Phát "{movieName}" </Button> ); } function UploadButton() { return ( <Button onClick={() => alert('Đang tải!')}> Tải ảnh lên </Button> ); } export default function Toolbar() { return ( <div> <PlayButton movieName="Kiki's Delivery Service" /> <UploadButton /> </div> ); }
Tại đây, component Toolbar
render một PlayButton
và một UploadButton
:
PlayButton
truyềnhandlePlayClick
như một proponClick
tớiButton
bên trong.UploadButton
truyền() => alert('Uploading!')
như một proponClick
tớiButton
bên trong.
Cuối cùng, component Button
của bạn nhận một prop được gọi là onClick
. Nó truyền prop đó trực tiếp tới thẻ <button>
có sẵn của trình duyệt với onClick={onClick}
. Điều này bảo React gọi hàm được truyền khi nhấn nút.
Nếu bạn sử dụng một hệ thiết kế, thông thường các component như các nút (Button
) chứa styling chứ không chỉ định hành vi. Thay vào đó, các component như PlayButton
và UploadButton
sẽ truyền hàm xử lý sự kiện xuống Button
.
Đặt tên cho các props hàm xử lý sự kiện
Các component có sẵn như <button>
và <div>
chỉ hỗ trợ các tên sự kiện của trình duyệt như onClick
. Tuy nhiên, khi xây dựng những component của riêng mình, bạn có thể đặt tên cho các props hàm xử lý sự kiện của các component đó tuỳ ý.
Theo quy chuẩn, các props hàm xử lý sự kiện nên bắt đầu bằng on
, theo sau đó là chữ cái viết hoa.
Ví dụ, prop onClick
của component Button
có thể được gọi là onSmash
:
function Button({ onSmash, children }) { return ( <button onClick={onSmash}> {children} </button> ); } export default function App() { return ( <div> <Button onSmash={() => alert('Đang phát!')}> Phát phim </Button> <Button onSmash={() => alert('Đang tải!')}> Tải ảnh lên </Button> </div> ); }
Ở ví dụ này, <button onClick={onSmash}>
cho ta thấy <button>
(viết thường) của trình duyệt vẫn cần một prop gọi là onClick
, nhưng tên prop nhận bởi component tuỳ chỉnh Button
là do bạn quyết định!
Khi component của bạn hỗ trợ nhiều tương tác, bạn có thể đặt props hàm xử lý sự kiện cho các khái niệm riêng của ứng dụng. Ví dụ, component Toolbar
này nhận hàm xử lý sự kiện onPlayMovie
và onUploadImage
:
export default function App() { return ( <Toolbar onPlayMovie={() => alert('Đang phát!')} onUploadImage={() => alert('Đang tải!')} /> ); } function Toolbar({ onPlayMovie, onUploadImage }) { return ( <div> <Button onClick={onPlayMovie}> Phát phim </Button> <Button onClick={onUploadImage}> Tải ảnh lên </Button> </div> ); } function Button({ onClick, children }) { return ( <button onClick={onClick}> {children} </button> ); }
Hãy để ý cách component App
không cần biết Toolbar
sẽ làm gì với onPlayMovie
hoặc onUploadImage
. Đó là chi tiết thực thi của riêng Toolbar
. Ở đây, Toolbar
truyền chúng bằng các props hàm xử lý onClick
xuống các Button
của Toolbar
, nhưng Toolbar
cũng có thể kích hoạt chúng sau trên phím tắt của bàn phím. Đặt tên props theo các tương tác riêng của ứng dụng như onPlayMovie
cho bạn sự linh hoạt trong việc thay đổi cách sử dụng chúng sau này.
Event propagation
Event handlers will also catch events from any children your component might have. We say that an event “bubbles” or “propagates” up the tree: it starts with where the event happened, and then goes up the tree.
This <div>
contains two buttons. Both the <div>
and each button have their own onClick
handlers. Which handlers do you think will fire when you click a button?
export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('You clicked on the toolbar!'); }}> <button onClick={() => alert('Playing!')}> Play Movie </button> <button onClick={() => alert('Uploading!')}> Upload Image </button> </div> ); }
If you click on either button, its onClick
will run first, followed by the parent <div>
’s onClick
. So two messages will appear. If you click the toolbar itself, only the parent <div>
’s onClick
will run.
Stopping propagation
Event handlers receive an event object as their only argument. By convention, it’s usually called e
, which stands for “event”. You can use this object to read information about the event.
That event object also lets you stop the propagation. If you want to prevent an event from reaching parent components, you need to call e.stopPropagation()
like this Button
component does:
function Button({ onClick, children }) { return ( <button onClick={e => { e.stopPropagation(); onClick(); }}> {children} </button> ); } export default function Toolbar() { return ( <div className="Toolbar" onClick={() => { alert('You clicked on the toolbar!'); }}> <Button onClick={() => alert('Playing!')}> Play Movie </Button> <Button onClick={() => alert('Uploading!')}> Upload Image </Button> </div> ); }
When you click on a button:
- React calls the
onClick
handler passed to<button>
. - That handler, defined in
Button
, does the following:- Calls
e.stopPropagation()
, preventing the event from bubbling further. - Calls the
onClick
function, which is a prop passed from theToolbar
component.
- Calls
- That function, defined in the
Toolbar
component, displays the button’s own alert. - Since the propagation was stopped, the parent
<div>
’sonClick
handler does not run.
As a result of e.stopPropagation()
, clicking on the buttons now only shows a single alert (from the <button>
) rather than the two of them (from the <button>
and the parent toolbar <div>
). Clicking a button is not the same thing as clicking the surrounding toolbar, so stopping the propagation makes sense for this UI.
Deep Dive
In rare cases, you might need to catch all events on child elements, even if they stopped propagation. For example, maybe you want to log every click to analytics, regardless of the propagation logic. You can do this by adding Capture
at the end of the event name:
<div onClickCapture={() => { /* this runs first */ }}>
<button onClick={e => e.stopPropagation()} />
<button onClick={e => e.stopPropagation()} />
</div>
Each event propagates in three phases:
- It travels down, calling all
onClickCapture
handlers. - It runs the clicked element’s
onClick
handler. - It travels upwards, calling all
onClick
handlers.
Capture events are useful for code like routers or analytics, but you probably won’t use them in app code.
Passing handlers as alternative to propagation
Notice how this click handler runs a line of code and then calls the onClick
prop passed by the parent:
function Button({ onClick, children }) {
return (
<button onClick={e => {
e.stopPropagation();
onClick();
}}>
{children}
</button>
);
}
You could add more code to this handler before calling the parent onClick
event handler, too. This pattern provides an alternative to propagation. It lets the child component handle the event, while also letting the parent component specify some additional behavior. Unlike propagation, it’s not automatic. But the benefit of this pattern is that you can clearly follow the whole chain of code that executes as a result of some event.
If you rely on propagation and it’s difficult to trace which handlers execute and why, try this approach instead.
Preventing default behavior
Some browser events have default behavior associated with them. For example, a <form>
submit event, which happens when a button inside of it is clicked, will reload the whole page by default:
export default function Signup() { return ( <form onSubmit={() => alert('Submitting!')}> <input /> <button>Send</button> </form> ); }
You can call e.preventDefault()
on the event object to stop this from happening:
export default function Signup() { return ( <form onSubmit={e => { e.preventDefault(); alert('Submitting!'); }}> <input /> <button>Send</button> </form> ); }
Don’t confuse e.stopPropagation()
and e.preventDefault()
. They are both useful, but are unrelated:
e.stopPropagation()
stops the event handlers attached to the tags above from firing.e.preventDefault()
prevents the default browser behavior for the few events that have it.
Can event handlers have side effects?
Absolutely! Event handlers are the best place for side effects.
Unlike rendering functions, event handlers don’t need to be pure, so it’s a great place to change something—for example, change an input’s value in response to typing, or change a list in response to a button press. However, in order to change some information, you first need some way to store it. In React, this is done by using state, a component’s memory. You will learn all about it on the next page.
Recap
- You can handle events by passing a function as a prop to an element like
<button>
. - Event handlers must be passed, not called!
onClick={handleClick}
, notonClick={handleClick()}
. - You can define an event handler function separately or inline.
- Event handlers are defined inside a component, so they can access props.
- You can declare an event handler in a parent and pass it as a prop to a child.
- You can define your own event handler props with application-specific names.
- Events propagate upwards. Call
e.stopPropagation()
on the first argument to prevent that. - Events may have unwanted default browser behavior. Call
e.preventDefault()
to prevent that. - Explicitly calling an event handler prop from a child handler is a good alternative to propagation.
Challenge 1 of 2: Fix an event handler
Clicking this button is supposed to switch the page background between white and black. However, nothing happens when you click it. Fix the problem. (Don’t worry about the logic inside handleClick
—that part is fine.)
export default function LightSwitch() { function handleClick() { let bodyStyle = document.body.style; if (bodyStyle.backgroundColor === 'black') { bodyStyle.backgroundColor = 'white'; } else { bodyStyle.backgroundColor = 'black'; } } return ( <button onClick={handleClick()}> Toggle the lights </button> ); }