본문으로 건너뛰기
fe.run

button의 type을 언제 뭐 쓸까

글 복사 완료!

submit, reset, button. 폼 안에서 type 하나로 버튼이 완전히 달라져요.

·14분·

삭제 버튼 하나를 폼 안에 넣었을 뿐인데, 누르면 페이지가 새로고침되면서 입력하던 내용이 다 날아가요. 저도 처음엔 자바스크립트 어딘가에 버그가 있나 한참을 뒤졌거든요. 근데 범인은 buttontype 이었어요. 폼 안의 buttontype 을 안 정하면 제출 버튼으로 동작해서, 목적에 맞는 type 과 이벤트 핸들러를 골라야 의도대로 움직여요.

폼 안 버튼은 왜 마음대로 새로고침될까

buttontype 은 세 가지예요. submit 은 폼을 제출하고, reset 은 폼을 초기값으로 되돌리고, button 은 아무 기본 동작이 없어요. 문제는 폼 안에서 type 을 지정하지 않으면 그 값이 submit 으로 정해진다는 거예요. 그러니까 별생각 없이 버튼을 만들면, 클릭할 때마다 폼이 제출되면서 페이지가 통째로 다시 로드돼요.

위 데모에서 type 없음 버튼은 눌러보면 submit 이벤트가 뜨죠. 반면 type=button 은 우리가 붙인 onClick 만 실행하고 폼은 건드리지 않아요. MDN 도 이 점을 짚어요.

"If your buttons are not for submitting form data to a server, be sure to set their type attribute to button." - MDN

서버로 폼을 보내는 버튼이 아니면 type="button" 을 꼭 붙이라는 거예요. 안 그러면 존재하지도 않는 응답을 부르려다 지금 화면 상태까지 날려버리거든요.

엄밀히 말하면 최신 명세는 기본값을 곧바로 submit 이라 부르지 않고 Auto 상태로 정의해요. 이 상태가 몇 가지 조건을 만족하면 제출 버튼으로 취급되는 거죠. 실무에서 외울 건 하나예요. 폼 안의 일반 버튼은 누르면 제출돼요.

submit은 클릭이 아니라 제출에 반응해요

그럼 제출 로직은 어디에 둬야 할까요. 버튼의 onClick 이 아니라 폼의 onSubmit 이에요. submit 이벤트는 제출 버튼을 클릭할 때만 뜨는 게 아니라, 입력창에서 Enter 를 눌러도 발동하거든요. 로직을 버튼 클릭에 걸어두면 Enter 로 제출하는 사용자를 놓쳐요.

제출 횟수가 올라가죠. onSubmit 핸들러 안에서는 preventDefault() 로 브라우저의 기본 제출, 즉 페이지 이동을 막아요. 요즘처럼 자바스크립트로 폼을 다루는 앱에서는 이 한 줄이 새로고침을 막아주거든요. 참고로 핸들러가 받는 이벤트 객체의 targetcurrentTarget 이 왜 다른지는 따로 정리한 글에서 다뤘어요.

한 가지 함정이 더 있어요. 스크립트에서 폼을 직접 보내려고 form.submit() 을 부르면, 정작 submit 이벤트가 안 떠요.

"the event is not sent to the form when a script calls the form.submit() method directly." - MDN

form.submit() 은 검증도 onSubmit 훅도 다 건너뛰고 곧장 보내버려요. 이벤트를 정상적으로 태우고 싶으면 requestSubmit() 을 쓰세요. 이 메서드는 사용자가 제출 버튼을 누른 것처럼 동작하거든요.

type=reset은 비우는 게 아니라 되돌려요

reset 버튼도 오해가 많아요. 폼을 텅 비우는 게 아니라, 각 필드를 처음 지정된 초기값으로 되돌려요. defaultValue 가 있으면 그 값으로 돌아가는 거지, 무조건 빈칸이 되는 게 아니에요.

여기서 제어 컴포넌트와 비제어 컴포넌트의 차이가 갈려요. 이 둘의 구분은 지난 글에서 자세히 다뤘는데, 리셋 동작에서 특히 도드라져요. 비제어 입력은 값이 DOM 에 있으니 네이티브 리셋이 그대로 먹혀요. 근데 제어 입력은 값이 React state 에 있어서, type="reset" 이 DOM 을 되돌려도 state 가 그대로면 화면이 안 바뀌어요.

제어 폼에서 초기화가 필요하면 폼의 onReset 핸들러 안에서 직접 state 를 되돌려야 해요. 네이티브 리셋에만 기대면 아무 일도 안 일어나거든요.

<form onReset={() => setName("")}>
  <input value={name} onChange={(e) => setName(e.target.value)} />
  <button type="reset">초기화</button>
</form>

사실 reset 버튼 자체를 잘 안 쓰기도 해요. 사용자가 실수로 눌러서 애써 채운 걸 날리는 사고가 잦거든요. "취소" 같은 버튼은 보통 type="button" 에 직접 만든 로직을 붙이는 편이에요.

그래서 언제 무엇을 쓰나

정리하면 상황별로 손이 가는 조합이 정해져 있어요.

상황권장 조합이유
폼 데이터 전송<form onSubmit> + <button type="submit">Enter 제출까지 한 번에 잡혀요
폼 안의 일반 액션 (삭제 등)<button type="button" onClick>실수로 폼이 제출되는 걸 막아요
비제어 폼 초기화<button type="reset">입력을 defaultValue 로 되돌려요
제어 폼 초기화type="button" + state 리셋 함수네이티브 리셋은 state 를 못 되돌려요
폼 밖 단순 버튼<button type="button">폼 밖에서는 기본이 button 이라 안전

button 은 겉보기엔 하나지만, type 을 뭘로 두느냐가 폼 안에서 완전히 다른 버튼을 만들어요. 제출이 목적이면 submit 을 두고 로직은 폼의 onSubmit 에 얹으면 돼요. 제출이 아닌 액션은 type="button"onClick 을 붙이고요. 초기화는 위 표대로 제어냐 비제어냐만 가르면 끝이에요. 이 갈래가 손에 익으면 "왜 새로고침되지" 같은 질문은 다시 안 하게 돼요.

자주 묻는 질문

답을 펼치기 전에 스스로 답해보세요

폼 안에 있는 버튼일 때만 필요해요. 폼 밖의 버튼은 기본 type이 button이라 안 붙여도 제출될 일이 없어요. 폼 안에서 제출이 목적이 아닌 버튼에만 붙이면 됩니다.
동작은 해요. 다만 입력창에서 Enter로 제출하는 사용자를 놓쳐요. submit 이벤트는 버튼 클릭과 Enter 양쪽에서 발동하니까, 제출 로직은 버튼의 onClick이 아니라 폼의 onSubmit에 두는 게 안전해요.

참고 자료

관련 글