Fullstack 86

heo4·2026년 8월 18일

Fullstack

목록 보기
41/70

풀스택

⚛️ React 조건부 렌더링 정리

state 값에 따라 화면에 다른 내용을 보여주는 조건부 렌더링을 정리했다. 삼항 연산자, && 연산자, 그리고 조건에 따라 컴포넌트 전체를 바꾸는 방법까지 다룬다.

1. 조건부 렌더링이란

state 값에 따라 화면에 다른 내용을 보여주는 것을 조건부 렌더링이라고 한다. 자바스크립트의 조건문을 JSX 안에서 활용하는 방식인데, JSX의 { } 안에는 if문을 그대로 쓸 수 없기 때문에 몇 가지 정해진 방법을 사용한다.


2. 삼항 연산자로 조건부 렌더링하기

조건 ? A : B 형태로, 조건이 참이면 A를, 거짓이면 B를 보여준다.

import { useState } from "react";

function App() {
  const [isOnline, setIsOnline] = useState(false);

  return (
    <div>
      <p>{isOnline ? "온라인입니다" : "오프라인입니다"}</p>
      <button onClick={() => setIsOnline(!isOnline)}>
        {isOnline ? "오프라인으로 전환" : "온라인으로 전환"}
      </button>
    </div>
  );
}

export default App;

setIsOnline(!isOnline)은 현재 isOnline 값의 반대(true↔false)로 뒤집는 코드다. 버튼을 누를 때마다 상태가 토글되면서 문구와 버튼 텍스트가 함께 바뀐다. 개발자 도구의 Components 탭을 통해 state의 상태 변화도 확인할 수 있다.


3. && 연산자로 조건부 렌더링하기

"조건이 참일 때만 보여주고, 거짓이면 아예 아무것도 보여주지 않는다"처럼 한쪽만 필요할 때는 && 연산자가 더 간결하다.

import { useState } from "react";

function App() {
  const [unreadCount, setUnreadCount] = useState(3);

  return (
    <div>
      <span>알림</span>
      {unreadCount > 0 && <span> ({unreadCount}개 안 읽음)</span>}
      <br />
      <button onClick={() => setUnreadCount(0)}>모두 읽음 처리</button>
    </div>
  );
}

export default App;

unreadCount > 0이 참이면 뒤에 있는 <span>이 화면에 나타나고, 거짓(0)이면 아무것도 나타나지 않는다.

삼항 연산자와 &&, 언제 뭘 써야 할까? 조건에 따라 "이것 또는 저것" 두 가지 중 하나를 보여줘야 한다면 삼항 연산자(조건 ? A : B)가 알맞다. 반대로 "조건에 맞을 때만 보여주고, 아니면 아무것도 없어야 한다"처럼 한쪽이 아예 없어도 되는 경우에는 && 연산자가 더 짧고 읽기 좋다.


4. 조건에 따라 다른 컴포넌트 렌더링하기

값 하나가 아니라 컴포넌트 전체를 조건에 따라 바꿔야 할 때도 같은 방식을 사용할 수 있다.

import { useState } from "react";

function LoginButton({ onClick }) {
  return <button onClick={onClick}>로그인</button>;
}

function LogoutButton({ onClick }) {
  return <button onClick={onClick}>로그아웃</button>;
}

function App() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  return (
    <div>
      <p>{isLoggedIn ? "환영합니다!" : "로그인이 필요합니다."}</p>
      {isLoggedIn ? (
        <LogoutButton onClick={() => setIsLoggedIn(false)} />
      ) : (
        <LoginButton onClick={() => setIsLoggedIn(true)} />
      )}
    </div>
  );
}

export default App;

isLoggedIn 값에 따라 LoginButton과 LogoutButton 중 하나만 화면에 나타난다.


5. 실습 정리

실습 1) 삼항 연산자로 좋아요 버튼 만들기

isLiked라는 boolean state를 만들고, 버튼을 클릭할 때마다 값이 토글되며 버튼 텍스트가 "좋아요" / "좋아요 취소"로 바뀌도록 삼항 연산자를 사용한다.

import { useState } from "react";

function App() {
  const [isLiked, setIsLiked] = useState(false);

  return (
    <button onClick={() => setIsLiked(!isLiked)}>
      {isLiked ? "좋아요 취소" : "좋아요"}
    </button>
  );
}

export default App;

실습 2) && 연산자로 조건부 문구 표시하기

cartCount(장바구니 개수)라는 숫자 state를 만들고, cartCount가 0보다 클 때만 "장바구니에 N개 담겨 있습니다" 문구가 나타나도록 && 연산자를 사용한다.

import { useState } from "react";

function App() {
  const [cartCount, setCartCount] = useState(0);

  return (
    <div>
      {cartCount > 0 && <p>장바구니에 {cartCount}개 담겨 있습니다</p>}
      <button onClick={() => setCartCount(cartCount + 1)}>담기</button>
      <button onClick={() => setCartCount(0)}>비우기</button>
    </div>
  );
}

export default App;

실습 3) 로그인 여부에 따라 다른 컴포넌트 보여주기

isLoggedIn state를 만들고, 로그인/로그아웃 버튼을 조건에 따라 다르게 렌더링한다. 로그인 상태일 때만 보이는 "내 정보" 문구도 추가한다.

import { useState } from "react";

function LoginButton({ onClick }) {
  return <button onClick={onClick}>로그인</button>;
}

function LogoutButton({ onClick }) {
  return <button onClick={onClick}>로그아웃</button>;
}

function App() {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  return (
    <div>
      <p>{isLoggedIn ? "환영합니다!" : "로그인이 필요합니다."}</p>
      {isLoggedIn && <p>내 정보: 한코딩님</p>}
      {isLoggedIn ? (
        <LogoutButton onClick={() => setIsLoggedIn(false)} />
      ) : (
        <LoginButton onClick={() => setIsLoggedIn(true)} />
      )}
    </div>
  );
}

export default App;

마무리

  • 둘 중 하나를 반드시 보여줘야 할 때는 삼항 연산자(조건 ? A : B), 조건에 맞을 때만 보여주고 아니면 아무것도 없어야 할 때는 && 연산자로 구분해서 기억하자.
  • 값 하나뿐 아니라 컴포넌트 전체도 같은 삼항 연산자 방식으로 조건부 렌더링할 수 있다.
  • setState(!state)는 boolean 값을 토글할 때 자주 쓰이는 패턴이니 익혀두면 편하다.

⚛️ React 리스트 렌더링 정리 - map()과 key

배열 데이터를 화면에 리스트로 그리는 방법을 정리했다. map() 사용법부터 key가 왜 필요한지, index를 key로 쓰면 안 되는 이유, 중첩 리스트까지 다룬다.

1. map()으로 배열을 화면에 리스트로 렌더링하기

지금까지는 값을 하나씩 직접 적어서 화면에 표시했다. 하지만 데이터가 배열로 여러 개 있다면, 자바스크립트의 map() 함수를 사용해서 배열 전체를 한 번에 화면 요소로 바꿀 수 있다.

function App() {
  const fruits = ["사과", "바나나", "포도"];

  return (
    <ul>
      {fruits.map((fruit) => (
        <li key={fruit}>{fruit}</li>
      ))}
    </ul>
  );
}

export default App;

fruits.map((fruit) => <li>{fruit}</li>)는 배열 안의 값 하나하나를 <li> 태그로 바꿔서, 그 결과를 다시 배열로 돌려준다. JSX는 배열을 그대로 화면에 나열해서 보여줄 수 있기 때문에, 이 결과가 리스트 형태로 렌더링된다.


2. key는 왜 필요할까

위 코드를 보면 <li>마다 key={fruit}라는 속성이 붙어 있다. key는 리스트 안의 각 항목을 React가 서로 구분할 수 있게 해주는 고유한 이름표다.

// key를 빼고 실행하면?
{fruits.map((fruit) => (
  <li>{fruit}</li>
))}

key를 빼고 실행해도 화면은 똑같이 나오지만, 브라우저 콘솔에 Warning: Each child in a list should have a unique "key" prop.이라는 경고가 뜬다.

key가 없으면 왜 경고가 뜰까? React는 리스트 항목 중 어떤 게 새로 추가됐는지, 어떤 게 삭제됐는지, 순서만 바뀐 건지를 빠르게 파악해서 화면의 필요한 부분만 효율적으로 다시 그리고 싶어한다. 그런데 각 항목에 이름표(key)가 없으면 React는 "이 리스트 어딘가가 바뀌었다"는 것만 알 뿐, 정확히 어떤 항목이 바뀐 건지 구분하지 못한다. 그래서 데이터마다 겹치지 않는 고유한 key를 지정해달라고 경고하는 것이다.


3. 잘못된 key 사용 시 발생하는 문제

key가 꼭 있어야 한다는 것만 기억하고, 배열의 순서 번호(index)를 그냥 key로 쓰는 경우가 많다.

{fruits.map((fruit, index) => (
  <li key={index}>{fruit}</li>
))}

지금처럼 리스트에 추가·삭제·순서 변경이 없다면 이렇게 써도 큰 문제는 없다. 하지만 Todo 앱처럼 항목을 삭제하거나 순서를 바꾸는 기능이 들어가면, index는 항목이 삭제될 때마다 다시 매겨지기 때문에 React가 "어떤 항목이 진짜로 삭제된 건지" 헷갈려서 엉뚱한 항목이 지워지거나 입력 중이던 값이 다른 항목으로 옮겨가는 등의 버그가 생길 수 있다.

⚠️ 그래서 되도록이면 각 데이터가 원래 가지고 있는 고유한 값(id 등)을 key로 사용하는 것이 안전하다.


4. 중첩 리스트 렌더링

배열 안에 또 다른 배열이 들어있는 경우, map() 안에서 map()을 한 번 더 사용해서 처리할 수 있다.

function App() {
  const users = [
    { id: 1, name: "한코딩", hobbies: ["독서", "등산"] },
    { id: 2, name: "김리액트", hobbies: ["게임"] },
  ];

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>
          {user.name}
          <ul>
            {user.hobbies.map((hobby) => (
              <li key={hobby}>{hobby}</li>
            ))}
          </ul>
        </li>
      ))}
    </ul>
  );
}

export default App;

바깥쪽 map()은 사용자 목록을, 안쪽 map()은 각 사용자의 취미 목록을 렌더링한다. 두 <ul>이 서로 다른 배열을 다루고 있기 때문에, key도 각각 따로 지정해줘야 한다.


5. 실습 정리

실습 1) 과일(음식) 목록을 map()으로 렌더링하기

  1. 좋아하는 음식 3~5개를 담은 배열을 만든다.
  2. map()을 사용해 <ul>, <li> 리스트로 화면에 렌더링한다.
  3. 일부러 key를 빼고 실행해서 콘솔에 경고가 뜨는지 확인한 뒤, 다시 key를 채워 넣는다.
function App() {
  const foods = ["김치찌개", "떡볶이", "초밥", "파스타"];

  return (
    <ul>
      {foods.map((food) => (
        <li key={food}>{food}</li>
      ))}
    </ul>
  );
}

export default App;

실습 2) 객체 배열로 이름 목록 렌더링하기

  1. { id: 1, name: "..." } 형태의 객체를 여러 개 담은 배열을 만든다.
  2. map()으로 각 사람의 이름을 리스트로 렌더링하고, key에는 index가 아니라 id를 사용한다.
function App() {
  const people = [
    { id: 1, name: "한코딩" },
    { id: 2, name: "김리액트" },
    { id: 3, name: "이자바" },
  ];

  return (
    <ul>
      {people.map((person) => (
        <li key={person.id}>{person.name}</li>
      ))}
    </ul>
  );
}

export default App;

실습 3) 중첩 리스트 렌더링해보기

  1. 나만의 카테고리별 목록(예: 좋아하는 영화 장르별 추천작)을 중첩 리스트로 만들어본다.
  2. 바깥쪽 리스트와 안쪽 리스트 각각에 알맞은 key를 지정한다.
function App() {
  const genres = [
    { id: 1, name: "액션", movies: ["존 윅", "매드맥스"] },
    { id: 2, name: "코미디", movies: ["극한직업"] },
  ];

  return (
    <ul>
      {genres.map((genre) => (
        <li key={genre.id}>
          {genre.name}
          <ul>
            {genre.movies.map((movie) => (
              <li key={movie}>{movie}</li>
            ))}
          </ul>
        </li>
      ))}
    </ul>
  );
}

export default App;

마무리

  • 배열 데이터를 화면에 그릴 땐 map()으로 각 항목을 JSX 요소로 변환하는 게 기본 패턴이다.
  • key는 React가 리스트의 각 항목을 구분해서 효율적으로 업데이트하기 위한 고유 이름표다. 없으면 경고가 뜬다.
  • 항목의 추가/삭제/순서 변경이 있는 리스트라면 index를 key로 쓰지 말고, 데이터가 가진 고유 id를 key로 쓰는 게 안전하다.
  • 중첩된 배열은 map() 안에 map()을 중첩해서 처리하며, 바깥/안쪽 리스트 각각에 key를 따로 지정해야 한다.

⚛️ React useEffect 기초 정리

React의 또 다른 핵심 훅인 useEffect를 정리했다. 컴포넌트 생명주기 개념부터 의존성 배열에 따라 실행 시점이 어떻게 달라지는지까지 다룬다.

1. 컴포넌트 생명주기란

컴포넌트는 화면에 나타나서 사라지기까지 몇 가지 단계를 거친다. 이 단계를 컴포넌트 생명주기(lifecycle)라고 부른다.

단계의미
마운트(mount)컴포넌트가 화면에 처음 나타나는 시점
업데이트(update)state나 props가 바뀌어서 다시 렌더링되는 시점
언마운트(unmount)컴포넌트가 화면에서 완전히 사라지는 시점

지금까지는 화면을 그리는 것(렌더링)만 다뤘는데, 실무에서는 "화면이 처음 나타났을 때 서버에서 데이터를 가져온다"거나 "특정 값이 바뀔 때마다 무언가를 실행한다"처럼 렌더링과 별개로 실행해야 하는 코드가 자주 필요하다. 이럴 때 사용하는 것이 useEffect다.


2. useEffect 기본 문법

import { useState, useEffect } from "react";

function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("렌더링됨!");
  });

  return (
    <div>
      <p>현재 값: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

export default App;

useEffect(() => { ... })처럼 작성하면, 화면이 렌더링될 때마다 그 안의 코드가 실행된다. 위 코드를 실행하고 콘솔을 열어보면, 처음 화면이 뜰 때 한 번, 그리고 버튼을 클릭할 때마다 "렌더링됨!"이 계속 찍히는 것을 확인할 수 있다.


3. 의존성 배열의 차이

useEffect의 두 번째 인자로 배열을 넘기면, "언제 실행할지"를 세밀하게 조절할 수 있다. 이 배열을 의존성 배열(dependency array)이라고 부른다.

작성 방식실행 시점
useEffect(() => {...}) (배열 생략)렌더링될 때마다 매번 실행
useEffect(() => {...}, []) (빈 배열)최초 1번(마운트 시)만 실행
useEffect(() => {...}, [count])처음 1번 + count 값이 바뀔 때만 실행

왜 의존성 배열에 무엇을 넣느냐에 따라 실행 시점이 달라질까? React는 의존성 배열 안의 값들을 "이 코드가 관심 있어 하는 값"으로 이해한다. 렌더링이 일어날 때마다 React는 배열 안의 값이 이전 렌더링 때와 달라졌는지 비교하고, 달라졌을 때만 useEffect의 코드를 다시 실행한다. 빈 배열([])은 "비교할 값이 없다"는 뜻이라 최초 1번만 실행되고, 배열 자체를 생략하면 "비교 없이 매번 실행하라"는 뜻이 된다.


4. 실전 예제: 특정 값이 바뀔 때만 실행하기

count와 name이라는 서로 다른 state가 있을 때, count가 바뀔 때만 실행되는 useEffect를 만들어보자.

import { useState, useEffect } from "react";

function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("");

  useEffect(() => {
    console.log("count가 바뀌어서 실행됨! 현재 count:", count);
  }, [count]);

  return (
    <div>
      <p>count: {count}</p>
      <button onClick={() => setCount(count + 1)}>count 증가</button>
      <br />
      <input onChange={(e) => setName(e.target.value)} placeholder="이름 입력" />
      <p>name: {name}</p>
    </div>
  );
}

export default App;

count 증가 버튼을 누르면 콘솔에 로그가 찍히지만, 이름 입력창에 글자를 쳐서 name이 바뀔 때는 화면이 다시 렌더링되는데도 콘솔에 아무것도 찍히지 않는다. useEffect가 [count]만 지켜보고 있기 때문이다.


5. 실습 정리

실습 1) 의존성 배열 없이 실행해보기

  1. count state와 useEffect(() => { console.log(...) })(의존성 배열 생략)를 작성한다.
  2. 버튼을 여러 번 눌러보면서, 클릭할 때마다 콘솔 로그가 매번 찍히는지 확인한다.
import { useState, useEffect } from "react";

function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("렌더링됨! 현재 count:", count);
  });

  return (
    <div>
      <p>현재 값: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

export default App;

실습 2) 빈 배열로 최초 1회만 실행해보기

  1. 위 코드의 의존성 배열을 []로 바꾼다.
  2. 버튼을 여러 번 눌러도 콘솔 로그가 처음 한 번만 찍히고 더 이상 찍히지 않는지 확인한다.
import { useState, useEffect } from "react";

function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("최초 1번만 실행됨!");
  }, []);

  return (
    <div>
      <p>현재 값: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

export default App;

실습 3) 특정 값이 바뀔 때만 실행해보기

  1. count와 name 두 가지 state를 만든다.
  2. useEffect의 의존성 배열에 [count]를 넣어서, count가 바뀔 때만 로그가 찍히도록 만든다.
  3. count 버튼을 눌렀을 때와 이름 입력창에 타이핑했을 때를 비교하며, 로그가 찍히는 경우와 안 찍히는 경우를 직접 확인한다.
import { useState, useEffect } from "react";

function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState("");

  useEffect(() => {
    console.log("count가 바뀌어서 실행됨! 현재 count:", count);
  }, [count]);

  return (
    <div>
      <p>count: {count}</p>
      <button onClick={() => setCount(count + 1)}>count 증가</button>
      <br />
      <input onChange={(e) => setName(e.target.value)} placeholder="이름 입력" />
      <p>name: {name}</p>
    </div>
  );
}

export default App;

마무리

  • useEffect는 렌더링과는 별개로 "화면이 그려진 뒤에 실행해야 할 코드"를 담당하는 훅이다.
  • 의존성 배열은 딱 세 가지 패턴만 기억하면 된다: 생략(매번), 빈 배열(최초 1번), 특정 값 배열(그 값이 바뀔 때만).
  • 여러 state가 있는 컴포넌트에서, 특정 state가 바뀔 때만 부수 효과를 실행하고 싶다면 의존성 배열에 그 state만 넣으면 된다.

⚛️ React 폼 다루기 - Controlled Component 정리

React에서 input, textarea, checkbox, select 같은 폼 요소를 state로 완전히 제어하는 방법을 정리했다. Controlled Component 개념부터 다양한 입력 요소 처리, form의 onSubmit과 e.preventDefault()까지 다룬다.

1. Controlled Component란

앞에선 input에 onChange만 연결해서 값을 읽기만 했다. 이번엔 한 걸음 더 나가서, input이 화면에 보여주는 값 자체를 state가 완전히 결정하도록 만들어본다. 이렇게 state가 입력 요소의 값을 완전히 통제하는 방식을 Controlled Component(제어 컴포넌트)라고 부른다.

// 이전 방식: state로 값을 읽기만 함 (value 연결 없음)
<input onChange={(e) => setText(e.target.value)} />
// Controlled Component 방식: value까지 state와 연결
<input value={text} onChange={(e) => setText(e.target.value)} />

2. input value 연결하기

import { useState } from "react";

function App() {
  const [text, setText] = useState("");

  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <p>입력한 값: {text}</p>
      <button onClick={() => setText("")}>지우기</button>
    </div>
  );
}

export default App;

value={text}가 있기 때문에, 지금 input에 보이는 값은 항상 text state와 정확히 같다. 그래서 "지우기" 버튼처럼 코드에서 setText("")를 호출하는 것만으로도 input 안의 글자를 화면에서 지울 수 있다.

value만 넣고 onChange를 안 넣으면 어떻게 될까? value={text}만 넣고 onChange를 빼면, input에 아무리 타이핑을 해도 글자가 전혀 입력되지 않는다. state는 그대로인데 value가 계속 그 값으로 고정되기 때문이다. 그래서 React는 이런 경우 콘솔에 "읽기 전용으로 만들 게 아니라면 onChange를 추가하라"는 경고를 띄운다. value와 onChange는 항상 짝을 이뤄야 한다.


3. 다양한 입력 요소 다루기

input 외에도 textarea, checkbox, select 같은 요소들을 state와 연결할 수 있다.

요소연결 방법
input, textareavalue와 onChange
checkboxchecked와 onChange
selectvalue와 onChange (option을 자식으로 나열)
import { useState } from "react";

function App() {
  const [message, setMessage] = useState("");
  const [agree, setAgree] = useState(false);
  const [level, setLevel] = useState("beginner");

  return (
    <div>
      <textarea
        value={message}
        onChange={(e) => setMessage(e.target.value)}
        placeholder="자기소개를 입력하세요"
      />
      <p>
        <input
          type="checkbox"
          checked={agree}
          onChange={(e) => setAgree(e.target.checked)}
        />
        약관에 동의합니다 ({agree ? "동의함" : "동의 안 함"})
      </p>
      <select value={level} onChange={(e) => setLevel(e.target.value)}>
        <option value="beginner">초급</option>
        <option value="intermediate">중급</option>
        <option value="advanced">고급</option>
      </select>
      <p>선택한 레벨: {level}</p>
    </div>
  );
}

export default App;

checkbox는 value 대신 checked를 사용하고, e.target.value가 아니라 e.target.checked(true/false)로 값을 읽는다는 점이 다르다.


4. form과 onSubmit, e.preventDefault()

HTML의 form은 원래 제출(submit) 버튼을 누르면 페이지 전체가 새로고침된다. React에서는 이 기본 동작을 막고, 자바스크립트 코드로 직접 제출을 처리하는 경우가 많다.

import { useState } from "react";

function App() {
  const [name, setName] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(`${name}님, 제출이 완료되었습니다!`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} placeholder="이름" />
      <button type="submit">제출</button>
    </form>
  );
}

export default App;

e.preventDefault()를 안 쓰면 어떻게 될까? e.preventDefault()가 없으면, 제출 버튼을 누르는 순간 브라우저가 원래 하던 대로 페이지 전체를 새로고침해버린다. 그러면 name state를 포함해서 지금까지 화면에서 바뀐 모든 값이 초기화되고, handleSubmit 함수의 나머지 코드(alert 등)도 정상적으로 실행되지 않을 수 있다. e.preventDefault()는 "브라우저의 기본 제출 동작은 막고, 대신 내가 작성한 코드로 처리하겠다"는 뜻이다.


5. 실습 정리

실습 1) input을 value + onChange로 완전히 제어하기

  1. text라는 state를 만들고, input의 value와 onChange를 모두 연결한다.
  2. "지우기" 버튼을 만들어서, 누르면 input 값이 즉시 비워지는지 확인한다.
import { useState } from "react";

function App() {
  const [text, setText] = useState("");

  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <p>입력한 값: {text}</p>
      <button onClick={() => setText("")}>지우기</button>
    </div>
  );
}

export default App;

실습 2) checkbox 다뤄보기

  1. agree라는 boolean state를 만들고, checkbox의 checked와 onChange를 연결한다.
  2. checkbox 값에 따라 "동의함" / "동의 안 함" 문구가 함께 바뀌도록 만든다.
import { useState } from "react";

function App() {
  const [agree, setAgree] = useState(false);

  return (
    <div>
      <input
        type="checkbox"
        checked={agree}
        onChange={(e) => setAgree(e.target.checked)}
      />
      약관에 동의합니다 ({agree ? "동의함" : "동의 안 함"})
    </div>
  );
}

export default App;

실습 3) 회원가입 폼 만들기

  1. 이름과 이메일을 입력받는 input 두 개를 각각 state와 연결한다.
  2. form과 onSubmit을 사용해서, 제출 버튼을 누르면 e.preventDefault()로 새로고침을 막고 입력한 값을 alert로 보여준다.
import { useState } from "react";

function App() {
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    alert(`이름: ${name}\n이메일: ${email}`);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={name} onChange={(e) => setName(e.target.value)} placeholder="이름" />
      <input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="이메일" />
      <button type="submit">회원가입</button>
    </form>
  );
}

export default App;

마무리

  • Controlled Component의 핵심은 value(또는 checked)와 onChange를 항상 짝지어 쓰는 것이다. 하나만 쓰면 입력이 막히거나 경고가 뜬다.
  • checkbox는 value/e.target.value가 아니라 checked/e.target.checked를 쓴다는 점이 다른 입력 요소와 구분되는 부분이다.
  • form의 제출 이벤트를 다룰 땐 e.preventDefault()로 브라우저의 기본 새로고침 동작을 막는 걸 잊지 말자.

0개의 댓글