
🔗 [디자인 설계 링크 | Figma]

import ProjectSidebar from "./components/ProjectSidebar";
function App() {
return (
<main>
<ProjectSidebar />
</main>
);
}
export default App;
export default function ProjectSidebar() {
return (
<aside>
<h2>Your Projects</h2>
<div>
<button>+ Add Project</button>
</div>
<ul>...</ul>
</aside>
);
}
// App.jsx
import ProjectSidebar from "./components/ProjectSidebar";
function App() {
return (
// h-screen : 화면 세로 길이를 전부 차지
<main className="h-screen my-8">
<ProjectSidebar/>
</main>
)
}
export default App;
// ProjectSidebar.jsx
export default function ProjectSidebar() {
return (
<aside className="w-1/3 px-8 py-16 bg-stone-900 text-stone-50 md:w-72 rounded-r-xl">
<h2 className="mb-8 font-bold uppercase md:text-xl text-stone-200">
Your Projects
</h2>
<div>
<button className="px-4 py-2 text-xs md:text-base rounded-md bg-stone-700 text-stone-400 hover:bg-stone-600 hover:text-stone-100">
+ Add Project
</button>
</div>
<ul>...</ul>
</aside>
);
}
import Input from "./Input";
export default function NewProject() {
return (
<div>
<menu>
<li>
<button>Cancle</button>
</li>
<li>
<button>Save</button>
</li>
</menu>
<div>
<Input label="Title" />
<Input label="Description" textarea />
<Input label="Due Date" />
</div>
</div>
);
}
export default function Input({ label, textarea, ...props }) {
return (
<p>
<label htmlFor={label}>{label}</label>
{textarea ? (
<textarea id={label} {...props} />
) : (
<input id={label} {...props} />
)}
</p>
);
}
import ProjectSidebar from "./components/ProjectSidebar";
import NewProject from "./components/NewProject";
function App() {
return (
// h-screen : 화면 세로 길이를 전부 차지
<main className="h-screen my-8 flex gap-8">
<ProjectSidebar />
<NewProject />
</main>
);
}
export default App;

// NewProject.jsx
import Input from "./Input";
export default function NewProject() {
return (
<div className="w-[35rem] mt-16">
<menu className="flex items-center justify-end gap-4 my-4">
<li>
<button className="text-stone-800 hover:text-stone-950">Cancle</button>
</li>
<li>
<button className="px-6 py-2 rounded-md bg-stone-800 text-stone-50 hover:bg-stone-950">Save</button>
</li>
</menu>
<div>
<Input label="Title"/>
<Input label="Description" textarea/>
<Input label="Due Date"/>
</div>
</div>
);
}
// Input.jsx
export default function Input({ label, textarea, ...props }) {
const classes =
"w-full p-1 border-b-2 rounded-sm border-stone-300 bg-stone-200 text-stone-600 focus:outline-none focus:border-stone-600";
return (
<p className="flex flex-col gap-1 my-4">
<label
className="text-sm font-bold uppercase text-stone-500"
htmlFor={label}
>
{label}
</label>
{textarea ? (
<textarea className={classes} id={label} {...props} />
) : (
<input className={classes} id={label} {...props} />
)}
</p>
);
}
focus:outline-none focus:border-stone-600 : 입력하려고 눌렀을 때 기존의 outline은 지우고 대신 border color 변경export default function Button({ children, ...props }) {
return (
<button
{...props}
className="px-4 py-2 text-xs md:text-base rounded-md bg-stone-700 text-stone-400 hover:bg-stone-600 hover:text-stone-100"
>
{children}
</button>
);
}
import noProjectImage from "../assets/no-projects.png";
import Button from "./Button";
export default function NoProjectSelected() {
return (
<div className="mt-24 text-center w-2/3">
<img
src={noProjectImage}
alt="An empty task list"
className="w-16 h-16 object-contain mx-auto"
/>
<h2 className="text-xl font-bold text-stone-500 my-4">
No Project Seleted.
</h2>
<p className="text-stone-400 mb-4">
Select a project or get started with a new one.
</p>
<p className="mt-8">
<Button>Create new project</Button>
</p>
</div>
);
}
import Button from "./Button";
export default function ProjectSidebar() {
return (
<aside className="w-1/3 px-8 py-16 bg-stone-900 text-stone-50 md:w-72 rounded-r-xl">
<h2 className="mb-8 font-bold uppercase md:text-xl text-stone-200">
Your Projects
</h2>
<div>
<Button>+ Add Project</Button> {/* Button 대체 */}
</div>
<ul>...</ul>
</aside>
);
}
import { useState } from "react";
import ProjectSidebar from "./components/ProjectSidebar";
import NewProject from "./components/NewProject";
import NoProjectSelected from "./components/NoProjectSelected";
function App() {
const [projectsState, setProjectsState] = useState({
selectedProjectId: undefined, // 아무것도 안하고 있다. 라는 신호
projects: [],
});
function handleStartAddProject() {
setProjectsState((prevState) => {
return {
...prevState,
selectedProjectId: null, // 우리가 새로운 프로젝트를 추가한다는 신호
};
});
}
let content;
if (projectsState.selectedProjectId === null) {
// 새로운 프로젝트를 추가한다는 버튼을 누르면
content = <NewProject />;
} else if (projectsState.selectedProjectId === undefined) {
content = <NoProjectSelected onStartAddProject={handleStartAddProject} />;
}
return (
// h-screen : 화면 세로 길이를 전부 차지
<main className="h-screen my-8 flex gap-8">
<ProjectSidebar onStartAddProject={handleStartAddProject} />
{content}
</main>
);
}
export default App;
나는 프로젝트와 관련된 신호를 따로 넣진 않았다. 강사 코드를 보면서 해당 신호(signal)을 주고 받는다면 로직을 짜고 코드를 작성하는데 더 편하게 할 수 있을 것이란 것을 깨달았다!!
🚨 앞으로 신호를 주고받아서 상태를 업데이트하는 방법에 대해서도 생각해보자! 🚨
// ProjectSidebar.jsx
import Button from "./Button";
export default function ProjectSidebar({onStartAddProject}) {
return (
<aside className="w-1/3 px-8 py-16 bg-stone-900 text-stone-50 md:w-72 rounded-r-xl">
<h2 className="mb-8 font-bold uppercase md:text-xl text-stone-200">
Your Projects
</h2>
<div>
<Button onClick={onStartAddProject}>+ Add Project</Button>
</div>
<ul>...</ul>
</aside>
);
}
// NoProjectSelected.jsx
import noProjectImage from '../assets/no-projects.png'
import Button from './Button'
export default function NoProjectSelected({ onStartAddProject }) {
return (
<div className="mt-24 text-center w-2/3">
<img
src={noProjectImage}
alt="An empty task list"
className="w-16 h-16 object-contain mx-auto"
/>
<h2 className="text-xl font-bold text-stone-500 my-4">
No Project Seleted.
</h2>
<p className="text-stone-400 mb-4">
Select a project or get started with a new one.
</p>
<p className="mt-8">
<Button onClick={onStartAddProject}>Create new project</Button>
</p>
</div>
);
}
chilren외에도 다른 속성들이 넘어올 것을 대비해 ...props를 추가했다.onClick 동작이 간단하게 진행된 것 같다.앞으로
...props에 대한 적극적인 활용을 해보자! 솔직히 해당 속성을 사용하려고 생각해보긴 했지만 어떻게 써야할지 감이 안와서 쓰지 못했다..!
import { useRef } from "react";
import Input from "./Input";
export default function NewProject({ onAdd }) {
const title = useRef();
const description = useRef();
const dueDate = useRef();
function handleSave() {
const enteredTitle = title.current.value;
const enteredDescription = description.current.value;
const enteredDueDate = dueDate.current.value;
// validation
onAdd({
title: enteredTitle,
description: enteredDescription,
dueDate: enteredDueDate,
});
}
return (
<div className="w-[35rem] mt-16">
<menu className="flex items-center justify-end gap-4 my-4">
<li>
<button className="text-stone-800 hover:text-stone-950">
Cancle
</button>
</li>
<li>
<button
className="px-6 py-2 rounded-md bg-stone-800 text-stone-50 hover:bg-stone-950"
onClick={handleSave}
>
Save
</button>
</li>
</menu>
<div>
<Input type="text" ref={title} label="Title" />
<Input ref={description} label="Description" textarea />
<Input type="date" ref={dueDate} label="Due Date" />
</div>
</div>
);
}
useRef()를 이용해 입력받을 정보들을 각각 참조한다.import { forwardRef } from "react";
// 외부에서 들어오는 ref를 사용하기 위해 forwardRef를 사용.
const Input = forwardRef(function Input({ label, textarea, ...props }, ref) {
const classes =
"w-full p-1 border-b-2 rounded-sm border-stone-300 bg-stone-200 text-stone-600 focus:outline-none focus:border-stone-600";
//focus:outline-none focus:border-stone-600 => 입력하려고 눌렀을 때 기존의 outline은 지우고 대신 border color 변경
return (
<p className="flex flex-col gap-1 my-4">
<label
className="text-sm font-bold uppercase text-stone-500"
htmlFor={label}
>
{label}
</label>
{textarea ? (
<textarea ref={ref} className={classes} id={label} {...props} />
) : (
<input ref={ref} className={classes} id={label} {...props} />
)}
</p>
);
});
export default Input;
forwardRef()를 사용해야 한다.import { useState } from "react";
import ProjectSidebar from "./components/ProjectSidebar";
import NewProject from "./components/NewProject";
import NoProjectSelected from "./components/NoProjectSelected";
function App() {
const [projectsState, setProjectsState] = useState({
selectedProjectId: undefined, // 아무것도 안하고 있다. 라는 신호
projects: [],
});
function handleStartAddProject() {
setProjectsState((prevState) => {
return {
...prevState,
selectedProjectId: null, // 우리가 새로운 프로젝트를 추가한다는 신호
};
});
}
function handleAddProject(projectData) {
setProjectsState((prevState) => {
const newProject = {
...projectData,
id: Math.random(),
};
return {
...prevState,
projects: [...prevState.projects, newProject],
};
});
}
console.log(projectsState);
let content;
if (projectsState.selectedProjectId === null) {
// 새로운 프로젝트를 추가한다는 버튼을 누르면
content = <NewProject onAdd={handleAddProject} />;
} else if (projectsState.selectedProjectId === undefined) {
content = <NoProjectSelected onStartAddProject={handleStartAddProject} />;
}
return (
// h-screen : 화면 세로 길이를 전부 차지
<main className="h-screen my-8 flex gap-8">
<ProjectSidebar onStartAddProject={handleStartAddProject} />
{content}
</main>
);
}
export default App;
projectsState를 업데이트 해야한다.NewProject의 onAdd 속성을 통해 handleAddProject 함수를 전달한다.handleSave함수가 동작하여 projectsState를 업데이트한다.function App() {
function handleAddProject(projectData) {
setProjectsState((prevState) => {
const projectId = Math.random(); // projectId 상수를 따로 설정하여
const newProject = {
...projectData,
id: projectId, // id에 입력
};
return {
...prevState,
selectedProjectId: undefined, // Save 버튼을 누르면 창이 닫히도록 설정.
projects: [...prevState.projects, newProject],
};
});
}
let content;
if (projectsState.selectedProjectId === null) {
content = <NewProject onAdd={handleAddProject} />;
} else if (projectsState.selectedProjectId === undefined) {
content = <NoProjectSelected onStartAddProject={handleStartAddProject} />;
}
return (
<main className="h-screen my-8 flex gap-8">
<ProjectSidebar
onStartAddProject={handleStartAddProject}
projects={projectsState.projects} // sidebar에 projectsState.projects 정보를 전달.
/>
{content}
</main>
);
}
export default App;
import Button from "./Button";
export default function ProjectSidebar({ onStartAddProject, projects }) {
// projects 상수를 통해 프로젝트 정보를 받아온다.
return (
<aside className="w-1/3 px-8 py-16 bg-stone-900 text-stone-50 md:w-72 rounded-r-xl">
<h2 className="mb-8 font-bold uppercase md:text-xl text-stone-200">
Your Projects
</h2>
<div>
<Button onClick={onStartAddProject}>+ Add Project</Button>
</div>
<ul className="mt-8">
{/* map을 이용해서 프로젝트 하나씩 타이틀을 버튼을 출력할 수 있도록 함.*/}
{projects.map((project) => (
<li key={project.id}>
<button className="w-full text-left px-2 py-1 rounded-sm my-1 text-stone-400 hover:text-stone-200 hover:bg-stone-800">
{project.title}
</button>
</li>
))}
</ul>
</aside>
);
}
useImperativeHandle로 에러 모달 띄우기.import { useRef, forwardRef, useImperativeHandle } from "react";
import { createPortal } from "react-dom";
import Button from "./Button.jsx";
const Modal = forwardRef(function Modal({ children, buttonCaption }, ref) {
const dialog = useRef();
// 외부에서 오는 참조 ref가 useImperativeHandle에 전달.
useImperativeHandle(ref, () => {
return {
open() {
dialog.current.showModal();
},
};
});
return createPortal(
<dialog
ref={dialog}
className="backdrop:bg-stone-900/90 p-4 roudned-md shadow-md"
>
{children}
<form method="dialog" className="mt-4 text-right">
<Button>{buttonCaption}</Button>
</form>
</dialog>,
document.getElementById("modal-root")
);
});
export default Modal;
import { useRef } from "react";
import Input from "./Input.jsx";
import Modal from "./Modal.jsx";
export default function NewProject({ onAdd }) {
const modal = useRef();
function handleSave() {
...
// validation
if (
enteredTitle.trim() === "" ||
enteredDescription.trim() === "" ||
enteredDueDate.trim() === ""
) {
//show the error modal
modal.current.open();
return; // 유효하지 않은 입력값을 받은 경우이기 때문에 return;
}
...
}
return (
<>
<Modal ref={modal} buttonCaption="Okay">
<h2 className="text-xl font-bold text-stone-700 my-4">Invalid Input</h2>
<p className="text-stone-600 mb-4">
Opps... looks like you forgot to enter a value
</p>
<p className="text-stone-600 mb-4">
Plz make sure you provide a valid value for every input field.
</p>
</Modal>
...
</>
);
}

// App.jsx
function App() {
function handleCancleAddProject() {
setProjectsState((prevState) => {
return {
...prevState,
selectedProjectId: undefined, // 취소한다..!
};
});
}
let content;
if (projectsState.selectedProjectId === null) {
content = (
<NewProject onAdd={handleAddProject} onCancle={handleCancleAddProject} />
);
} else if (projectsState.selectedProjectId === undefined) {
content = <NoProjectSelected onStartAddProject={handleStartAddProject} />;
}
}
// NewProject.jsx --> onCancle 연결
export default function NewProject({ onAdd, onCancle }) {
return (
<div className="w-[35rem] mt-16">
<menu className="flex items-center justify-end gap-4 my-4">
<li>
<button
className="text-stone-800 hover:text-stone-950"
onClick={onCancle}
>
Cancle
</button>
</li>
</menu>
</div>
);
}
export default function SelectedProject({ project }) {
const formattedDate = new Date(project.dueDate).toLocaleDateString("ko-KR", {
year: "numeric",
month: "short",
day: "numeric",
});
return (
<div className="w-[35rem] mt-16">
<header className="pb-4 mb-4 border-b-2 border-stone-300">
<div className="flex items-center justify-between">
<h1 className="text-3xl font-bold text-stone-600 mb-2">
{project.title}
</h1>
<button className="text-stone-600 hover:text-stone-950">
Delete
</button>
</div>
<p className="mb-4 text-stone-400">{formattedDate}</p>
<p className="text-stone-600 whitespace-pre-wrap">
{project.description}
</p>
{/* whitespace-pre-wrap: 상세 내용란에 줄 바꿈이 제거되지 않고 유지될 수 있게 함. */}
</header>
TASKS
</div>
);
}
import { useState } from "react";
import ProjectSidebar from "./components/ProjectSidebar";
import NewProject from "./components/NewProject";
import NoProjectSelected from "./components/NoProjectSelected";
import SelectedProject from "./components/SelectedProject"; // 추가
function App() {
const [projectsState, setProjectsState] = useState({
selectedProjectId: undefined,
projects: [],
});
//========================= 추가 =========================
function handleSelectProject(id) {
setProjectsState((prevState) => {
return {
...prevState,
selectedProjectId: id,
};
});
}
//=======================================================
function handleStartAddProject() {
setProjectsState((prevState) => {
return {
...prevState,
selectedProjectId: null,
};
});
}
function handleCancleAddProject() {
setProjectsState((prevState) => {
return {
...prevState,
selectedProjectId: undefined,
};
});
}
function handleAddProject(projectData) {
setProjectsState((prevState) => {
const projectId = Math.random();
const newProject = {
...projectData,
id: projectId,
};
return {
...prevState,
selectedProjectId: undefined,
projects: [...prevState.projects, newProject],
};
});
}
//========================= 추가 =========================
const selectedProject = projectsState.projects.find(
(project) => project.id === projectsState.selectedProjectId
);
let content = <SelectedProject project={selectedProject} />;
//=======================================================
if (projectsState.selectedProjectId === null) {
content = (
<NewProject onAdd={handleAddProject} onCancle={handleCancleAddProject} />
);
} else if (projectsState.selectedProjectId === undefined) {
content = <NoProjectSelected onStartAddProject={handleStartAddProject} />;
}
return (
<main className="h-screen my-8 flex gap-8">
<ProjectSidebar
onStartAddProject={handleStartAddProject}
projects={projectsState.projects}
onSelectProject={handleSelectProject} // 추가
/>
{content}
</main>
);
}
export default App;
import Button from "./Button";
export default function ProjectSidebar({
onStartAddProject,
projects,
onSelectProject,
selectedProjectId,
}) {
return (
<aside className="w-1/3 px-8 py-16 bg-stone-900 text-stone-50 md:w-72 rounded-r-xl">
<h2 className="mb-8 font-bold uppercase md:text-xl text-stone-200">
Your Projects
</h2>
<div>
<Button onClick={onStartAddProject}>+ Add Project</Button>
</div>
<ul className="mt-8">
{projects.map((project) => {
let cssClasses =
"w-full text-left px-2 py-1 rounded-sm my-1 hover:text-stone-200 hover:bg-stone-800";
if (project.id === selectedProjectId) {
cssClasses += " bg-stone-800 text-stone-200";
} else {
cssClasses += " text-stone-400";
}
return (
<li key={project.id}>
<button
onClick={() => onSelectProject(project.id)}
className={cssClasses}
>
{project.title}
</button>
</li>
);
})}
</ul>
</aside>
);
}
handleSelectProject 함수가 onSelectProject 속성을 통해 ProjectSidebar.jsx에 전달된다. ✚ 참고: App.jsx에서 프로젝트에 대한 정보도 함께 전달된다.(projectsState.projects)()=>onSelectProject(project.id) 전달하도록 되어있다.handleSelectProject함수가 실행이 되고 해당 함수에 사이드바에서 클릭한 프로젝트의 고유 아이디가 매개변수도 들어가게 된다.selectedProjectId 속성을 전달받은 id로 업데이트한다.selectedProjectId와 일치하는 project.id가 있는지 projectsState에서 한 프로젝트씩 뽑아 검사한다.projectsState {
selectProjectId : undefined / null / 혹은 id
projects : [
{
title:
description:
dueDate:
id
},
{...},{...}
]
}
function App() {
const [projectsState, setProjectsState] = useState({
selectedProjectId: undefined,
projects: [],
});
function handleSelectProject(id) {
setProjectsState((prevState) => {
console.log("prevState=>", prevState);
return {
...prevState,
selectedProjectId: id,
};
});
}
function handleDeleteProject() {
setProjectsState((prevState) => {
console.log("prevState=>", prevState);
return {
...prevState,
selectedProjectId: undefined,
projects: prevState.projects.filter(
(project) => project.id !== prevState.selectedProjectId // id가 동일하다면 지운다!
// prevState에 selectedProjectId가 있기 때문에 따로 id를 입력받지 않아도 된다.
),
};
});
}
const selectedProject = projectsState.projects.find(
(project) => project.id === projectsState.selectedProjectId
);
let content = (
<SelectedProject project={selectedProject} onDelete={handleDeleteProject} /> // onDelete 속성을 통해 함수 전달.
);
// ...
}
export default App;
export default function SelectedProject({ project, onDelete }) {
//...
return (
<div className="w-[35rem] mt-16">
<header className="pb-4 mb-4 border-b-2 border-stone-300">
<div className="flex items-center justify-between">
{/*...*/}
<button
className="text-stone-600 hover:text-stone-950"
onClick={onDelete} {/* onDelete 속성 전달 */}
>
Delete
</button>
</div>
{/*...*/}
</header>
TASKS
</div>
);
}
import NewTask from "./NewTask";
export default function Task() {
return (
<section>
<h2 className="text-2xl font-bold text-stone-700 mb-4">Tasks</h2>
<NewTask />
<p className="text-stone-800 my-4">
This project does not have any tasks yet.
</p>
<ul></ul>
</section>
);
}
export default function NewTask() {
return (
<div className="flex items-center gap-4">
<input type="text" className="w-64 px-2 py-1 rounded-sm bg-stone-200" />
<button className="text-stone-700 hover:text-stone-950">Add Task</button>
</div>
);
}
function App() {
const [projectsState, setProjectsState] = useState({
selectedProjectId: undefined,
projects: [],
tasks: [], // tasks 추가
});
function handleAddTask(text) {
setProjectsState((prevState) => {
const taskId = Math.random();
const newTask = {
text: text,
projectId: prevState.selectedProjectId,
id: taskId,
};
return {
...prevState,
tasks: [...prevState.tasks, newTask],
};
});
}
function handleDeleteTask() {}
let content = (
<SelectedProject
project={selectedProject}
onDelete={handleDeleteProject}
onAddTask={handleAddTask} {/* 태스크 추가 함수 추가 */}
onDeleteTask={handleDeleteTask} {/* 태스크 제거 함수 추가 */}
tasks={projectsState.tasks} {/* 태스크 배열 전달 */}
/>
);
}
export default App;
import Task from "./Task";
export default function SelectedProject({
project,
onDelete,
onAddTask, // App에서 받은 속성
onDeleteTask, // App에서 받은 속성
tasks, // App에서 받은 속성
}) {
return (
//...
<Task onAdd={onAddTask} onDelete={onDeleteTask} tasks={tasks} />
//...
);
}
import NewTask from "./NewTask";
export default function Task({ tasks, onAdd, onDelete }) { // App -> SelectedProject에서 받은 속성
return (
<section>
<h2 className="text-2xl font-bold text-stone-700 mb-4">Tasks</h2>
<NewTask onAdd={onAdd} /> {/* 속성 전달 */}
{tasks.length === 0 && ( {/* tasks 배열이 비어있을 때 */}
<p className="text-stone-800 my-4">
This project does not have any tasks yet.
</p>
)}
{tasks.length > 0 && ( {/* tasks 배열에 값이 있을 때 */}
<ul className="p-4 mt-8 rounded-md bg-stone-100">
{tasks.map((task) => (
<li key={task.id} className="flex justify-between my-4">
<span>{task.text}</span>
<button className="text-stone-700 hover:text-red-500">
Clear
</button>
</li>
))}
</ul>
)}
</section>
);
}
import { useState } from "react";
export default function NewTask({ onAdd }) {
// App -> SelectedProject -> Task에서 온 속성
const [enteredTask, setEnteredTask] = useState();
function handleChange(e) {
setEnteredTask(e.target.value);
}
function handleClick() {
onAdd(enteredTask); // 속성 꽂기.(props drilling : App -> SelectedProject -> Task -> NewTask)
setEnteredTask(""); // 입력칸을 비도록 만듦.
}
return (
<div className="flex items-center gap-4">
<input
onChange={handleChange}
value={enteredTask}
type="text"
className="w-64 px-2 py-1 rounded-sm bg-stone-200"
/>
<button
onClick={handleClick}
className="text-stone-700 hover:text-stone-950"
>
Add Task
</button>
</div>
);
}
function App() {
//...
function handleDeleteTask(id) {
setProjectsState((prevState) => {
return {
...prevState,
tasks: prevState.tasks.filter((task) => task.id !== id),
};
});
}
//...
}
export default function Task({ tasks, onAdd, onDelete }) {
return (
<section>
{/* ... */}
{tasks.length > 0 && (
<ul className="p-4 mt-8 rounded-md bg-stone-100">
{tasks.map((task) => (
<li key={task.id} className="flex justify-between my-4">
<span>{task.text}</span>
<button
onClick={() => onDelete(task.id)} {/* id 전달 */}
className="text-stone-700 hover:text-red-500"
>
Clear
</button>
</li>
))}
</ul>
)}
</section>
);
}

const [enteredTask, setEnteredTask] = useState();로 상태를 정의하고 있다. 처음에는 상태를 아무것도 설정해두지 않았는데, 사용자가 입력을 하자 문자열로 업데이트가 된다.const [enteredTask, setEnteredTask] = useState('');로 하여 초기 설정을 빈 문자열로 둔다.
undefined, null, id 세 가지의 신호를 통해서 프로젝트를 선택하지 않았을 때(혹은 아무것도 안한 상태) - 프로젝트를 생성했을 때 - 프로젝트를 선택했을 때의 세 가지 상태를 구분했다. → 상태(state)를 더 쉽게 사용할 수 있게 된 원인 중 하나이지 않았을까 생각된다.