Menu

리액트 이벤트: onClick, onChange, onSubmit 등

React 이벤트는 onClick, onChange처럼 함수를 받는 props이며, 사용자가 행동하면 React가 이벤트 객체와 함께 그 함수를 호출합니다. 핸들러를 호출하지 않고 넘기는 법, 인자 전달, e.target.value와 e.key 읽기, 폼 새로고침 막기, 버블링 중단을 배웁니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

React는 onClick, onChange, onSubmit처럼 이벤트 이름을 camelCase로 쓴 props로 이벤트를 처리합니다. 함수를 넘기면, 사용자가 클릭하거나 입력하거나 제출할 때 React가 이벤트 객체와 함께 그 함수를 호출합니다.

클릭할 때마다 handleClick이 실행되어 카운트를 업데이트하고 미리보기 아래의 콘솔에 로그를 남깁니다. onClick 대신 onDoubleClick을 써 보세요. 이제 더블 클릭만 카운트됩니다.

함수를 호출하지 말고 넘기세요

prop은 함수를 받고, React가 나중에 그 함수를 호출합니다. 아주 흔한 실수는 괄호를 붙이는 것입니다. 그러면 렌더링 중에 함수가 곧바로 호출되고, React에는 함수 대신 그 반환값이 넘어갑니다.

아무것도 건드리기 전에 콘솔에 "Hi, during render"가 표시됩니다. React가 첫 번째 버튼을 렌더링하면서 sayHi를 실행했고, sayHi는 undefined를 반환하므로 그 버튼에는 핸들러가 전혀 없습니다. Wrong을 클릭해도 아무 일도 일어나지 않습니다. Right를 클릭하면 화살표 함수가 클릭할 때만 실행되므로 "Hi, Ada"가 출력됩니다.

화살표 함수는 인자를 넘기는 방법이기도 합니다. onClick={() => removeItem(item.id)}는 항목마다 작은 함수를 만들고, 그 함수가 올바른 ID로 직접 만든 함수를 호출합니다. 핸들러가 인자를 받지 않는다면 이름으로 넘기세요: onClick={handleClick}.

상태 setter에서는 이 실수가 더 심각합니다. onClick={setCount(count + 1)}은 렌더링 중에 상태를 설정하고, 그러면 다시 렌더링되고, 다시 상태를 설정하므로, React는 "Too many re-renders"와 함께 멈춥니다.

이벤트 객체

React는 모든 핸들러에 인자 하나, 즉 이벤트 객체를 넘깁니다. 브라우저 이벤트와 같은 속성을 가지고 있습니다. e.target은 이벤트가 발생한 요소, e.key는 누른 키, e.preventDefault()는 브라우저의 기본 동작을 취소합니다. React는 네이티브 이벤트를 합성 이벤트라는 것으로 감싸서 모든 브라우저에서 똑같이 동작하게 합니다. 원본이 필요하다면 e.nativeEvent에 있습니다.

가장 많이 쓰는 두 가지는 onChange의 e.target.value와 onKeyDown의 e.key입니다.

hooks를 입력하고 Enter를 누르세요. 태그가 추가되고 입력창이 비워집니다. 콘솔에는 Shift, Backspace, Enter를 포함한 모든 키가 e.key의 이름으로 기록됩니다. 단어를 입력하다가 Escape를 누르면 그 단어를 버립니다.

React의 onChange는 키를 누를 때마다 발생합니다. 일반 HTML에서 텍스트 필드의 change 이벤트는 포커스를 잃을 때만 발생하므로, 이것은 의도된 차이입니다. 덕분에 입력창과 상태가 글자 하나하나 함께 움직입니다. 체크박스는 value 대신 e.target.checked를 줍니다. 폼 페이지에서 모든 입력 타입을 다룹니다.

폼과 preventDefault

폼이 제출되면 브라우저의 기본 동작은 폼을 전송하고 새 페이지를 불러오는 것입니다. React 앱에서는 거의 항상 데이터를 직접 처리하고 싶으므로, 제출 핸들러는 e.preventDefault()로 시작합니다.

버튼에 onClick을 두지 말고 <form>에 onSubmit을 두세요. 그래야 입력창에서 Enter를 눌러도 제출되고, 브라우저 자체의 검사(여기서는 required와 type="email")가 먼저 실행됩니다. e.preventDefault() 줄을 지우고 다시 제출해 보세요. 실제 페이지라면 새 페이지로 이동하면서 감사 메시지를 잃게 됩니다. 미리보기는 그 이동을 막고 대신 폼이 보낼 내용을 보여 줍니다. 값이 없는 email=입니다. 폼을 읽을 때는 이미 setEmail('')이 입력창을 비웠기 때문입니다.

React 19에서는 <form action={fn}>에 함수를 넘길 수도 있습니다. 이 함수는 폼 데이터를 받고, 비제어 필드를 대신 초기화해 줍니다. 폼 페이지에서 두 가지 방식을 모두 보여 줍니다.

버블링과 stopPropagation

대부분의 이벤트는 버블링됩니다. 클릭한 요소에서 실행된 뒤 부모에서, 그다음 부모의 부모에서, 맨 위까지 실행됩니다. React 핸들러도 같은 순서를 따릅니다. 그래서 클릭할 수 있는 카드 안의 버튼을 클릭하면 카드를 클릭한 것으로도 처리됩니다.

Bubbles up을 클릭하면 콘솔에 버튼, 안쪽 패널, 바깥 카드 순으로 로그가 남습니다. Stops here를 클릭하면 버튼만 로그를 남깁니다. e.stopPropagation()이 올라가는 흐름을 끝냅니다. 안쪽 패널의 텍스트 자체를 클릭하면 패널과 카드가 로그를 남깁니다.

stopPropagation은 아껴서 쓰세요. 흔한 정당한 경우는 클릭할 수 있는 목록 행 안의 삭제 버튼입니다. 삭제를 클릭했을 때 행까지 열리면 안 되기 때문입니다. 부모가 클릭이 자식에게서 왔는지 알아야 한다면 이벤트를 멈추지 말고 e.target을 확인하세요. 자식이 이벤트를 보기 전에, 내려가는 단계에서 핸들러를 실행하려면 React가 제공하는 onClickCapture 같은 캡처 버전을 사용합니다.

preventDefault와 stopPropagation은 서로 다른 도구입니다. 앞의 것은 브라우저가 할 일(링크 이동, 폼 제출, 체크박스 체크)을 취소합니다. 뒤의 것은 다른 핸들러가 이벤트를 듣지 못하게 막습니다. 하나를 호출한다고 다른 하나가 되지는 않습니다.

그 밖의 자주 쓰는 이벤트

React가 지원하는 모든 이벤트에 같은 패턴이 적용됩니다. 자주 쓰게 될 몇 가지는 다음과 같습니다.

  • onFocus와 onBlur는 입력창이 포커스를 얻고 잃을 때 발생하며, 사용자가 필드를 떠난 뒤 유효성 검사 결과를 보여 줄 때 편리합니다.
  • onMouseEnter와 onMouseLeave는 포인터가 요소 위로 들어오고 나갈 때 발생합니다. 대부분의 이벤트와 달리 버블링되지 않으므로, 포인터를 부모에서 자식으로 옮겨도 부모의 onMouseEnter가 다시 발생하지 않습니다.
  • onPointerDown, onPointerMove, onPointerUp은 마우스, 터치, 펜 모두에서 동작하므로 드래그에 더 적합합니다.
  • onScroll은 요소가 스크롤될 때 발생합니다. React에서는 버블링되지 않습니다.

각 핸들러는 이벤트에 맞는 속성을 가진 이벤트 객체를 받습니다. 포인터 이벤트에는 e.clientX, 키보드 이벤트에는 e.key, 그리고 모든 이벤트에 e.target이 있습니다.

핸들러를 props로 넘기기

컴포넌트는 다른 prop처럼 부모에게서 핸들러를 받을 수 있습니다. 일어난 일에 따라 onSomething으로 이름 짓고, 그것을 처리하는 함수는 handleSomething으로 이름 지으세요. 내장 이벤트와 같은 모양이라서 어떤 props가 콜백인지 한눈에 알 수 있습니다.

Stepper는 숫자를 소유하지 않습니다. 클릭을 알리기만 하고, 그 의미는 부모가 정합니다. handleDecrement를 0 대신 1에서 멈추도록 바꾸면 stepper는 수정하지 않아도 그대로 따라갑니다. 상태를 부모에 두고 핸들러를 아래로 넘기는 것이 상태 끌어올리기에서 설명하는 패턴입니다.

직접 만든 핸들러 props는 이름일 뿐입니다. 직접 만든 컴포넌트의 onIncrement는 컴포넌트가 그것을 실제 요소의 onClick에 넘기기 전까지 아무 일도 하지 않으며, onSelect(item.id)처럼 원하는 인자를 무엇이든 줄 수 있습니다.

자주 묻는 질문

onClick 핸들러에 매개변수를 넘기려면 어떻게 하나요?

호출을 화살표 함수로 감싸세요: onClick={() => deleteItem(item.id)}. 클릭할 때 React가 호출하는 것은 화살표 함수이고, 화살표 함수가 인자와 함께 직접 만든 함수를 호출합니다.

페이지가 로드될 때 onClick이 실행되는 이유는 무엇인가요?

함수를 넘기지 않고 호출했기 때문입니다. onClick={handleClick()}은 렌더링 중에 handleClick을 실행하고 그 반환값을 넘깁니다. onClick={handleClick}이나 onClick={() => handleClick(id)}로 쓰세요.

React의 onChange와 HTML의 onChange는 무엇이 다른가요?

HTML에서 텍스트 입력창의 change 이벤트는 입력창이 포커스를 잃을 때 발생합니다. React의 onChange는 네이티브 input 이벤트처럼 키를 누를 때마다 발생하므로, 입력하는 텍스트를 상태가 그대로 따라갈 수 있습니다.

React에서 폼이 페이지를 새로고침하지 않게 하려면 어떻게 하나요?

onSubmit 핸들러의 시작 부분에서 e.preventDefault()를 호출하세요. 핸들러는 제출 버튼이 아니라 <form>에 두어야 입력창에서 Enter를 눌렀을 때도 제출됩니다.

React의 합성 이벤트(synthetic event)란 무엇인가요?

React가 핸들러에 넘기는 이벤트 객체입니다. 브라우저의 네이티브 이벤트를 같은 속성과 메서드(target, key, preventDefault(), stopPropagation())로 감싸며, 모든 브라우저에서 똑같이 동작합니다. 원본은 e.nativeEvent로 접근할 수 있습니다.

Coddy 프로그래밍 언어 일러스트

Coddy로 코딩 배우기

시작하기