[TIL] 4. React: props와 useState()

MinseoKim·2026년 8월 4일
post-thumbnail

1. 오늘의 한 줄 요약

React + MUI + axios 실습에서 만난 다섯 가지 에러 대부분이 "값은 만들었지만 실제 사용처까지 연결을 안 한" 문제였고, useState를 오해했던 것도 결국 같은 맥락이었다.

2. 배운 내용

★ MUI

npm install @mui/material @emotion/react @emotion/styled
import Button from '@mui/material/Button';

그동안 개발하면서는 비슷한 버튼 동작을 직접 만들어 쓰고 있었는데, MUI라는 컴포넌트 라이브러리를 이번에 처음 제대로 써봤다!

https://mui.com/material-ui/getting-started/templates/


트러블슈팅 1 — 버튼 클릭 시 onClick이 동작하지 않음

커스텀 컴포넌트(MaterialButton)가 props.onClick을 받기만 했지, 내부의 실제 MUI <Button>에는 연결하지 않았다.

// 수정 전
const MaterialButton = (props) => {
    return <Button className='button'>{props.title}</Button>;
};

// 수정 후
const MaterialButton = (props) => {
    return <Button className='button' onClick={props.onClick}>{props.title}</Button>;
};

핵심: props로 함수를 받는 것과, 그 함수를 실제 엘리먼트에 연결하는 것은 별개의 작업이다. 자동으로 연결되지 않음을 기억하기!


트러블슈팅 2 — CSS가 MUI 컴포넌트에 반영 안 됨

MUI는 emotion(CSS-in-JS)으로 <head>에 스타일을 런타임에 동적으로 삽입한다. 같은 specificity(우선순위)라면 나중에 삽입된 쪽이 이긴다 — 그래서 내가 처음에 커스텀하려고 만들었던 일반 .button 클래스가 무시됐다. mui의 개념에 대해서 잘 몰랐기 때문에 계속 css 코드 호출 문제인줄 알았다..

.button {
    background-color: #2563eb !important;
    border-radius: 2px !important;
}

!important로 임시 해결했지만, 정석은 sx prop이나 styled(Button)()을 쓰는 것. plain CSS 오버라이드는 로드 순서에 의존하는 임시방편이라는 걸 기억해두자!


트러블슈팅 3 — 댓글 데이터가 화면에 안 뜸

// 부모
<Comment key={idx} data={comment}/>

// 자식 (수정 전) — data를 거치지 않고 최상위처럼 접근
<span>{props.writer}</span>

// 자식 (수정 후)
<span>{props.data.writer}</span>

부모는 data라는 이름 하나로 객체를 통째로 넘겼는데, 자식은 props.writer처럼 data를 건너뛰고 접근했다. 넘기는 이름과 읽는 경로는 항상 쌍으로 맞아야 한다. ({...comment}로 전개해서 넘겼다면 props.writer로 바로 접근 가능했음)

그리고 Comment 컴포넌트가 props를 받아야 하는 이유도 다시 정리했다 — .map()으로 3명(작성자별)의 서로 다른 데이터를 같은 컴포넌트로 렌더링해야 하기 때문. props라는 통로가 없으면 매번 같은 내용만 찍히고 재사용이 안 된다.


★ useState 오해 정리 (CapacityPage.jsx 사례)

오해실제
cnt = cnt + 1처럼 직접 재할당하면 값이 바뀐다리렌더링이 안 일어나고, 다음 렌더에서 useState()가 다시 호출되며 값이 원래대로 돌아감. state는 "변수"가 아니라 리렌더링을 트리거하는 값
{cnt} + 1이 숫자 덧셈이다JS 표현식 자리에서 {cnt}는 객체 축약 표현({ cnt: cnt })으로 파싱됨 → "객체 + 1" 연산이 되어버림. 그냥 cnt + 1로 써야 함
useState()를 인자 없이 호출해도 된다인자를 안 주면 초기값이 undefined, undefined + 1NaN. 상태 타입에 맞는 초기값(useState(0))을 항상 넣어야 함
const [cnt, setCnt] useState();= 빠뜨림 → 구조분해 할당은 const [a, b] = 표현식 형태가 필수, = 없으면 문법 에러

기억할 것:

  • state 변경은 항상 setter(setCnt(...))로만 한다.
  • 이전 값에 의존하는 갱신은 함수형 업데이트가 더 안전하다: setCnt(prev => prev + 1)
  • JS 표현식 안에서 {}는 문맥에 따라 객체 리터럴로 해석될 수 있다는 것을 주의한다.

3. 실습 결과

(1) MaterialButton, Comment 컴포넌트 만들고 props 연결

(2) axios.create() + json-server로 목 서버(5000번 포트) 연동

(3)CapacityPage.jsx에서 카운트 증가 버튼 만들다가 위 useState 오해 4가지를 전부 만남

  • 현재 useState()를 활용해서 입장 인원 cnt가 이벤트와 동기화되게 만들기까지 완료한 상태

4. 문제와 해결

막힌 부분

CommentPage.jsx:7 GET http://localhost:3000/comment 404 (Not Found)

가장 헷갈렸던 건 axios baseURL 이슈였다. 콘솔에는 그냥 404만 찍혀서, 처음엔 백엔드(json-server)가 안 켜진 줄 알고 서버 쪽만 계속 확인했다.

★ 해결 방법

  1. 요청이 실제로 어느 URL로 나가는지 Network 탭에서 확인 (localhost:3000/comment로 나가고 있었음 — 기대한 5000이 아니었음)
  2. api/axios.js를 열어서 export default axios;로 원본을 내보내고 있는 걸 발견
  3. export default api;로 수정 후 정상 동작 확인

5. 다음에 할 일

  • 추가 및 제거 인원 제한 코드 추가
  • useEffect() -> 내일 학습할 내용!

0개의 댓글