src/App.js
import React, { useState } from "react";
import TodoTemplate from "./components/TodoTemplate";
import InputSection from "./components/InputSection";
import TodoSection from "./components/TodoSection";
import CompleteSection from "./components/CompleteSection";
import { createGlobalStyle } from "styled-components";
const GlobalStyle = createGlobalStyle`
body {
background: #2d518a;
opacity: 0.9;
}
`;
const inputArray = ["title", "subTitle", "desc"];
function App() {
const [todos, setTodos] = useState([]);
const [id, setId] = useState("");
// Text Value State
const [inputValue, setInputValue] = useState({
title: "",
subTitle: "",
desc: "",
isDone: false,
});
const [editValue, setEditValue] = useState({
title: "",
subTitle: "",
desc: "",
isDone: false,
});
// Filtered Array(Complete)
const noCompleteArray = todos.filter((todo) => todo?.isDone === false);
const completeArray = todos.filter((todo) => todo?.isDone === true);
// onChange Function
const handleInputValue = (event) => {
const { value, name } = event?.target;
setInputValue((prev) => {
return { ...prev, [name]: value };
});
};
const handleEditValue = (event) => {
const { value, name } = event?.target;
setEditValue((prev) => {
return { ...prev, [name]: value };
});
};
// handleButton Function
const handleAddClick = () => {
if (!inputValue?.title && !inputValue?.subTitle && !inputValue?.desc) {
alert("Please enter at least one item!");
return;
}
setTodos((prev) => {
return [
...prev,
{
...inputValue,
id: `${inputValue?.title} ${inputValue?.subTitle} ${inputValue?.desc}`,
},
];
});
setInputValue({ title: "", subTitle: "", desc: "", isDone: false });
};
const handleDeleteClick = (id) => {
setTodos((prev) => {
return prev?.filter((todo) => todo?.id !== id);
});
};
const handleCompleteClick = (id) => {
let newArray = todos?.map((todo) =>
todo?.id === id ? { ...todo, isDone: true } : todo
);
setTodos(newArray);
};
const handleEditClick = (id) => {
setId(id);
const editTodo = todos.find((todo) => todo?.id === id);
setEditValue(editTodo);
};
const handleSaveClick = (id) => {
setTodos((prev) => {
return [...prev]?.map((todo) => (todo?.id === id ? editValue : todo));
});
setId("");
setEditValue({
title: "",
subTitle: "",
desc: "",
isDone: false,
});
};
const handleCancelClick = (id) => {
setId("");
setEditValue({
title: "",
subTitle: "",
desc: "",
isDone: false,
});
};
// console.log({ inputValue });
// console.log({ todos });
return (
<>
<GlobalStyle />
<TodoTemplate>
<InputSection
inputArray={inputArray}
inputValue={inputValue}
handleInputValue={handleInputValue}
handleAddClick={handleAddClick}
/>
<TodoSection
editId={id}
noCompleteArray={noCompleteArray}
inputArray={inputArray}
editValue={editValue}
handleEditClick={handleEditClick}
handleCompleteClick={handleCompleteClick}
handleEditValue={handleEditValue}
handleSaveClick={handleSaveClick}
handleCancelClick={handleCancelClick}
handleDeleteClick={handleDeleteClick}
/>
<CompleteSection
completeArray={completeArray}
handleDeleteClick={handleDeleteClick}
/>
</TodoTemplate>
</>
);
}
export default App;
src/components/InputSection.js
import React from "react";
import styled from "styled-components";
const InputTitle = styled.h1`
text-align: center;
`;
const InputFieldWrap = styled.div`
display: flex;
justify-content: center;
margin-bottom: 10px;
`;
const InputField = styled.input`
border: 2px solid #85afee;
border-radius: 7px;
padding: 5px;
width: 250px;
cursor: pointer;
`;
const InputAddButtonWrap = styled.div`
display: flex;
justify-content: center;
`;
const InputAddButton = styled.button`
background-color: #85afee;
color: white;
font-weight: bolder;
border: none;
border-radius: 10px;
width: 150px;
padding: 10px;
&:hover {
background-color: navy;
color: white;
}
cursor: pointer;
`;
const InputSection = ({
inputArray,
inputValue,
handleInputValue,
handleAddClick,
}) => {
return (
<>
<InputTitle>Todo List 📝</InputTitle>
{/* Input & Add Button Field */}
<div>
{inputArray?.map((name, idx) => {
return (
<InputFieldWrap key={idx}>
<InputField
name={name}
value={inputValue?.[name]}
onChange={handleInputValue}
placeholder={`Add your Todo '${name}'`}
/>
</InputFieldWrap>
);
})}
<InputAddButtonWrap>
<InputAddButton onClick={handleAddClick}>
Add your Passion!
</InputAddButton>
</InputAddButtonWrap>
</div>
</>
);
};
export default InputSection;
src/components/PrevEditForm.js
import React from "react";
import styled from "styled-components";
const PrevEditWrap = styled.div`
border: 2px solid #85afee;
border-radius: 7px;
margin: 20px;
padding: 16px;
display: flex;
flex-direction: column;
align-items: center;
background-color: white;
`;
const PrevEditText = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
margin-bottom: 10px;
p {
margin: 0;
font-size: 18px;
font-weight: bolder;
color: black;
}
`;
const PrevEditButtonWrap = styled.div`
display: flex;
justify-content: center;
column-gap: 5px;
`;
const PrevEditButton = styled.button`
background-color: #85afee;
color: white;
font-weight: bolder;
border: none;
border-radius: 10px;
width: 150px;
padding: 10px;
&:hover {
background-color: navy;
color: white;
}
cursor: pointer;
`;
const PrevEditForm = ({
todo,
handleEditClick,
handleCompleteClick,
handleDeleteClick,
}) => {
return (
<PrevEditWrap>
<PrevEditText>
<p>{todo?.title}</p>
<p>{todo?.subTitle}</p>
<p>{todo?.desc}</p>
</PrevEditText>
<PrevEditButtonWrap>
<PrevEditButton onClick={() => handleEditClick(todo?.id)}>
Edit
</PrevEditButton>
<PrevEditButton onClick={() => handleCompleteClick(todo?.id)}>
Complete
</PrevEditButton>
<PrevEditButton onClick={() => handleDeleteClick(todo?.id)}>
Delete
</PrevEditButton>
</PrevEditButtonWrap>
</PrevEditWrap>
);
};
export default PrevEditForm;
기존에 TodoSection.js 파일에서 todo.id와 edit.id가 다른 경우 반환하던 코드였다. TodoSection.js 파일의 코드가 너무 길어져서 별도의 component 파일로 분리했다.
src/components/EditForm.js
import React from "react";
import styled from "styled-components";
const EditWrap = styled.div`
border: 2px solid #85afee;
border-radius: 7px;
margin: 20px;
padding: 16px;
display: flex;
flex-direction: column;
align-items: center;
background-color: white;
`;
const EditInputFieldWrap = styled.div`
display: flex;
justify-content: center;
margin-bottom: 10px;
`;
const EditInputField = styled.input`
border: 2px solid #85afee;
border-radius: 7px;
padding: 5px;
width: 250px;
cursor: pointer;
`;
const EditButtonWrap = styled.div`
display: flex;
justify-content: center;
column-gap: 5px;
`;
const EditButton = styled.button`
background-color: #85afee;
color: white;
font-weight: bolder;
border: none;
border-radius: 10px;
width: 150px;
padding: 10px;
&:hover {
background-color: navy;
color: white;
}
cursor: pointer;
`;
const EditForm = ({
inputArray,
editValue,
handleEditValue,
handleSaveClick,
handleCancelClick,
todo,
}) => {
return (
<EditWrap>
{inputArray?.map((name, idx) => {
return (
<EditInputFieldWrap key={idx}>
<EditInputField
name={name}
value={editValue?.[name]}
onChange={handleEditValue}
placeholder={`Edit your Todo '${name}'`}
/>
</EditInputFieldWrap>
);
})}
<EditButtonWrap>
<EditButton onClick={() => handleSaveClick(todo?.id)}>Save</EditButton>
<EditButton onClick={() => handleCancelClick(todo?.id)}>
Cancel
</EditButton>
</EditButtonWrap>
</EditWrap>
);
};
export default EditForm;
해당 코드는 TodoSection.js 파일에서 todo.id와 edit.id가 같은 경우 반환하던 코드였다. TodoSection.js 파일의 코드가 너무 길어져서 별도의 component 파일로 분리했다.
src/components/TodoSection.js
import React from "react";
import styled from "styled-components";
import EditForm from "./EditForm";
import PrevEditForm from "./PrevEditForm";
const TodoTitle = styled.h2`
text-align: center;
`;
const TodoSection = ({
noCompleteArray,
handleEditClick,
handleCompleteClick,
inputArray,
editValue,
handleEditValue,
handleSaveClick,
editId,
handleCancelClick,
handleDeleteClick,
}) => {
const sectionTitle = noCompleteArray.length > 0 ? `Todo 👨💻` : "";
return (
<div>
{sectionTitle && <TodoTitle>{sectionTitle}</TodoTitle>}
{/* Todo & {(Edit(save, cancel), Complete, Delete) Button Field} */}
{noCompleteArray.map((todo) => {
return (
<div key={todo?.id}>
{todo?.id === editId ? (
<EditForm
inputArray={inputArray}
editValue={editValue}
handleEditValue={handleEditValue}
handleSaveClick={handleSaveClick}
handleCancelClick={handleCancelClick}
todo={todo}
/>
) : (
<PrevEditForm
todo={todo}
handleEditClick={handleEditClick}
handleCompleteClick={handleCompleteClick}
handleDeleteClick={handleDeleteClick}
/>
)}
</div>
);
})}
</div>
);
};
export default TodoSection;
EditForm과 PrevEditForm을 컴포넌트로 분리함으로써, 전체 코드의 길이가 확연히 줄었다. 더 직관적으로 코드를 파악할 수 있게 되었다. 추가적으로 contents가 없는 경우에는 제목이 나타나지 않도록 예외처리를 해주었다.
src/components/CompleteSection.js
import React from "react";
import styled from "styled-components";
const CompleteTitle = styled.h2`
text-align: center;
`;
const CompleteArea = styled.div`
max-height: 400px;
margin-bottom: 270px;
`;
const CompleteWrap = styled.div`
border: 2px solid #85afee;
border-radius: 7px;
margin: 20px;
padding: 16px;
display: flex;
flex-direction: column;
align-items: center;
background-color: white;
`;
const CompleteText = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
margin-bottom: 10px;
p {
margin: 0;
font-size: 18px;
font-weight: bolder;
color: black;
}
`;
const DeleteButton = styled.button`
background-color: #85afee;
color: white;
font-weight: bolder;
border: none;
border-radius: 10px;
width: 150px;
padding: 10px;
&:hover {
background-color: navy;
color: white;
}
cursor: pointer;
`;
const CompleteSection = ({ completeArray, handleDeleteClick }) => {
const sectionTitle = completeArray.length > 0 ? `Complete Todo 🔥` : "";
return (
<CompleteArea>
{sectionTitle && <CompleteTitle>{sectionTitle}</CompleteTitle>}
{/* Complete Todo & Delete Button Field */}
{completeArray.map((todo) => {
return (
<CompleteWrap key={todo?.id}>
<CompleteText>
<p>{todo?.title}</p>
<p>{todo?.subTitle}</p>
<p>{todo?.desc}</p>
</CompleteText>
<DeleteButton onClick={() => handleDeleteClick(todo?.id)}>
Delete
</DeleteButton>
</CompleteWrap>
);
})}
</CompleteArea>
);
};
export default CompleteSection;
TodoSection과 동일한 방식으로 제목에 대한 예외처리를 추가했다.
src/components/TodoTemplate.js
import React from "react";
import styled from "styled-components";
const TodoTemplateBlock = styled.div`
width: 512px;
height: 768px;
overflow-y: auto;
background: linear-gradient(to bottom, white, #dce6f5);
border-radius: 16px;
margin: 0 auto; /* 페이지 중앙에 나타나도록 설정 */
margin-top: 30px;
margin-bottom: 30px;
display: flex;
flex-direction: column;
`;
function TodoTemplate({ children }) {
return <TodoTemplateBlock>{children}</TodoTemplateBlock>;
}
export default TodoTemplate;