2026. 6. 19.
기본 버튼은 되는데 커스텀 Button만 안 될 때, prop 전달 두 곳
네이티브 button은 되는데 디자인용 커스텀 Button만 반응하지 않을 때, 호출부 prop 이름과 내부 button의 onClick 매핑을 비교해 끊긴 한 줄만 고치는 순서입니다.

AI가 만든 화면에서 기본 <button>은 눌리는데 디자인이 적용된 <Button>만 아무 반응이 없을 때가 있습니다. 이때 함수와 페이지를 통째로 다시 만들 필요는 없습니다. 호출부가 넘긴 prop 이름과 커스텀 컴포넌트가 실제 <button onClick>에 붙인 이름을 나란히 보면 끊긴 위치를 좁힐 수 있습니다.
먼저 커스텀 <Button>을 잠깐 네이티브 <button>으로 바꿔 같은 함수를 연결하세요. 네이티브 버튼에서 로그가 찍히면 함수 자체보다 커스텀 컴포넌트의 전달 경로를 볼 차례입니다. 수정 전에 대조 테스트로 범위를 줄이는 것이 첫 행동입니다.
먼저 네이티브 button으로 함수를 대조합니다
아래처럼 커스텀 컴포넌트를 네이티브 요소로 바꾸고 같은 핸들러를 연결합니다.
function handleSave() {
console.log("save clicked");
}
return <button onClick={handleSave}>저장하기</button>;
React 이벤트 공식 문서는 이벤트 핸들러에 handleSave()를 호출해서 넣는 것이 아니라 handleSave 함수를 넘기라고 설명합니다. 네이티브 버튼에서도 로그가 없다면 함수 선언, 렌더 오류, disabled 상태를 먼저 봅니다. 네이티브 버튼에서는 로그가 찍히는데 커스텀 Button만 안 된다면 문제 범위는 prop 전달 경로로 좁아집니다.
호출부와 받는 prop 이름을 맞춥니다
커스텀 컴포넌트의 이벤트 prop 이름은 onClick, onPress, handleClick처럼 정할 수 있습니다. 중요한 것은 호출부가 넘긴 이름과 컴포넌트가 받는 이름이 같아야 한다는 점입니다.
// 호출부는 onClick으로 넘깁니다.
<Button onClick={handleSave}>저장하기</Button>
// 그런데 컴포넌트는 handleClick을 찾고 있습니다.
function Button({ handleClick, children }) {
return <button onClick={handleClick}>{children}</button>;
}
위 코드는 이름이 달라 handleClick이 undefined가 됩니다. 한쪽 이름만 바꿔 계약을 맞춥니다.
type ButtonProps = {
onClick: () => void;
children: React.ReactNode;
};
function Button({ onClick, children }: ButtonProps) {
return <button onClick={onClick}>{children}</button>;
}
TypeScript 타입을 붙이면 호출부에서 빠진 prop이나 잘못된 이름을 저장 전에 찾기 쉬워집니다. 목표는 새 컴포넌트를 만드는 것이 아니라 받는 이름 하나를 호출부와 일치시키는 것입니다.
받은 함수를 실제 button의 onClick에 넘깁니다
이름이 같아도 커스텀 컴포넌트가 prop을 실제 DOM 버튼에 전달하지 않으면 클릭은 함수까지 도달하지 않습니다.
function Button({ onClick, children }: ButtonProps) {
// onClick을 받았지만 실제 button에 붙이지 않은 상태
return <button className="primary">{children}</button>;
}
다음처럼 받은 함수를 실제 요소에 연결합니다.
function Button({ onClick, children }: ButtonProps) {
return (
<button className="primary" onClick={onClick}>
{children}
</button>
);
}
React 공식 문서도 커스텀 컴포넌트가 받은 이벤트 prop을 내부 <button onClick={...}>에 전달하는 예를 보여 줍니다. 호출부 → 컴포넌트 props → 실제 button 세 칸이 이어져야 클릭 함수가 실행됩니다.
prop 연결이 맞으면 client 경계와 disabled를 봅니다
전달 경로가 맞는데도 반응이 없다면 두 분기를 확인합니다.
- Next.js App Router에서 상호작용 컴포넌트의 진입 파일에
'use client'가 있는가 - 실제
<button>의disabled가 계속true인가
Next.js use client 공식 문서는 이벤트 처리 같은 상호작용 UI에 클라이언트 진입 경계가 필요하지만 모든 파일에 지시어를 붙일 필요는 없다고 설명합니다. 경계의 props는 직렬화 가능해야 하므로 서버 컴포넌트에서 만든 일반 함수를 그대로 넘기는 구조도 피해야 합니다. 관련 경계가 헷갈리면 Next.js use client 경계를 좁히는 점검 순서를 이어서 확인할 수 있습니다.
MDN의 disabled 문서에 따르면 비활성화된 버튼은 클릭을 받지 않습니다. disabled={isSaving || !title}이라면 onClick을 더 고치기 전에 어떤 조건이 true로 남는지 로그로 확인합니다.
클릭 로그와 실제 동작을 함께 확인합니다
수정 뒤에는 아래 네 줄을 채웁니다.
호출부 prop 이름: onClick
컴포넌트가 받는 이름: onClick
실제 button 연결: onClick={onClick}
재검증: save clicked 로그 + 저장 결과 확인
Chrome DevTools Console 공식 문서는 Console의 주요 용도를 로그 메시지 확인과 JavaScript 실행으로 설명합니다. 클릭 직전 Console을 비우고 버튼을 한 번 누른 뒤 로그가 한 번 찍히는지 봅니다. 로그는 찍히지만 저장 결과가 없다면 클릭 전달은 해결된 것이므로 다음 범위는 핸들러 안의 API 요청이나 상태 변경입니다. 그때는 브라우저 콘솔 첫 오류를 전달하는 AI 프롬프트로 다음 문제를 분리합니다.
AI에게 다시 요청할 때도 전체 재작성을 맡기지 않습니다.
네이티브 button에서는 handleSave 로그가 찍히지만 커스텀 Button에서는 찍히지 않습니다.
호출부 prop 이름과 Button 컴포넌트가 받는 이름,
실제 <button onClick> 매핑만 비교해 주세요.
관련 없는 파일은 바꾸지 말고 최소 수정 한 곳과 재검증 방법을 제시해 주세요.
오늘 남길 결과는 새 버튼이 아니라 클릭 신호가 어느 줄까지 도달했는지 설명할 수 있는 상태입니다. 네이티브 대조 테스트를 하고, 두 prop 이름과 실제 button 매핑을 맞춘 뒤, 로그와 화면 결과를 각각 확인하세요.
참고 출처
- React: Responding to Events
- Next.js:
use client - MDN: HTMLButtonElement.disabled
- Chrome for Developers: Console overview
이 글은 AI 코딩과 개발 학습의 일반 정보 제공 목적입니다. 도구, 모델, 커리큘럼, 요금은 버전과 시점에 따라 달라질 수 있으므로 실습이나 도입 전 공식 문서와 최신 릴리스 노트를 확인하세요.
다음으로 읽을 기사
같은 흐름으로 이어 읽기 좋은 기사만 추려 보여줍니다.
첫 번째 댓글을 남겨보세요
여러분의 생각이 다른 독자에게 도움이 됩니다.
댓글 0
이 글을 읽은 독자들의 생각을 나눠보세요.