🐨CoalaCoding
DocsExamplesTry itBoardB반
🐨CoalaCoding

개발자를 위한 한국어 웹 기술 문서

문서

  • JavaScript
  • Web Publishing
  • React
  • Python

커뮤니티

  • 게시판
  • 예제 모음
  • Try it 에디터

기타

  • GitHub
  • 관리자
© 2026 CoalaCoding. All rights reserved.
  • 22_무비앱-완료본
  • 01. GOFLIX 프로젝트 소개와 개발환경 설정
  • 02. React 진입점과 라우팅 설정
  • 03. Axios로 TMDB API 연결하기
  • 04. 공통 UI 컴포넌트 만들기 (UI.jsx)
  • 05. App.jsx — 레이아웃 구성과 데이터 가져오기
  • 06. Header와 Footer 만들기
  • 07. Home.jsx — 메인 페이지 완성하기
  • 08. Section과 Card — 영화 카드 목록 만들기
  • 09. MovieDetail — 영화 상세 페이지 만들기
  • 10. Category, ErrorPage 완성하기
  • 11. AI 챗봇 연동하기
  • 12_Swiper_캐러셀_적용과_프로젝트_마무리
  • 13. GOFLEX Gemini CLI 바이브코딩 프롬프트 템플릿
  • 00_시작하기
  • 01_App
  • 02_CSS
  • 03_Nav
  • 04_Hero
  • 05_AboutMe
  • 06_Projects
  • 07_Contact
  • 08_Footer
  • 09_완성_정리
  • 10_바이브코딩
  1. 홈
  2. 문서
  3. React
  4. 실전 프로젝트
  5. 07_Contact

07_Contact

코드 블록의 Try it Yourself 버튼으로 직접 실행할 수 있다.

구문

07. Contact.jsx — EmailJS로 이메일 전송

이번 단계에서 할 일

Contact 폼에서 입력한 내용을 실제 이메일로 전송한다. EmailJS — 백엔드 서버 없이 프론트엔드에서 바로 이메일을 보내주는 서비스이다.


피그마 디자인 분석

│ ─── contacts ──────────────────────────────── │
│                                               │
│   Name                                        │
│   ┌────────────────────────────────────────┐  │
│   │ Value                                  │  │  높이: 40px
│   └────────────────────────────────────────┘  │
│                                               │
│   Email                                       │
│   ┌────────────────────────────────────────┐  │
│   │ Value                                  │  │
│   └────────────────────────────────────────┘  │
│                                               │
│   Message (textarea)                          │
│                                               │
│   ┌──────────┐  ┌──────────────────────────┐  │
│   │  Cancel  │  │         Submit           │  │
│   └──────────┘  └──────────────────────────┘  │

STEP 0 — EmailJS 가입 및 설정

EmailJS를 사용하려면 3가지 값이 필요하다.

값설명어디서 찾나
Service ID연결한 이메일 서비스 식별자Email Services 메뉴
Template ID이메일 템플릿 식별자Email Templates 메뉴
Public Key공개 인증 키Account > API Keys
  1. EmailJS 가입 — https://www.emailjs.com 에 접속하여 무료 계정을 만든다

  2. 이메일 서비스 연결 — Email Services 메뉴에서 Gmail 등 사용할 이메일을 연결한다. 연결하면 Service ID가 생긴다 (예: service_abc123)

  3. 이메일 템플릿 만들기 — Email Templates 메뉴에서 새 템플릿을 만든다. 아래처럼 작성한다

Subject: 이력서 사이트에서 새 메시지가 도착했습니다

Hello,

You got a new message from {{user_name}} ({{user_email}}):

{{message}}

Best wishes,
EmailJS team
ℹ️INFO

{{user_name}}, {{user_email}}, {{message}} — 이중 중괄호 안의 이름은 HTML form의 name 속성과 일치해야 한다. 폼에서 name="user_name"으로 보내면 템플릿에서 {{user_name}}으로 받는다.

  1. Template ID 확인 — 템플릿을 저장하면 Template ID가 생긴다 (예: template_xyz789)

  2. Public Key 확인 — Account > API Keys 에서 Public Key를 복사한다


STEP 1 — 패키지 설치

npm install @emailjs/browser

@emailjs/browser — 브라우저 환경에서 EmailJS API를 사용할 수 있게 해주는 라이브러리이다


STEP 2 — Contact.jsx 작성

import { useState, useRef } from 'react'
import emailjs from '@emailjs/browser'
import SectionTitle from './SectionTitle'

const Contact = () => {
  const formRef = useRef(null)
  const [loading, setLoading] = useState(false)

  const handleSubmit = (e) => {
    e.preventDefault()
    setLoading(true)

    emailjs
      .sendForm(
        'YOUR_SERVICE_ID',
        'YOUR_TEMPLATE_ID',
        formRef.current,
        { publicKey: 'YOUR_PUBLIC_KEY' }
      )
      .then(() => {
        alert('메시지가 전송되었다!')
        formRef.current.reset()
      })
      .catch((error) => {
        alert('전송 실패: ' + error.text)
      })
      .finally(() => {
        setLoading(false)
      })
  }

  const handleReset = () => {
    formRef.current.reset()
  }

  return (
    <section className="contact" id="contacts">
      <SectionTitle
        title="contacts"
        subtitle="끈기와 열정으로 성장하는 오성의입니다."
      />

      <form className="contact-form" ref={formRef} onSubmit={handleSubmit}>
        <div className="form-field">
          <label className="form-label">Name</label>
          <input
            className="form-input"
            type="text"
            name="user_name"
            placeholder="이름을 입력하세요"
            required
          />
        </div>

        <div className="form-field">
          <label className="form-label">Email</label>
          <input
            className="form-input"
            type="email"
            name="user_email"
            placeholder="이메일을 입력하세요"
            required
          />
        </div>

        <div className="form-field">
          <label className="form-label">Message</label>
          <textarea
            className="form-textarea"
            name="message"
            placeholder="메시지를 입력하세요"
            required
          />
        </div>

        <div className="form-buttons">
          <button
            type="button"
            className="form-btn form-btn--cancel"
            onClick={handleReset}
          >
            Cancel
          </button>
          <button
            type="submit"
            className="form-btn form-btn--submit"
            disabled={loading}
          >
            {loading ? 'Sending...' : 'Submit'}
          </button>
        </div>
      </form>
    </section>
  )
}

export default Contact

1: import { useState, useRef } from 'react' — useState는 상태 관리, useRef는 DOM 요소를 직접 참조할 때 사용한다

2: import emailjs from '@emailjs/browser' — EmailJS 라이브러리를 가져온다

3: import SectionTitle from './SectionTitle' — 이전에 만든 섹션 타이틀 컴포넌트이다

6: const formRef = useRef(null) — form 태그를 직접 참조하기 위한 ref이다. EmailJS의 sendForm이 form 요소를 직접 읽어야 하기 때문에 필요하다

7: const [loading, setLoading] = useState(false) — 전송 중인지 아닌지 상태를 저장한다. 전송 중에는 버튼을 비활성화한다

9: const handleSubmit = (e) => { — 폼을 제출할 때 실행되는 함수이다

10: e.preventDefault() — form 태그의 기본 동작(페이지 새로고침)을 막는다

11: setLoading(true) — 전송 시작을 표시한다

13~18: emailjs.sendForm(...) — EmailJS에 이메일 전송을 요청한다

  • 첫 번째 인자: Service ID
  • 두 번째 인자: Template ID
  • 세 번째 인자: form DOM 요소 (formRef.current)
  • 네 번째 인자: Public Key 객체

19~21: .then(() => { ... }) — 전송 성공 시 알림을 띄우고, formRef.current.reset()으로 폼을 초기화한다

22~24: .catch((error) => { ... }) — 전송 실패 시 에러 메시지를 보여준다

25~27: .finally(() => { ... }) — 성공/실패 상관없이 loading 상태를 false로 되돌린다

30~32: const handleReset = () => { ... } — Cancel 버튼 클릭 시 폼의 모든 값을 초기화한다

42: ref={formRef} — form 태그에 ref를 연결한다. 이제 formRef.current로 이 form 요소에 접근할 수 있다

47: name="user_name" — EmailJS 템플릿의 {{user_name}}과 매칭되는 이름이다

49: required — 빈 칸으로 제출할 수 없게 막는다

75: disabled={loading} — 전송 중에는 버튼을 클릭할 수 없게 막는다. 중복 전송을 방지한다

77: {loading ? 'Sending...' : 'Submit'} — 삼항 연산자로 전송 중이면 "Sending...", 아니면 "Submit"을 표시한다

⚠️WARNING

'YOUR_SERVICE_ID', 'YOUR_TEMPLATE_ID', 'YOUR_PUBLIC_KEY'는 반드시 본인의 EmailJS 대시보드에서 복사한 실제 값으로 바꿔야 한다.


새로운 개념: useRef

이전 단계에서는 useState로 input 값을 관리했다 (제어 컴포넌트). 이번에는 useRef로 form 자체를 참조한다 (비제어 컴포넌트).

제어 컴포넌트 (useState):
  React가 input 값을 직접 관리한다
  value={state} + onChange={setState}

비제어 컴포넌트 (useRef):
  HTML form이 값을 스스로 관리한다
  React는 ref로 form에 접근만 한다

EmailJS의 sendForm은 form DOM 요소를 통째로 읽어서 데이터를 수집한다. 그래서 useRef로 form을 참조하는 방식이 더 자연스럽다.

const formRef = useRef(null)

<form ref={formRef}>
  <input name="user_name" />
</form>

emailjs.sendForm('...', '...', formRef.current, { publicKey: '...' })

formRef.current — ref 안에 저장된 실제 DOM 요소이다. useRef는 .current 속성에 값을 담아둔다.


새로운 개념: Promise (.then / .catch / .finally)

emailjs.sendForm()은 Promise를 반환한다. Promise — 미래에 완료될 작업을 나타내는 객체이다. "나중에 결과를 알려줄게"라는 약속이다.

emailjs.sendForm(...)     ← 이메일 전송 요청 (시간이 걸린다)
  .then(() => { ... })    ← 성공하면 실행
  .catch(() => { ... })   ← 실패하면 실행
  .finally(() => { ... }) ← 성공/실패 상관없이 항상 실행

비유: 택배를 보내는 것과 같다.

택배 접수 (sendForm)
  ├─ 배달 완료 (.then)    → "배달되었습니다" 알림
  ├─ 배달 실패 (.catch)   → "배달 실패" 알림
  └─ 어떤 경우든 (.finally) → 배달 추적 화면 닫기

STEP 3 — CSS 추가

/* src/index.css 에 추가 */

.contact {
  padding: 80px 24px;
}

.contact-form {
  width: 320px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.form-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.form-label {
  font-size: 16px;
  font-weight: 500;
  color: #1e1e1e;
}

.form-input {
  height: 40px;
  padding: 0 12px;
  border: 1px solid #d0d0d0;
  border-radius: 4px;
  font-size: 16px;
  color: #1e1e1e;
  outline: none;
  width: 100%;
}

.form-textarea {
  height: 80px;
  padding: 10px 12px;
  border: 1px solid #d0d0d0;
  border-radius: 4px;
  font-size: 16px;
  color: #1e1e1e;
  resize: none;
  outline: none;
  width: 100%;
}

.form-input::placeholder,
.form-textarea::placeholder {
  color: #b3b3b3;
}

.form-input:focus,
.form-textarea:focus {
  border-color: #2c2c2c;
}

.form-buttons {
  display: flex;
  gap: 12px;
}

.form-btn {
  height: 40px;
  padding: 0 20px;
  border: none;
  border-radius: 4px;
  font-size: 16px;
  cursor: pointer;
  transition: opacity 0.2s;
}

.form-btn--cancel {
  background-color: #f0f0f0;
  color: #303030;
}

.form-btn--submit {
  flex: 1;
  background-color: #2c2c2c;
  color: #f5f5f5;
}

.form-btn--submit:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.form-btn:hover:not(:disabled) {
  opacity: 0.8;
}

89~92: .form-btn--submit:disabled — disabled 속성이 있을 때의 스타일이다. 전송 중에는 반투명하게 보이고 커서가 금지 모양으로 바뀐다

94~96: .form-btn:hover:not(:disabled) — 비활성화 상태가 아닐 때만 hover 효과를 적용한다


브라우저 확인

  1. 폼에 이름, 이메일, 메시지를 입력한다
  2. Submit 버튼을 클릭한다
  3. 버튼이 "Sending..."으로 바뀌는 것을 확인한다
  4. "메시지가 전송되었다!" 알림이 뜨면 성공이다
  5. EmailJS에 연결한 이메일 받은편지함을 확인한다 — 실제 이메일이 와 있다
⚠️WARNING

전송이 실패하면 확인할 것:

  • Service ID, Template ID, Public Key가 정확한지 확인한다
  • EmailJS 대시보드에서 이메일 서비스가 Active 상태인지 확인한다
  • 템플릿의 변수 이름({{user_name}} 등)이 form의 name 속성과 일치하는지 확인한다
  • 무료 플랜은 월 200건까지 전송 가능하다

정리

개념설명
EmailJS백엔드 없이 프론트엔드에서 이메일을 보내는 서비스
useRefDOM 요소를 직접 참조하는 React Hook
ref={formRef}form 태그에 ref를 연결하는 방법
sendForm()form 요소를 읽어서 이메일을 전송하는 함수
Promise미래에 완료될 작업을 나타내는 객체
.then()성공 시 실행
.catch()실패 시 실행
.finally()성공/실패 상관없이 항상 실행
disabled버튼을 클릭 불가능하게 만드는 속성
required빈 칸 제출을 막는 HTML 속성

목차

  • 구문