본문 바로가기

react

React / state, props를 이용해 CRUD 기능을 가진 Todo-List 만들기

 

App.jsx

더보기

 

import { useState } from 'react';
import './App.css';

function App() {
  const [todoList, setTodoList] = useState([
    { id: 0, content: '밥먹기' },
    { id: 1, content: '공부하기' },
    { id: 2, content: '잠자기' },
  ]);

  return (
    <>
      <TodoInput todoList={todoList} setTodoList={setTodoList} />
      <hr />
      <TodoList todoList={todoList} setTodoList={setTodoList} />
    </>
  );
}

function TodoInput({ todoList, setTodoList }) {
  const [inputValue, setInputValue] = useState('');
  const insertTodo = () => {
    if (!inputValue.trim()) {
      alert('글자를 입력해주세요.');
      return;
    }
    const newTodo = { id: Number(new Date()), content: inputValue };
    const newTodoList = [...todoList, newTodo];
    setTodoList(newTodoList);
    setInputValue('');
  };
  return (
    <>
      <input
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <button onClick={insertTodo}>추가하기</button>
    </>
  );
}

function TodoList({ todoList, setTodoList }) {
  return (
    <ul>
      {todoList.map((todo) => (
        <Todo key={todo.id} todo={todo} setTodoList={setTodoList} />
      ))}
    </ul>
  );
}

function Todo({ todo, setTodoList }) {
  const [inputValue, setInputValue] = useState('');
  const updateTodo = () => {
    // 빈 문자열일 경우
    if (!inputValue.trim()) {
      alert('글자를 입력해주세요.');
      return;
    }
    setTodoList((prev) =>
      prev.map((el) =>
        el.id === todo.id ? { ...el, content: inputValue } : el,
      ),
    );
    setInputValue('');
  };

  const deleteTodo = () => {
    setTodoList((prev) => {
      return prev.filter((el) => el.id !== todo.id);
    });
  };

  return (
    <li>
      {todo.content}{' '}
      <input
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <button onClick={updateTodo}>수정</button>
      <button onClick={deleteTodo}>삭제</button>
    </li>
  );
}

export default App;

 

App.jsx에 컴포넌트들을 다 넣어줬고 각 컴포넌트는 다음과 같다.

 

  • App: todoList의 상태를 갖고 있는 최상위 컴포넌트
  • TodoInput: 새로운 할 일을 입력하고 추가하는 컴포넌트
  • TodoList: 할 일 목록을 출력하는 컴포넌트
  • Todo: 각 할 일 항목 하나를 나타내는 컴포넌트 (수정, 삭제 기능 포함)

 

 

App

function App() {
  const [todoList, setTodoList] = useState([
    { id: 0, content: '밥먹기' },
    { id: 1, content: '공부하기' },
    { id: 2, content: '잠자기' },
  ]);

  // 자식 컴포넌트 TodoInput, TodoList에게 todoList들의 상태를 담은 props를 전달
  return (
    <>
      <TodoInput todoList={todoList} setTodoList={setTodoList} />
      <hr />
      <TodoList todoList={todoList} setTodoList={setTodoList} />
    </>
  );
}

 

 

 

TodoInput

function TodoInput({ todoList, setTodoList }) {
  // input 요소의 상태 생성
  const [inputValue, setInputValue] = useState('');
  const insertTodo = () => {
    // 빈 문자열 방지
    if (!inputValue.trim()) {
      alert('글자를 입력해주세요.');
      return; 
    }
    // id의 고유성을 위해 Date()객체를 생성해 부여한다
    const newTodo = { id: Number(new Date()), content: inputValue };
    // 새 변수 newTodoList : 기존 리스트를 스프레드 연산자로 복사, 새 할일이 담겨있음
    const newTodoList = [...todoList, newTodo];
    // newTodoList가 setTodoList로 업데이트됨
    setTodoList(newTodoList);
    setInputValue('');
  }
  return (
    <>
      {/* 초기 value는 inputValue, 
      value가 변경될 시 setInputValue 함수로 value가 바뀌게 설정 */}
      <input value={inputValue} onChange={(e) => setInputValue(e.target.value)} />
      <button
        {/* 클릭 시 이벤트 */}
        onClick={insertTodo}>
        추가하기
      </button>
    </>
  );
}

 

 

 

todoList

function TodoList({ todoList, setTodoList }) {
  return (
    <ul>
      {/* todoList를 map으로 순회해 Todo 컴포넌트 렌더링 */} 
      {todoList.map((todo) => (
        <Todo key={todo.id} todo={todo} setTodoList={setTodoList} />
      ))}
    </ul>
  );
}

 

 

 

todo

리액트는 불변성을 중요시하기 때문에 스프레드 연산자로 기존 배열을 직접 바꾸지 않고 복사한 새 배열을 생성해 교체하면,

변경 사항을 감지하고 화면이 리렌더링된다.

function Todo({ todo, setTodoList }) {
  // input의 상태 생성
  const [inputValue, setInputValue] = useState('');
  
  // 수정 기능
  const updateTodo = () => {
    // 빈 문자열일 경우
    if (!inputValue.trim()) {
      alert('글자를 입력해주세요.');
      return;
    }
    // 빈 문자열이 아니면
    setTodoList((prev) =>
      // 기존 todoList 배열을 하나씩 순회하면서 각 el(element)확인
      prev.map((el) =>
      	// el.id === todo.id인 항목의 content만 수정, 아니면 그냥 el 반환
        el.id === todo.id ? { ...el, content: inputValue } : el,
      ),
    ); // 새로운 배열이 상태로 들어감 => 리렌더링됨
    // input창 초기화
    setInputValue('');
  };

  // 삭제 기능
  const deleteTodo = () => {
    setTodoList((prev) => {
      // 클릭한 항목의 id와 일치하지 않는 것만 남김 (삭제 버튼을 클릭한 항목은 제거됨)
      return prev.filter((el) => el.id !== todo.id);
    });
  };

  return (
    <li>
      {todo.content}{' '}
      <input
        value={inputValue}
        onChange={(e) => setInputValue(e.target.value)}
      />
      <button onClick={updateTodo}>수정</button>
      <button onClick={deleteTodo}>삭제</button>
    </li>
  );
}

 

 

 

 

 

 

'react' 카테고리의 다른 글

React / Router  (0) 2025.04.09
React / 조건부 렌더링  (0) 2025.04.09
React / props, state 끌어올리기  (0) 2025.04.06
React / SPA vs MPA  (0) 2025.04.04
React / 상태관리  (0) 2025.04.04