리스트로 쌓이게 될 컴포넌트의 형태가 위와 같다. 그리고 컴포넌트 전체에 대해서는 onClick 이벤트 핸들러가 할당되어 있고, 그 내부의 빨간색 휴지통 모양 버튼에도 다른 onClick 이벤트 핸들러가 할당되어 있다.
전체 컴포넌트에는 클릭시에 특정 URL로 이동하는 함수가 할당되어 있고, 휴지통 모양 버튼에는 서버에 delete요청을 하는 함수가 할당되어 있었다.
시험삼아 휴지통 버튼을 클릭하자, 서버에서 해당 데이터가 삭제가 되면서 URL이동을 하였다. 당연히 서버에서 삭제된 데이터를 이동한 url의 route에서 읽어오려고 시도하고 있으니 에러 메시지가 발생하였다. 나는 삭제만 하고 싶었던건데.
한 요소에서 이벤트가 발생하면, 이 요소에 할당된 핸들러가 동작하고, 이어서 부모 요소의 핸들러가 동작한다. 가장 최상단의 조상 요소를 만날 때까지 이 과정이 반복되면서 요소 각각에 할당된 핸들러가 동작한다.
ex)
<style>
body * {
margin: 10px;
border: 1px solid blue;
}
</style>
<form onclick="alert('form')">FORM
<div onclick="alert('div')">DIV
<p onclick="alert('p')">P</p>
</div>
</form>
가장 안쪽의 <p>
를 클릭하면 순서대로 다음과 같은 일이 벌어진다.
<p>
에 할당된 onclick 핸들러가 동작한다.<div>
에 할당된 핸들러가 동작한다.<form>
에 할당된 핸들러가 동작한다.이런 흐름을 '이벤트 버블링'이라고 부른다.
event.target과 this(=event.currentTarget)을 구분하여 함수를 할당하면 원하는 요소에만 함수를 실행시킬 수 있다.
form.onclick = function(event) {
event.target.style.backgroundColor = 'yellow';
// chrome needs some time to paint yellow
setTimeout(() => {
alert("target = " + event.target.tagName + ", this=" + this.tagName);
event.target.style.backgroundColor = ''
}, 0);
};
form요소를 정확히 클릭했을 때 -> target = FORM, this = FORM
div요소를 정확히 클릭했을 때 -> target = DIV, this = FORM
p요소를 정확히 클릭했을 때 -> target = P, this = FORM
event.stopPropagation();
이벤트 핸들러로 실행될 함수 내부에서 실행시켜주면 이벤트 버블링을 방지해준다.
ex)
<TodoWrapper
onClick={() => {
navigate(`/Detail/${id}`);
}}
>
<div id='upper-div'>
<div id='todo-title-text'>{title}</div>
<button onClick={(event) => handleDelete(event, id)}>
<DeleteButtonIcon />
</button>
</div>
<div>
<div>{name}</div>
</div>
</TodoWrapper>
const handleDelete = (event, id) => {
event.stopPropagation();
const result = window.confirm("정말로 삭제하시겠습니까?");
result && mutation.mutate(id);
};
이게 내가 겪었던 상황인데 event.stopPropagation() 부분이 없으면 이벤트 버블링 현상으로 삭제를 진행하고 나서 그 위 요소의 event핸들러로 할당되어 있는 navigate함수도 실행되어 버린다.
이렇게 실행시키고 싶은 함수 내부에 event.stopPropagation()를 실행시켜주면 이벤트 버블링을 방지할 수 있다.