자료 유형: 무료 실무 해설팩
React 기본 문법은 알지만 폼, state, API 화면에서 자주 막히는 학습자를 위한 실무 해설팩입니다. 컴포넌트 문법보다 사용자가 입력하고, 제출하고, 실패하고, 다시 시도하는 흐름을 안정적으로 설계하는 데 초점을 맞춥니다.
이 팩의 약속
이 자료는 JSX 문법 요약이 아니다. 실제 화면에서 사용자 행동이 들어왔을 때 React state와 UI가 어떻게 움직여야 하는지 설명한다.
- controlled input 없이 입력값이 흩어지는 문제
button onClick만으로 제출을 처리해 Enter/모바일 제출을 놓치는 문제- 로딩/성공/실패 상태가 섞이는 문제
- API 실패 화면에 복구 액션이 없는 문제
예제 1. controlled input
연결 문제: https://nst21c.com/?task=reactInputPreview#labApp
흔한 실패 코드
function SearchBox() {
let keyword = "";
return (
<input onChange={(event) => {
keyword = event.target.value;
}} />
);
}
변수는 바뀌지만 React는 화면을 다시 그리지 않는다. 검색어 미리보기, 필터, 폼 검증처럼 입력값과 화면이 연결되어야 하는 곳에서 문제가 생긴다.
개선 코드
function SearchBox() {
const [keyword, setKeyword] = useState("");
return (
<section>
<input
value={keyword}
onChange={(event) => setKeyword(event.target.value)}
placeholder="검색어"
/>
<p>현재 검색어: {keyword || "없음"}</p>
</section>
);
}
실무 화면 예
- 검색창
- 관리자 필터
- 문의 폼
- 입력값 미리보기
코드 리뷰 체크포인트
- 입력값의 기준이 React state인가?
value와onChange가 함께 있는가?- 빈 값일 때 화면이 어색하지 않은가?
예제 2. form onSubmit과 검증
연결 문제: https://nst21c.com/?task=reactFormValidation#labApp
흔한 실패 코드
function EmailForm() {
const [email, setEmail] = useState("");
return (
<div>
<input value={email} onChange={(event) => setEmail(event.target.value)} />
<button onClick={() => submit(email)}>신청하기</button>
</div>
);
}
버튼 클릭만 처리하면 Enter 제출, 모바일 키보드 제출, 접근성 흐름을 놓친다. 이메일 형식이 틀렸을 때 사용자가 왜 실패했는지도 알기 어렵다.
개선 코드
function EmailForm() {
const [email, setEmail] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
const emailError =
email.trim().length === 0
? "이메일을 입력해주세요."
: !email.includes("@")
? "이메일 형식을 확인해주세요."
: "";
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
if (emailError || isSubmitting) return;
setIsSubmitting(true);
try {
await submit(email.trim());
} finally {
setIsSubmitting(false);
}
}
return (
<form onSubmit={handleSubmit}>
<input
value={email}
onChange={(event) => setEmail(event.target.value)}
aria-invalid={Boolean(emailError)}
/>
{emailError && <p role="alert">{emailError}</p>}
<button type="submit" disabled={Boolean(emailError) || isSubmitting}>
{isSubmitting ? "신청 중" : "신청하기"}
</button>
</form>
);
}
실무 화면 예
- 뉴스레터 신청
- 문의 폼
- 로그인/회원가입
- 결제 전 정보 입력
코드 리뷰 체크포인트
- 제출 로직이
form onSubmit에 있는가? preventDefault가 있는가?- 제출 중 중복 클릭을 막는가?
- 에러 메시지가 사용자 행동을 안내하는가?
예제 3. API 화면 상태 분리
연결 문제: https://nst21c.com/?task=reactLoadingState#labApp
흔한 실패 코드
function UserList({ loading, error, users }) {
if (loading) return <p>불러오는 중</p>;
if (error) return <p>오류</p>;
return <ul>{users.map((user) => <li>{user.name}</li>)}</ul>;
}
users가 undefined이면 바로 깨진다. 또 빈 배열과 실패 상태가 분리되지 않으면 사용자는 “데이터가 없는지, 오류인지” 알 수 없다.
개선 코드
type UserListState =
| { status: "loading" }
| { status: "success"; users: User[] }
| { status: "empty" }
| { status: "error"; message: string };
function UserList({ state }: { state: UserListState }) {
if (state.status === "loading") return <p>불러오는 중입니다.</p>;
if (state.status === "error") return <p role="alert">{state.message}</p>;
if (state.status === "empty") return <p>조건에 맞는 사용자가 없습니다.</p>;
return (
<ul>
{state.users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
실무 화면 예
- 상품 목록
- 사용자 프로필
- 검색 결과
- 관리자 대시보드 카드
코드 리뷰 체크포인트
- 로딩, 성공, 빈 결과, 실패가 분리되어 있는가?
- 성공 상태가 아닐 때 목록을 렌더링하지 않는가?
- 목록 key가 안정적인 ID인가?
예제 4. 실패 상태와 재시도
연결 문제: https://nst21c.com/?task=reactApiRetryState#labApp
흔한 실패 코드
if (error) {
return <p>오류가 발생했습니다.</p>;
}
실패 메시지만 보여주면 사용자는 다음 행동을 알 수 없다. 실무 화면에서 실패 상태는 끝이 아니라 복구 흐름의 시작이다.
개선 코드
function ErrorPanel({ onRetry, retrying }: { onRetry: () => void; retrying: boolean }) {
return (
<section role="alert">
<h2>데이터를 불러오지 못했습니다</h2>
<p>네트워크 상태를 확인한 뒤 다시 시도해주세요.</p>
<button type="button" onClick={onRetry} disabled={retrying}>
{retrying ? "다시 불러오는 중" : "다시 시도"}
</button>
</section>
);
}
실무 화면 예
- 네트워크 실패 복구
- 서버 오류 후 재요청
- 인증 만료 후 다시 불러오기
- 저장 실패 후 재시도
코드 리뷰 체크포인트
- 실패 상태에 복구 액션이 있는가?
- 재시도 중 중복 요청을 막는가?
- 개발자용 에러 문구를 그대로 노출하지 않는가?
5일 복습 루틴
1일차: controlled input을 만들고 입력값의 기준을 설명한다. 2일차: form validation을 풀고 onSubmit과 preventDefault를 설명한다. 3일차: loading/error/empty/success 상태를 화면 단위로 나눈다. 4일차: retry 버튼을 붙이고 중복 요청을 막는다. 5일차: 네 예제를 하나의 문의 신청 화면으로 합친다.
이 팩 다음에 보낼 후속 안내
React 팩을 받은 사용자는 TypeScript 상태 모델링과 연결하면 더 큰 가치를 느낀다. 후속 메일에서는 ApiState<T>를 React 화면에 연결하는 미니 프로젝트로 안내한다.