πŸ” ν”„λ‘œμ νŠΈ2. todolist

κΉ€μˆ˜μ§„Β·2025λ…„ 7μ›” 9일

ν•œ μž… 크기둜 잘라 λ¨ΉλŠ” λ¦¬μ•‘νŠΈ μ„Ήμ…˜ 9- ν”„λ‘œμ νŠΈ2. νˆ¬λ‘λ¦¬μŠ€νŠΈ

UI κ΅¬ν˜„ν•˜κΈ°

<Header/>
<Editor/>
<List/>

둜 λ‚˜λˆˆλ‹€.

κΈ°λŠ₯ κ΅¬ν˜„ν•˜κΈ°

mockData 잘 λΆˆλŸ¬μ™€μ§€λŠ”μ§€ ν™•μΈν•œλ‹€.

ν•­λͺ©μ„ μΆ”κ°€ν•˜λ©΄ μ½˜μ†”μ— μΆ”κ°€ν•œ 값이 μ°νžˆλŠ” 것을 λ³Ό 수 μžˆλ‹€.

λ‚΄κ°€ μΆ”κ°€ν•œ κ°œκ΅΄μ΄λΌλŠ” ν•­λͺ©μ΄ λœ¨λŠ” 것을 λ³Ό 수 μžˆλ‹€.

ν•­λͺ© λ‘κ°œ 이상도 κ°€λŠ₯ν•˜λ‹€. ν•˜μ§€λ§Œ λ‘˜λ‹€ λͺ¨λ‘ id값이 0이닀.
id값이 λ‹€λ₯΄κ²Œ λ‚˜μ˜€κ²Œλ” μˆ˜μ •μ΄ ν•„μš”ν•˜λ‹€!

useRefλ₯Ό μ‚¬μš©ν•˜μ—¬ id값을 λ‹€λ₯΄κ²Œ 해보면,

idλ§ˆλ‹€ κ³ μœ κ°’μ„ 가지도둝 바뀐것을 확인할 수 μžˆλ‹€.

빈 λ¬Έμžμ—΄μ„ μΆ”κ°€ν•˜μ—¬λ„ νˆ¬λ‘κ°€ μΆ”κ°€λ˜λŠ” 문제
Editor.jsx

 const onSubmit = () => {
    if (content === "") {
      return;
    }
    onCreate(content);
  };

μž…λ ₯폼이 λΉ„μ›Œμ§€μ§€ μ•Šμ•„μ„œ μ‚¬μš©μžκ°€ λ¬Έμžμ—΄μ΄ μΆ”κ°€λ˜μ—ˆλŠ”μ§€ λͺ¨λ₯Ό μˆ˜λ„ μžˆλ‹€.
Editor.jsx

const onSubmit = () => {
    if (content === "") {
        contentRef.current.focus();
      return;
    }
    onCreate(content);
    setContent(""); //μž…λ ₯ν›„ μž…λ ₯λž€ μ΄ˆκΈ°ν™”
  };

Enter만 λˆŒλŸ¬λ„ ν•­λͺ©μ΄ 좔가될 수 μžˆλ„λ‘ 해쀄 수 μžˆλ‹€.
onKeyDown={onKeydown}λ₯Ό μ΄μš©ν•˜μ—¬ μ„€μ •ν•˜λ©΄ λœλ‹€.

μ΅œμ’… μ½”λ“œ:

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;
}

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;

Header.css

.Header > h1 {
  color: rgb(37, 147, 255);
}

Header.jsx

import "./Header.css";

const Header = () => {
  return (
    <div className="Header">
      <h3>μ˜€λŠ˜μ€ πŸ“† </h3>
      <h1>{new Date().toDateString()}</h1>
    </div>
  );
};

export default Header; //window + . 둜 이λͺ¨μ§€ 좔각 κ°€λŠ₯ν•˜λ‹€.

List.css

.List {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.List > input {
  width: 100%;
  border: one;
  border-bottom: 1px solid rgb(220, 220, 220);
  padding: 15px 0px;
}

.List > input:focus {
  outline: none;
  border-bottom: 1px solid rgb(37, 147, 255);
}
.List .todos_wrapper {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

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 (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;

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.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;

App.css

.App {
  width: 500px;
  margin: 0 auto;

  display: flex;
  flex-direction: column;
  gap: 10px;
}

App.jsx

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

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;

main.jsx

import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";

createRoot(document.getElementById("root")).render(<App />);

μ΅œμ’… κ΅¬ν˜„ ν™”λ©΄:

1개의 λŒ“κΈ€

comment-user-thumbnail
2025λ…„ 7μ›” 9일

덕뢄에 항상 μœ μš©ν•œ 정보와 κΉ¨λ‹¬μŒ μ–»κ³  κ°€μš”!

λ‹΅κΈ€ 달기