6)프로젝트 카운터앱 만들
Controller.jsx
const Controller = ({ onClickButton }) => {
return <div>
<button onClick={() => {
onClickButton(-1)
}}
>
-1
</button>
<button
onClick={() => {
onClickButton(-10);
}}
>-10</button>
<button
onClick={() => {
onClickButton(-100);
}}>-100</button>
<button
onClick={() => {
onClickButton(+100);
}}>+100</button>
<button
onClick={() => {
onClickButton(+10);
}}>+10</button>
<button
onClick={() => {
onClickButton(+1);
}}>+1</button>
</div>
};
export default Controller;
Controller 컴포넌트는
부모로부터 전달받은 onClickButton 함수를 props로 받아,
각 버튼을 클릭했을 때 해당 값(−1, −10, −100, +1, +10, +100)을 인자로 전달하는 역할을 합니다.
즉, 버튼 UI + 클릭 이벤트 전달자로서,
자신은 상태를 직접 관리하지 않고 부모 컴포넌트의 상태 변경을 트리거하는 중간 제어 역할을 합니다.
const Viewer = ({ count }) => {
return (<div>
<div>현재카운트:</div>
<h1>{count}</h1>
</div>
);
};
export default Viewer;
Viewer 컴포넌트는
부모로부터 전달받은 count 값을 화면에 표시하는 출력 전용 UI 컴포넌트입니다.
‘현재카운트:’라는 문구와 함께 전달된 count 값을 <h1> 태그로 렌더링하며,
자체적인 상태나 로직 없이 데이터를 시각적으로 보여주는 역할만 합니다.
App.css
body {
padding: 20px;
}
.App {
margin: 0 auto;
width: 400px;
}
.App>section {
background-color: rgb(245, 245, 245);
border: 1px solid rgb(240, 240, 240);
border-radius: 5px;
padding: 20px;
margin-bottom: 10px;
}
이 CSS는
App 컴포넌트를 중앙 정렬된 400px 카드형 레이아웃으로 구성하고,
내부 section들을 깔끔한 박스 형태로 나열하도록 하는 스타일입니다.
main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<App />
);
이 main.jsx 코드는
React 앱을 브라우저의 root 요소에 렌더링하는 가장 기본적인 진입점(entry point)이에요.
- StrictMode: React의 개발용 검사기.
불필요한 렌더링이나 잠재적 버그를 콘솔에서 알려줌. - createRoot: React 18의 새로운 렌더링 API (이전의 ReactDOM.render 대체)
- App: 우리가 만든 메인 컴포넌트


React는 단방향 데이터 흐름(Top → Down) 을 가진다.
상태는 부모가 관리하고, 자식은 부모에게 신호만 보내는 구조로 유지된다.
이렇게 하면 데이터 흐름이 명확해지고, 예측 가능한 앱을 만들 수 있다.
7)라이프사이클

리액트 컴포넌트의 라이프사이클 흐름은 👇
Mount → Update → Unmount
- Mount (탄생) : 컴포넌트가 처음 화면에 나타남.
- Update (변화) : props나 state가 바뀌어 다시 렌더링됨.
- Unmount (죽음) : 컴포넌트가 화면에서 사라짐.
생성 → 변경 → 제거의 순서로 컴포넌트가 생명주기를 거칩니다.

-useEffect란?
리액트 컴포넌트의 사이드 이펙트를 제어하는 새로운 React Hook

useEffect는 컴포넌트의 생명주기(Mount → Update → Unmount) 를 제어하며,
특정 시점마다 원하는 “부수 효과(side effect)”를 수행하도록 도와주는 훅입니다
App.jsx
import './App.css'
import Controller from './components/Controller';
import Viewer from './components/Viewer';
import Even from './components/Even';
import { useState, useEffect, useRef } from 'react'
function App() {
const [count, setCount] = useState(0);
const [input, setInput] = useState("");
const isMount = useRef(false);
//1.마운트: 탄생
useEffect(() => {
console.log("mount");
}, [])
//2.업데이트: 변화, 리렌더링
useEffect(() => {
if (!isMount.current) {
isMount.current = true;
return;
}
console.log("update");
})
//3. 언마운트: 죽음
const onClickButton = (value) => {
setCount(count + value);
};
return (
<div className="App">
<h1>Simple Counter</h1>
<section>
<input value={input} onChange={(e) => {
setInput(e.target.value)
}}
/>
</section>
<section>
<Viewer count={count} />
{count % 2 == 0 ? <Even /> : null}
</section>
<section>
<Controller onClickButton={onClickButton} />
</section>
</div>
);
}
export default App;
1.상태 정의
App 컴포넌트는 count와 input을 useState로 관리하고,
useRef로 isMount 변수를 만들어 처음 렌더링 여부를 확인한다.
2.마운트(Mount)
컴포넌트가 처음 화면에 나타날 때
useEffect(() => { console.log("mount"); }, [])가 실행되어
콘솔에 “mount”가 출력된다.
3. 업데이트(Update)
input이나 count가 바뀌면 컴포넌트가 다시 렌더링되고,
두 번째 useEffect가 실행된다.
처음 렌더링 시에는 isMount.current를 이용해 건너뛰고,
그 이후부터는 콘솔에 “update”가 출력된다.
4. 언마운트(Unmount)
App에는 직접적인 언마운트 로직은 없지만,
count가 짝수일 때 렌더링되는 <Even /> 컴포넌트가
홀수가 되면 사라지면서 언마운트가 발생한다.
5. 이벤트 흐름
Controller에서 버튼을 클릭하면 onClickButton(value)이 실행되고,
App의 count 상태가 바뀌면서 Viewer와 Even이 다시 렌더링된다.
6. 전체 흐름 요약
입력(Controller) → 상태 변경(App) → 출력(Viewer, Even) 순으로
데이터가 한 방향으로 흐르며, React의 단방향 데이터 흐름 구조를 따른다.
Even.jsx
import { useEffect } from "react";
const Even = () => {
useEffect(() => {
//클린업,정리함수
return () => {
console.log("unmount");
};
}, []);
return <div>짝수입니다</div>;
};
export default Even;
1. 컴포넌트 정의
Even은 함수형 컴포넌트로, 화면에 “짝수입니다”라는 문구를 표시한다.
2. 마운트(Mount)
useEffect는 의존성 배열 []로 설정되어 있으므로,
컴포넌트가 처음 렌더링될 때 한 번만 실행된다.
3. 언마운트(Unmount)
useEffect 내부의 return 부분은 클린업(정리) 함수로,
컴포넌트가 화면에서 사라질 때 실행된다.
따라서 Even이 언마운트될 때 콘솔에 "unmount"가 출력된다.
4. 전체 흐름 요약
Even 컴포넌트는 화면에 렌더링될 때는 문구를 표시하고,
사라질 때 콘솔에 정리 메시지를 남기는
간단한 생명주기 테스트용 컴포넌트이다.

출력화면입니다.
8.2) 프로젝트2. 투두리스트 UI구현하기
Editor.css
.Editor {
display: flex;
gap: 10px;
}
.Editor input {
flex: 1;
padding: 15px;
border: 1px solid rgb(220, 220, 220);
border-radius: 5px;
}
.Editor button {
cursor: pointer;
width: 80px;
border: none;
background-color: rgb(37, 147, 255);
color: white;
border-radius: 5px;
}
이 CSS는 .Editor 영역의 입력창과 버튼을 가로로 나란히 배치하고, 보기 좋게 간격과 스타일을 주는 코드다.
display: flex로 한 줄에 정렬하고, gap으로 간격을 준다.
input은 가로로 넓게 차지하도록 flex: 1을 주고, 패딩과 테두리, 둥근 모서리로 깔끔하게 디자인했다.
button은 고정된 폭(80px)에 파란색 배경과 흰색 글씨, 둥근 모서리를 적용해 클릭 가능한 형태로 만든다.
Editor.jsx
import "./Editor.css";
const Editor = () => {
return <div className="Editor">
<input placeholder="새로운 Todo..." />
<button>추가</button>
</div>;
};
export default Editor;
Editor는 새로운 Todo 항목을 입력받는 입력창과 “추가” 버튼을 함께 보여주는 컴포넌트다.
className="Editor"로 지정되어 있어서, 연결된 Editor.css의 flex 스타일이 적용되어 입력창과 버튼이 가로로 나란히 배치된다.
입력창에는 "새로운 Todo..."라는 placeholder가 표시되어 사용자가 무엇을 입력해야 하는지 직관적으로 알 수 있고,
버튼은 클릭 시 새로운 Todo를 추가하는 기능이 나중에 연결될 예정인 자리다.
Editor는 Todo 입력 UI의 기본 틀을 담당하는 컴포넌트로, 입력 필드 + 추가 버튼의 레이아웃과 구조를 정의한다
Header.css
.Header>h1 {
color: rgb(37, 147, 255);
}
이 CSS는 .Header 클래스 내부의 <h1> 태그에만 스타일을 적용하는 코드다.
color: rgb(37, 147, 255); 속성으로 글자색을 파란빛 계열로 지정해, 헤더의 제목이 강조되어 보이게 만든다.
Header 컴포넌트의 제목(h1) 을 파란색으로 표시해 시각적으로 구분되고 눈에 띄도록 하는 역할을 한다.
Header.jsx
import "./Header.css";
const Header = () => {
return (
<div className="Header">
<h3>오늘은📅</h3>
<h1>{new Date().toDateString()}</h1>
</div>
);
};
export default Header;
Header는 "./Header.css"를 불러와 스타일을 적용하고,
<div className="Header"> 안에
- <h3>: “오늘은 📅”이라는 문구
- <h1>: new Date().toDateString()을 이용해 현재 날짜 문자열을 표시한다.
Header.css에서 .Header > h1의 색상을 파란색으로 지정했기 때문에 날짜 부분이 시각적으로 강조되어 보인다.
Header 컴포넌트는 오늘 날짜를 보여주는 단순한 상단 정보 영역으로, 날짜 데이터를 Date 객체로 생성해 동적으로 렌더링하고
스타일 파일과 함께 화면 상단을 구성한다.
List.css
.List {
display: flex;
flex-direction: column;
gap: 20px;
}
.List>input {
width: 100%;
border: none;
border-bottom: 1px solid rgb(220, 220, 220);
padding: 15px 0px;
}
.List>input:focus {
outline: none;
border-bottom: 1px solid rgb(37, 247, 255);
}
.List .todos_wrapper {
display: flex;
flex-direction: column;
gap: 20px;
}
.List는 전체 Todo 리스트 컨테이너로, display: flex와 flex-direction: column을 사용해 세로 방향으로 정렬하고, 각 요소 간 간격을 gap: 20px으로 설정했다.
.List > input은 리스트 검색 또는 필터 입력창으로, 테두리를 없애고 하단에만 얇은 회색 선을 주어 깔끔한 인풋 스타일을 만든다.
padding을 주어 입력 영역이 답답하지 않게 공간을 확보했다.
List > input:focus는 사용자가 클릭했을 때의 스타일로, 하단 테두리 색이 파란색(rgb(37, 147, 255))으로 바뀌어 활성화된 상태를 시각적으로 강조한다.
List .todos_wrapper는 실제 Todo 항목들을 세로로 나열하기 위한 영역이며, flex를 세로 방향으로 설정하고 gap: 20px으로 항목 간 간격을 준다.
List.css는 입력창과 Todo 목록을 세로로 정렬하고, 입력 시점에 포커스 효과를 주어 시각적으로 깔끔한 리스트 UI를 구성하는 스타일 파일이다.
Lisx.jsx
import './List.css';
import TodoItem from './TodoItem';
const List = () => {
return (
<div className="List">
<h4>Todo List🌱</h4>
<input placeholder="검색어를 입력하세요" />
<div className="todos_wrapper">
<TodoItem />
<TodoItem />
<TodoItem />
</div>
</div>
);
};
export default List;
List 컴포넌트는 "./List.css"를 불러와 스타일을 적용하고, TodoItem 컴포넌트를 여러 개 렌더링해 Todo 목록의 기본 틀을 만든다.
먼저 <h4> 태그를 통해 “Todo List📝”라는 제목을 표시하고, 그 아래 input 요소를 배치해 사용자가 검색어를 입력하거나 Todo를 필터링할 수 있도록 했다.
<div className="todos_wrapper"> 안에는 <TodoItem /> 컴포넌트를 3개 넣어,
실제 Todo 항목들이 세로로 나열되는 구조를 표현한다.
.List와 .todos_wrapper의 CSS 설정(flex-direction: column, gap) 덕분에 입력창과 항목들이 자연스럽게 세로로 정렬되고 일정한 간격을 유지한다.
List 컴포넌트는 Todo 목록의 전체 레이아웃을 담당하며, 제목, 검색창, 그리고 여러 개의 Todo 항목을 묶어주는 리스트 컨테이너 역할을 한다.
TodoItem.css
.TodoItem {
display: flex;
align-items: center;
gap: 20px;
padding-bottom: 20px;
border-bottom: 1px solid rgb(240, 240, 240);
}
.TodoItem input {
width: 20px;
}
.TodoItem .content {
flex: 1;
}
.TodoItem .date {
font-size: 14px;
color: gray;
}
.TodoItem button {
cursor: pointer;
color: gray;
font-size: 14px;
border: none;
border-radius: 5px;
padding: 5px;
}
.TodoItem은 각 할 일 항목의 전체 컨테이너로, display: flex와 align-items: center를 사용해 체크박스, 내용, 날짜, 버튼이 가로로 한 줄에 정렬되도록 한다.
아래쪽에는 border-bottom을 주어 항목 간의 구분선을 만든다.
.TodoItem input은 체크박스의 크기를 지정하고, TodoItem .content는 flex: 1로 남은 공간을 넓게 차지해 할 일의 본문 내용이 가운데 자연스럽게 위치하도록 한다.
.TodoItem .date는 회색 글씨와 작은 글자 크기로 할 일의 등록일 또는 수정일 등을 표시하는 보조 정보 영역이다.
.TodoItem button은 클릭 가능한 삭제 버튼 등으로, 회색 글씨와 둥근 테두리를 주어 자연스러운 인터페이스를 만든다.
TodoItem.css는 각 할 일 항목을 한 줄 단위로 정돈하고, 체크박스–내용–날짜–버튼이 일렬로 배치된 깔끔하고 읽기 쉬운 리스트 형태로 보여주는 역할을 한다.
TodoItem.jsx
import './TodoItem.css';
const TodoItem = () => {
return <div className="TodoItem">
<input type="checkbox" />
<div className="content">Todo...</div>
<div className="date">Date</div>
<button>삭제</button>
</div>
};
export default TodoItem;
TodoItem은 "./TodoItem.css"를 불러와 스타일을 적용하고, 각 Todo 항목을 한 줄로 구성한다.
가장 바깥쪽의 <div className="TodoItem">은 한 개의 Todo 항목 전체를 감싸는 컨테이너다.
그 안에는
- <input type="checkbox" />: 완료 여부를 표시하는 체크박스
- <div className="content">Todo...</div>: 할 일의 내용
- <div className="date">Date</div>: 작성일이나 일정 날짜 표시
- <button>삭제</button>: 해당 항목을 삭제하는 버튼
이 순서로 배치되어 있다.
CSS에서 flex 정렬을 적용했기 때문에 이 요소들이 한 줄에 깔끔하게 정렬된다.
TodoItem 컴포넌트는 하나의 할 일 정보를 시각적으로 보여주는 단위 컴포넌트로, 체크박스, 내용, 날짜, 삭제 버튼을 포함한
Todo 리스트의 기본 구성 요소 역할을 한다.

출력화면입니다.
8.3~8.4) 기능 구현 준비하기, Create- 투두 추가하기
App.jsx
import './App.css';
import { useState, useRef } from "react";
import Header from './components/Header';
import List from './components/List';
import Editor from './components/Editor';
const mockData = [
{
id: 0,
isDone: false,
content: "React 공부하기",
date: new Date().getTime(),
},
{
id: 1,
isDone: false,
content: "빨래하기",
date: new Date().getTime(),
},
{
id: 2,
isDone: false,
content: "노래연습하기",
date: new Date().getTime(),
}
];
function App() {
const [todos, setTodos] = useState(mockData);
const idRef = useRef(3)
const onCreate = (content) => {
const newTodo = {
id: idRef.current++,
isDone: false,
content: content,
date: new Date().getTime(),
};
setTodos([newTodo, ...todos]);
}
return (
<div className="App">
<Header />
<Editor onCreate={onCreate} />
<List />
</div>
)
}
export default App
1.초기 데이터 구성
mockData 배열을 통해 미리 세 개의 할 일(React 공부하기, 빨래하기, 노래연습하기)이 정의되어 있다.
이 데이터는 컴포넌트가 처음 렌더링될 때 useState를 통해 todos 상태로 저장되어,
초기 화면에서 기본 할 일 목록이 보이도록 한다.
2.상태 관리와 참조
todos는 현재 등록된 모든 할 일 목록을 저장하는 상태이며,
setTodos를 통해 새 항목이 추가될 때마다 갱신된다.
또한 useRef(3)로 생성된 idRef는 새로 추가되는 할 일에 고유한 id를 부여하기 위한 값으로,
렌더링이 반복되어도 초기화되지 않고 계속 증가하는 고정 참조값이다.
3.새로운 Todo 추가(onCreate 함수)
onCreate 함수는 Editor 컴포넌트로부터 전달받은 content를 기반으로 새 Todo 객체를 만든다.
- id: idRef.current++를 사용해 고유 번호 부여
- isDone: 아직 완료되지 않았으므로 false
- content: 입력된 텍스트
- date: 생성 시점의 타임스탬프
이렇게 생성된 새 Todo는 setTodos([newTodo, ...todos])로 기존 목록 앞에 추가된다.
즉, 새로운 항목이 가장 위에 표시되도록 구성되어 있다.
4.컴포넌트 구조
렌더링 부분에서는
- <Header />: 오늘 날짜를 표시
- <Editor onCreate={onCreate} />: 새로운 Todo를 입력받고 추가 버튼을 누르면 onCreate 호출
- <List />: Todo 목록을 표시하는 영역(아직 todos 전달은 안 되어 있지만, 이후 props로 연결 예정)
이 세 컴포넌트를 조합해 Todo 앱의 전체 UI를 구성한다.
5.전체 흐름 요약
App은 Todo 데이터(todos)를 중앙에서 관리하며,
새로운 항목이 추가될 때마다 상태를 갱신하고 하위 컴포넌트들(Header, Editor, List)에 필요한 기능을 내려보낸다.
Editor.jsx
import "./Editor.css";
import { useState, useRef } from "react";
const Editor = ({ onCreate }) => {
const [content, setContent] = useState("");
const contentRef = useRef();
const onChangeContent = (e) => {
setContent(e.target.value);
};
const onKeydown = (e) => {
if (e.keyCode === 13) {
onSubmit();
};
};
const onSubmit = () => {
if (content === "") {
contentRef.current.focus();
return;
}
onCreate(content);
setContent("");
};
return <div className="Editor">
<input
ref={contentRef}
value={content}
onKeyDown={onKeydown}
onChange={onChangeContent}
placeholder="새로운 Todo..." />
<button onClick={onSubmit}>추가</button>
</div>;
};
export default Editor;
1.상태 관리와 참조 설정
useState를 통해 입력창의 내용(content)을 상태로 관리하며,
useRef로 생성한 contentRef는 입력창 DOM을 직접 가리켜
입력값이 비어 있을 때 포커스를 줄 수 있게 한다.
2.입력 처리
onChangeContent 함수는 사용자가 입력할 때마다
e.target.value를 읽어와 content 상태를 즉시 업데이트한다.
즉, 입력창의 내용과 내부 상태가 항상 동기화되어 있다.
3. 엔터 키 입력 처리
onKeydown 함수는 사용자가 키보드를 눌렀을 때 실행되며,
keyCode === 13(Enter 키)인 경우 onSubmit() 함수를 호출한다.
이를 통해 사용자가 버튼을 클릭하지 않아도 Enter로 할 일을 추가할 수 있다.
4. 추가 버튼 클릭 및 검증 로직
onSubmit 함수는 입력값이 비어 있는 경우
contentRef.current.focus()를 이용해 입력창에 포커스를 주고 함수 실행을 멈춘다.
입력값이 있으면 부모 컴포넌트(App)로부터 받은 onCreate(content)를 실행해
새로운 Todo를 등록하고, 이후 setContent("")로 입력창을 비워준다.
5. 렌더링 구조
렌더링된 결과는 입력창(input)과 추가 버튼(button)으로 구성되어 있으며,
input에는 ref, value, onChange, onKeyDown 이벤트가 연결되어 있다.
버튼을 클릭하거나 Enter를 누르면 onSubmit이 실행되어 Todo가 추가된다.

화면 출력 내용입니다.
8.5) Read-투두리스트 랜더링하기
App.jsx
import './App.css';
import { useState, useRef } from "react";
import Header from './components/Header';
import List from './components/List';
import Editor from './components/Editor';
const mockData = [
{
id: 0,
isDone: false,
content: "React 공부하기",
date: new Date().getTime(),
},
{
id: 1,
isDone: false,
content: "빨래하기",
date: new Date().getTime(),
},
{
id: 2,
isDone: false,
content: "노래연습하기",
date: new Date().getTime(),
}
];
function App() {
const [todos, setTodos] = useState(mockData);
const idRef = useRef(3)
const onCreate = (content) => {
const newTodo = {
id: idRef.current++,
isDone: false,
content: content,
date: new Date().getTime(),
};
setTodos([newTodo, ...todos]);
}
return (
<div className="App">
<Header />
<Editor onCreate={onCreate} />
<List todos={todos} />
</div>
)
}
export default App
1.초기 설정과 상태 정의
App 컴포넌트는 mockData를 초기값으로 하여 todos 상태를 생성하고, 각 Todo에 고유한 id를 부여하기 위해 useRef(3)를 사용한다. 이로써 앱이 실행되면 세 개의 기본 Todo(React 공부하기, 빨래하기, 노래연습하기)가 처음부터 화면에 표시된다.
2.새로운 Todo 추가 동작
Editor 컴포넌트에서 사용자가 새로운 할 일을 입력하고 추가 버튼을 누르면 onCreate(content)가 실행된다.
이 함수는 새로운 Todo 객체를 만들고(id, content, date 포함), setTodos([newTodo, ...todos])로 기존 목록 앞에 추가해 상태를 갱신한다.
이로 인해 화면이 다시 렌더링되며 새 항목이 즉시 리스트에 반영된다.
3.렌더링과 데이터 흐름
Header는 오늘 날짜를 표시하고, Editor는 새로운 할 일 입력을 담당하며,
List는 todos 배열을 받아 Todo 목록을 화면에 보여준다.
즉, App이 모든 데이터를 관리하고 하위 컴포넌트로 전달하는 구조로, 입력(Editor) → 상태 변경(App) → 출력(List) 순서로 실행 흐름이 이어진다.
List.jsx
import './List.css';
import TodoItem from './TodoItem';
import { useState } from 'react';
const List = ({ todos }) => {
const [search, setSearch] = useState('');
const onChangeSearch = (e) => {
setSearch(e.target.value);
};
const getFilteredData = () => {
if (!todos) return [];
if (search === '') return todos;
return todos.filter((todo) =>
todo.content.toLowerCase().includes(search.toLowerCase())
);
};
const filteredTodos = getFilteredData();
return (
<div className="List">
<h4>Todo List🌱</h4>
<input
value={search}
onChange={onChangeSearch}
placeholder="검색어를 입력하세요"
/>
<div className="todos_wrapper">
{filteredTodos.map((todo) => (
<TodoItem key={todo.id} {...todo} />
))}
</div>
</div>
);
};
export default List;
1.상태 관리와 검색 입력 처리
List 컴포넌트는 내부에서 search 상태를 관리하며, 사용자가 입력창에 검색어를 입력할 때마다 onChangeSearch가 실행되어
setSearch(e.target.value)로 상태를 갱신한다.
이로써 입력창에 적은 검색어가 실시간으로 반영된다.
2.검색어 필터링 로직 실행
getFilteredData 함수는 현재 검색 상태에 따라 보여줄 데이터를 결정한다.
검색어가 비어 있으면 모든 todos를 반환하고, 검색어가 있을 경우 filter와 includes를 이용해 content 속성에 검색어가 포함된 Todo만 남긴다.
대소문자 구분을 없애기 위해 .toLowerCase()로 비교한다.
3.렌더링과 데이터 표시
filteredTodos 배열을 순회하며 각 항목을 <TodoItem>으로 렌더링한다.
검색창과 리스트가 함께 <div className="List"> 내부에 배치되어 입력 → 필터링 → 출력의 흐름이 자연스럽게 이어진다.
즉, List 컴포넌트는 Todo 전체 목록을 받아 검색어로 필터링한 뒤, 해당 결과를 TodoItem 단위로 화면에 출력하는 역할을 수행한다.
TodoItem.jsx
import './TodoItem.css';
const TodoItem = ({ id, isDone, content, date }) => {
return <div className="TodoItem">
<input readOnly checked={isDone} type="checkbox" />
<div className="content">{content}</div>
<div className="date">{new Date(date).toLocaleDateString()}</div>
<button>삭제</button>
</div>
};
export default TodoItem;
1.컴포넌트 구조
TodoItem은 하나의 Todo 데이터를 화면에 표시하는 단위 컴포넌트로, 각 항목(id, isDone, content, date)을 props로 전달받아 출력한다.
2.화면 표시 흐름
가장 바깥쪽 <div className="TodoItem">이 한 줄짜리 Todo 영역을 감싸며,
그 안에는
- 완료 여부를 표시하는 checkbox,
- Todo 내용(content),
- 날짜(date),
- 삭제 버튼(button)
이 순서대로 배치된다.
날짜는 new Date(date).toLocaleDateString()을 이용해
현재 지역 형식(예: YYYY.MM.DD)으로 변환되어 표시된다.
3.역할 정리
체크박스는 완료 여부를 시각적으로 보여주지만 readOnly로 설정되어 있어 직접 수정은 불가능하고 표시용이다.
삭제 버튼은 향후 삭제 기능을 연결할 수 있는 자리다.
TodoItem은 하나의 할 일 정보를 한 줄로 구성해 내용·날짜·상태를 깔끔하게 보여주는 UI 컴포넌트이다.

->소문자로 react로 검색해도 React 공부하기라고 뜨는 실행화면모습이다.
8.6) Update -투두 수정하기
App.jsx
import './App.css';
import { useState, useRef } from "react";
import Header from './components/Header';
import List from './components/List';
import Editor from './components/Editor';
const mockData = [
{
id: 0,
isDone: false,
content: "React 공부하기",
date: new Date().getTime(),
},
{
id: 1,
isDone: false,
content: "빨래하기",
date: new Date().getTime(),
},
{
id: 2,
isDone: false,
content: "노래연습하기",
date: new Date().getTime(),
}
];
function App() {
const [todos, setTodos] = useState(mockData);
const idRef = useRef(3)
const onCreate = (content) => {
const newTodo = {
id: idRef.current++,
isDone: false,
content: content,
date: new Date().getTime(),
};
setTodos([newTodo, ...todos]);
};
const onUpdate = (targetId) => {
//todos State 값들 중에
//targetId와 일치하는 id를 갖는 투두아이템의 isDone변경
//인수:todos 배열에서 targetId와 일치하는 id를 갖는 요소의 데이터만 딱 바꾼 새로운 배열
setTodos(
todos.map((todo) =>
todo.id === targetId
? { ...todo, isDone: !todo.isDone }
: todo
)
);
}
return (
<div className="App">
<Header />
<Editor onCreate={onCreate} />
<List todos={todos} onUpdate={onUpdate} />
</div>
)
}
export default App
onUpdate 함수가 있어서 체크박스 클릭 시 해당 Todo의 isDone 상태가 true/false로 바뀌는 완료 토글 기능이 생겼다.
onUpdate 함수는 특정 Todo 항목의 id를 받아서,
그 id와 일치하는 Todo 객체의 isDone 값을 반대로 바꾸는 역할을 합니다.
- isDone: false → 체크박스 클릭 시 true로 변경 (완료 상태)
- isDone: true → 다시 클릭 시 false로 변경 (미완료 상태)
- 이 과정을 통해 사용자는 할 일을 체크하거나 해제할 수 있게 됩니다.
List.jsx
import './List.css';
import TodoItem from './TodoItem';
import { useState } from 'react';
const List = ({ todos, onUpdate }) => {
const [search, setSearch] = useState('');
const onChangeSearch = (e) => {
setSearch(e.target.value);
};
const getFilteredData = () => {
if (!todos) return [];
if (search === '') return todos;
return todos.filter((todo) =>
todo.content.toLowerCase().includes(search.toLowerCase())
);
};
const filteredTodos = getFilteredData();
return (
<div className="List">
<h4>Todo List🌱</h4>
<input
value={search}
onChange={onChangeSearch}
placeholder="검색어를 입력하세요"
/>
<div className="todos_wrapper">
{filteredTodos.map((todo) => (
<TodoItem key={todo.id} {...todo} onUpdate={onUpdate} />
))}
</div>
</div>
);
};
export default List;
onUpdate 전달 추가
이전에는 List가 todos만 전달했지만, 이제 onUpdate 함수를 함께 TodoItem으로 전달한다.
따라서 각 Todo 항목에서 체크박스를 클릭하면 이 함수가 실행된다.
완료 상태 토글 가능
TodoItem이 클릭된 Todo의 id를 onUpdate(id)로 전달하면, App 컴포넌트에서 해당 Todo의 isDone 값이 반전되어
완료 ↔ 미완료 상태가 실시간으로 변경된다.
TodoItem.jsx
import './TodoItem.css';
const TodoItem = ({ id, isDone, content, date, onUpdate }) => {
const onChangeCheckbox = () => {
onUpdate(id);
};
return <div className="TodoItem">
<input
onChange={onChangeCheckbox}
readOnly
checked={isDone}
type="checkbox"
/>
<div className="content">{content}</div>
<div className="date">
{new Date(date).toLocaleDateString()}
</div>
<button>삭제</button>
</div>
};
export default TodoItem;
1.이벤트 핸들러 추가
체크박스를 클릭하면 onChangeCheckbox 함수가 실행되고, 이 함수가 onUpdate(id)를 호출해 해당 Todo의 id를 부모(App)로 전달한다.
2.부모(App)에서 상태 변경
App 컴포넌트의 onUpdate 함수가 실행되어 전달받은 id와 일치하는 Todo의 isDone 값을 반전시킨다
(true ↔ false).
3. 화면 자동 갱신
상태가 업데이트되면 React가 자동으로 다시 렌더링하여 체크박스의 체크 여부가 즉시 바뀌고, 완료/미완료 상태가 실시간으로 표시된다.

->React 공부하기를 누르니 isDone:false에서 true가 된 모습을 볼수있는 실행화면이다.
8.7) Delete - 투두 삭제하기
App.jsx
import './App.css';
import { useState, useRef } from "react";
import Header from './components/Header';
import List from './components/List';
import Editor from './components/Editor';
const mockData = [
{
id: 0,
isDone: false,
content: "React 공부하기",
date: new Date().getTime(),
},
{
id: 1,
isDone: false,
content: "빨래하기",
date: new Date().getTime(),
},
{
id: 2,
isDone: false,
content: "노래연습하기",
date: new Date().getTime(),
}
];
function App() {
const [todos, setTodos] = useState(mockData);
const idRef = useRef(3)
const onCreate = (content) => {
const newTodo = {
id: idRef.current++,
isDone: false,
content: content,
date: new Date().getTime(),
};
setTodos([newTodo, ...todos]);
};
const onUpdate = (targetId) => {
//todos State 값들 중에
//targetId와 일치하는 id를 갖는 투두아이템의 isDone변경
//인수:todos 배열에서 targetId와 일치하는 id를 갖는 요소의 데이터만 딱 바꾼 새로운 배열
setTodos(
todos.map((todo) =>
todo.id === targetId
? { ...todo, isDone: !todo.isDone }
: todo
)
);
};
const onDelete = (targetId) => {
//인수: todos 배열에서 targetId와 일치하는 id를 갖는 요소만 삭제한 새로운 배열
setTodos(todos.filter((todo) => todo.id !== targetId))
};
return (
<div className="App">
<Header />
<Editor onCreate={onCreate} />
<List todos={todos}
onUpdate={onUpdate}
onDelete={onDelete}
/>
</div>
)
}
export default App
1.삭제 버튼 클릭 시 이벤트 발생
사용자가 TodoItem의 “삭제” 버튼을 누르면 onClick 이벤트가 실행되고, 그 Todo의 고유한 id가 인수로 전달되어 onDelete(id) 함수가 호출된다.
2. 이벤트 전달
List는 이 onDelete 함수를 App으로 전달하고, App의 onDelete(targetId)가 실행되면
setTodos(todos.filter((todo) => todo.id !== targetId))를 통해 해당 id를 가진 Todo 항목이 배열에서 제거된다.
3. 화면 리렌더링 및 반영 —
React가 상태 변화(todos 배열 변경)를 감지하고 자동으로 화면을 다시 렌더링하여, 삭제된 Todo가 즉시 화면에서 사라진다.
List.jsx
import './List.css';
import TodoItem from './TodoItem';
import { useState } from 'react';
const List = ({ todos, onUpdate, onDelete }) => {
const [search, setSearch] = useState('');
const onChangeSearch = (e) => {
setSearch(e.target.value);
};
const getFilteredData = () => {
if (!todos) return [];
if (search === '') return todos;
return todos.filter((todo) =>
todo.content.toLowerCase().includes(search.toLowerCase())
);
};
const filteredTodos = getFilteredData();
return (
<div className="List">
<h4>Todo List🌱</h4>
<input
value={search}
onChange={onChangeSearch}
placeholder="검색어를 입력하세요"
/>
<div className="todos_wrapper">
{filteredTodos.map((todo) => (
<TodoItem key={todo.id}
{...todo}
onUpdate={onUpdate}
onDelete={onDelete} />
))}
</div>
</div>
);
};
export default List;
1.삭제 버튼 클릭 (이벤트 발생)
사용자가 TodoItem의 “삭제” 버튼을 누르면 그 항목의 고유 id가 인수로 전달되어 onDelete(id) 함수가 실행된다.
2. 이벤트 전달 (List → App)
List 컴포넌트는 삭제 로직을 직접 처리하지 않고, App에서 받은 onDelete 함수를 TodoItem으로 전달하는 중간 전달자 역할을 한다.TodoItem이 클릭 이벤트를 발생시키면 App의 onDelete가 호출되어 실제 데이터가 변경된다.
TodoItem.jsx
import './TodoItem.css';
const TodoItem = ({ id, isDone, content, date, onUpdate, onDelete }) => {
const onChangeCheckbox = () => {
onUpdate(id);
};
const onClickDeleteButton = () => {
onDelete(id);
};
return (
<div className="TodoItem">
<input
onChange={onChangeCheckbox}
checked={isDone}
type="checkbox"
/>
<div className="content">{content}</div>
<div className="date">{new Date(date).toLocaleDateString()}</div>
<button onClick={onClickDeleteButton}>삭제</button>
</div>
);
};
export default TodoItem;
체크박스 클릭 시
사용자가 체크박스를 누르면 onChangeCheckbox 함수가 실행되어 onUpdate(id)가 호출되고, 해당 Todo의 완료 상태(isDone)가
true ↔ false로 반전된다.

->React 공부하기와 빨래하기가 삭제된 실행화면입니다.