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으로 메모이제이션해 불필요한 리렌더링을 줄일 수 있습니다.

리액트 onClick 예제 실행 결과 화면

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 도구를 고려하세요.

리액트 onChange 예제 실행 결과 화면

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;
리액트 onSubmit 예제 실행 결과 화면

마무리

리액트에서 이벤트를 다룰 때는 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과 연결해 스크린리더 친화적으로 만듭니다.

참고 링크

함께 보면 좋은 게시글

위로 스크롤