[TIL/React] 2023/04/27

원민관·2023년 4월 27일
0

[TIL]

목록 보기
63/159

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;
profile
Write a little every day, without hope, without despair ✍️

0개의 댓글