React 이벤트 처리 방법: onClick·onChange·onSubmit 예제
리액트 이벤트는 브라우저 이벤트를 SyntheticEvent로 표준화해 제공합니다. JSX에서는 카멜케이스 속성에 함수 레퍼런스를 전달해야 하며, 상황에 따라 preventDefault()로 기본 동작을 막거나 stopPropagation()으로 전파를 차단할 수 있습니다. 아래 예제로 onClick·onChange·onSubmit의 핵심 패턴을 바로 실습해 보세요.
1. 리액트 이벤트 기본기
리액트는 브라우저 이벤트를 SyntheticEvent로 감싸 브라우저 간 차이를 줄이고 일관된 API를 제공합니다(React 17+에서는 더 이상 이벤트 풀링을 하지 않습니다). 이벤트 처리 시 규칙은 다음과 같습니다.
- 속성은 카멜케이스로 작성합니다:
onclick❌ →onClick✅ - 문자열이 아닌 함수 레퍼런스를 전달해야 합니다:
onClick={handleClick} - 핸들러에서
event객체(SyntheticEvent)를 받아preventDefault/stopPropagation을 사용할 수 있습니다. - 버튼이 폼 안에 있으면 기본 타입이
submit이므로, 의도치 않은 제출을 막으려면type="button"을 지정하세요.
1-1. onClick 기본 패턴
클릭마다 카운트를 증가하고, 전파 차단·기본 동작 방지까지 한 번에 확인하는 예제입니다.
import React, { useState } from 'react';
function ClickExamples() {
const [count, setCount] = useState(0);
// 분리한 핸들러 (권장)
const handleIncrement = () => setCount((c) => c + 1);
// 매개변수 전달 (화살표로 감싸기)
const handleGreet = (name) => {
alert(`안녕하세요, ${name}!`);
};
// 전파 차단 / 기본동작 취소 예시
const handleOuter = () => alert('바깥 DIV 클릭');
const handleInner = (e) => {
e.stopPropagation(); // 버블링 중단
alert('버튼만 처리 (전파 차단됨)');
};
const handleLink = (e) => {
e.preventDefault(); // a 태그 기본 이동 막기
alert('페이지 이동 대신 커스텀 로직 실행');
};
return (
<section style={{border:'1px solid #ddd', padding:12, borderRadius:8}}>
<h4>onClick 예제</h4>
<p>카운트: {count}</p>
<button onClick={handleIncrement} type="button">+1</button>
<button onClick={() => handleGreet('BlueShareHub')} type="button">인사하기</button>
<div onClick={handleOuter} style={{marginTop:12, padding:12, background:'#f8f8f8'}}>
<button onClick={handleInner} type="button">전파 차단 테스트</button>
</div>
<p style={{marginTop:12}}>
<a href="https://bluesharehub.com" onClick={handleLink}>bluesharehub.com 로 이동 (막힘)</a>
</p>
</section>
);
}
export default ClickExamples;팁: 무거운 콜백을 자식 컴포넌트에 내려줄 때는 useCallback으로 메모이제이션해 불필요한 리렌더링을 줄일 수 있습니다.
▼ 실행 결과 화면

2. onChange: Controlled 입력 관리
onChange는 입력값이 바뀔 때마다 호출됩니다. 입력값을 state로 관리하는 패턴을 Controlled Component라고 하며, value = state, onChange에서 setState가 핵심입니다.
import React, { useState } from 'react';
function ChangeExamples() {
const [name, setName] = useState('');
const [age, setAge] = useState('');
const [agree, setAgree] = useState(false);
const [color, setColor] = useState('red');
// input name 속성으로 핸들러 통합하기 (선택)
const [form, setForm] = useState({ nick: '', email: '' });
const handleUnified = (e) => {
const { name, type, value, checked } = e.target;
setForm((f) => ({ ...f, [name]: type === 'checkbox' ? checked : value }));
};
return (
<section style={{border:'1px solid #ddd', padding:12, borderRadius:8}}>
<h4>onChange 예제</h4>
<div>
<label>이름: </label>
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
</div>
<div>
<label>나이: </label>
<input type="number" value={age} onChange={(e) => setAge(e.target.value)} />
</div>
<div>
<label>
<input type="checkbox" checked={agree} onChange={(e) => setAgree(e.target.checked)} />
약관 동의
</label>
</div>
<div>
<label>좋아하는 색: </label>
<select value={color} onChange={(e) => setColor(e.target.value)}>
<option value="red">빨강</option>
<option value="green">초록</option>
<option value="blue">파랑</option>
</select>
</div>
<hr/>
<p><strong>통합 핸들러</strong> 예제</p>
<input name="nick" placeholder="닉네임" value={form.nick} onChange={handleUnified} />
<input name="email" placeholder="이메일" value={form.email} onChange={handleUnified} />
<pre style={{background:'#f6f8fa', padding:8}}>{JSON.stringify({ name, age, agree, color, form }, null, 2)}</pre>
</section>
);
}
export default ChangeExamples;팁: 입력 지연(자동 저장 등)에는 setTimeout 기반 디바운스 또는 useDeferredValue·useTransition 같은 concurrent 도구를 고려하세요.
▼ 실행 결과 화면

3. onSubmit: 폼 제출 제어와 검증
폼 제출 시에는 onSubmit에서 e.preventDefault()로 새로고침을 막고, 유효성 검사를 수행한 뒤 서버로 전송합니다. 버튼 타입은 기본적으로 submit이므로, 다른 버튼은 type="button"을 지정하세요.
import React, { useState } from 'react';
function SubmitExamples() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
if (!email.includes('@')) {
setError('이메일 형식이 올바르지 않습니다.');
return;
}
if (password.length < 6) {
setError('비밀번호는 6자 이상이어야 합니다.');
return;
}
// TODO: 실제 API 호출
// const res = await fetch('/api/login', { method: 'POST', body: JSON.stringify({ email, password }) });
alert(`제출됨: ${email}`);
};
return (
<section style={{border:'1px solid #ddd', padding:12, borderRadius:8}}>
<h4>onSubmit 예제</h4>
<form onSubmit={handleSubmit}>
<div>
<label>이메일</label>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
</div>
<div>
<label>비밀번호</label>
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
</div>
{error && <p style={{color:'crimson'}}>{error}</p>}
<button type="submit">로그인</button>
<button type="button" onClick={() => { setEmail(''); setPassword(''); setError(''); }}>초기화</button>
</form>
</section>
);
}
export default SubmitExamples;▼ 실행 결과 화면

마무리
리액트에서 이벤트를 다룰 때는 onClick·onChange·onSubmit의 기본 규칙을 지키는 것만으로도 대부분의 UI 상호작용을 깔끔하게 구현할 수 있습니다. 이벤트는 SyntheticEvent로 표준화되어 있고, JSX 속성에는 함수 레퍼런스를 전달해야 하며, 필요 시 preventDefault()와 stopPropagation()으로 흐름을 제어합니다. 입력값은 Controlled Component 패턴으로 상태와 동기화하고, 폼 제출은 onSubmit에서 검증 → 제출 순서로 처리하는 것이 핵심입니다.
- 자주 하는 실수:
onClick={handler()}같이 즉시 호출하거나, 폼 안 버튼에type="button"을 빼서 의도치 않게 제출되는 경우. - 디버깅 팁: 이벤트가 기대와 다르면
console.log(e.type, e.target, e.currentTarget)으로 전파·타깃을 먼저 확인하세요. - 접근성: 클릭 요소는
<div>대신<button>을 사용하고, 폼 필드는label과 연결해 스크린리더 친화적으로 만듭니다.
참고 링크
- React 공식 문서: Responding to Events
- React 공식 문서: Managing State
- React 공식 문서: Sharing State Between Components
함께 보면 좋은 게시글
- 리액트 state 관리: useState 기본 사용법과 동작 원리
- 리액트 props 사용법: 부모에서 자식으로 데이터 전달하기
- React JSX 기초: 표현식·조건부 렌더링·리스트 출력
- 리액트 JSX 문법 규칙: 인라인 스타일링·className·닫는 태그·주석
- React 시작하기: 환경 구축 & 첫 컴포넌트 만들기
이 글이 도움이 되셨다면 공유 부탁 드립니다.



