Language/React.js

[React] TodoList 프로젝트 : 시작

JJcoding 2024. 10. 18. 19:05

간단한 TodoList 프로젝트를 만들어보자.

일단 완성본은?

이렇게 생겼고, 코드를 작성해보자.

Header.jsx

import "./Header.css";

const Header = () => {
  return (
    <div className="Header">
      <h3>오늘은🐿️💛</h3>
      <h1>{new Date().toDateString()}</h1>
    </div>
  );
};

export default Header;
  • 가장 상위 컴포넌트로서 제목과 날짜를 표시한다. 

 

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 onSubmit = () => {
    if (content === "") {
      contentRef.current.focus();
      return;
    }
    onCreate(content);
    setContent("");
  };

  const onKeydown = (e) => {
    if (e.keyCode === 13) {
      onSubmit();
    }
  };

  return (
    <div className="Editor">
      <input
        ref={contentRef}
        value={content}
        onKeyDown={onKeydown}
        onChange={onChangeContent}
        placeholder="새로운 Todo를 입력하세요."
      />
      <button onClick={onSubmit}>추가</button>
    </div>
  );
};

export default Editor;

  • 새로운 할일을 추가하는 컴포넌트
  • onKeydown() - 엔터 키로도 추가 되도록 한다.
  • 내용이 비어 있으면 추가되지 않고 입력상자에 포커스 되도록 한다.
  • 내용을 추가 했으면 입력 상자를 비운다.

 

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 (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) => {
          return (
            <TodoItem
              key={todo.id}
              {...todo}
              onUpdate={onUpdate}
              onDelete={onDelete}
            />
          );
        })}
      </div>
    </div>
  );
};

export default List;
  • filter 사용하여 대소문자 구분 없이 검색 가능하도록 한다.
  • map 사용하여 TodoItem 컴포넌트에서 각각의 값을 가져온다.

 

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;
  • 각각의 할일 요소들을(체크박스, 내용, 날짜, 삭제버튼)을 렌더링 하는 컴포넌트
  • id를 이용하여 요소마다 삭제, 수정할 수 있다.

 

App.jsx

import "./App.css";
import Header from "./components/Header";
import Editor from "./components/Editor";
import List from "./components/List";
import { useState, useRef } from "react";

const mockDate = [
  {
    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(mockDate);
  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를 갖는 Todo Item의 isDone을 변경

    // 인수 : todos 배열에서 targetId와 일치하는 id를 갖는 요소의 데이터만 딱 바꾼 새로운 배열
    setTodos(
      todos.map((todo) => {
        // if (todo.is === targetId) {
        //   return {
        //     ...todo,
        //     isDone: !todo.isDone,
        //   };
        // }
        // return todo;
        return 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;

  • 최상위 부모 컴포넌트로 Editor 컴포넌트와 List 컴포넌트에 props로 변수와 이벤트 핸들러를 보낸다.

 

다음 포스팅에는 useReducer를 사용해 TodoList를 업데이트 해볼 예정이다.

출처 : 한입 크기로 잘라먹는 리액트