상세 컨텐츠

본문 제목

Todo List 만들기로 배우는 React(feat.Styled component in emotion) - 7

Study with me

by Agathe_1024 2022. 9. 7. 22:55

본문

✔️ 해당 글은 John Ahn님의 인프런강좌 "따라하며 배우는 리액트 A-Z"를 들으며 작성합니다.

 

이번에는 할 일 아이템을 수정, 저장하는 기능을 생성하겠습니다.

 

각 할 일 아이템의 삭제 버튼 옆에 EDIT버튼을 만들고,

그 버튼을 클릭하면 할 일 아이템의 ui를 바꿔서 글 수정이 가능하게하고,

EDIT버튼이 SAVE버튼이 되어 수정한 글을 저장할 수 있도록 만들고자합니다.

 

1️⃣ State 만들기

Item컴포넌트로 가서 State 두 개를 만듭니다.

하나는 글을 수정하는 State, 다른 하나는 수정이 완료된 글 State입니다.

    const [IsEditing, setIsEditing] = useState(false);
    const [editedTitle, setEditedTitle] = useState(title);

그리고 버튼ui도 만듭니다.

const BtnEdit = styled.button`
  color: #245572;
  background-color: transparent;
  border: none;
  padding: 6px 9px;
  box-sizing: border-box;
  cursor: pointer;
`;

...

<BtnEdit>EDIT</BtnEdit>

 

2️⃣ onClick 함수 만들기

setState를 통해 EDIT 버튼을 클릭하면 해당 할 일 아이템의 ui가 바뀌는 함수를 만듭니다.

isEditing 은 기본적으로 false인데, 버튼을 클릭하면 setIsEditing을 true로 상태를 변경해줍니다.

<BtnEdit onClick={() => setIsEditing(true)}>EDIT</BtnEdit>

그리고 if문을 사용하여,

만약 isEditing이 true라면 "editing..."을 반환하고,

false라면 원래의 할 일 아이템 목록을 반환하도록 만들고자 했더니

map()이 걸립니다(...)

 

기존 Item 컴포넌트에 만들어둔 map()을 상위 컴포넌트인 List로 옮깁니다.

const List = React.memo(({ todoData, setTodoData, handleClick }) => {
  return (
    <WrapList>
      {todoData.map((item) => (
        <Item
          todoData={todoData}
          setTodoData={setTodoData}
          handleClick={handleClick}
          id={item.id}
          title={item.title}
          completed={item.completed}
        />
      ))}
    </WrapList>
  );
});

export default List;

상위 컴포넌트인 List에서 보내지는 id, title, completed props들도 Item에 넣어줍니다.

const Item = React.memo(
  ({ todoData, setTodoData, handleClick, id, title, completed }) => {
    const getStyled = (completed) => {
      return {
        textDecoration: completed ? "line-through" : "none",
        color: completed ? "#89acbe" : "#0c1214",
      };
    };

    const handleCompletedChange = (id) => {
      let newTodoData = todoData.map((item) => {
        if (item.id === id) {
          item.completed = !item.completed;
        }
        return item;
      });
      setTodoData(newTodoData);
    };

    const [isEditing, setIsEditing] = useState(false);
    const [editied, setEditied] = useState(title);
    
     return (
        <WrapItem key={id}>
          <ItemLabel style={getStyled(completed)} key={id}>
            <Checkbox
              type="checkbox"
              defaultChecked={false}
              onChange={() => handleCompletedChange(id)}
            />
            {title}
          </ItemLabel>
          <BtnEdit onClick={() => setIsEditing(true)}>EDIT</BtnEdit>
          <BtnDelete onClick={() => handleClick(id)}>X</BtnDelete>
        </WrapItem>
      );
    }
  }
);

export default Item;

이 상태에서 if 문을 만들어봅니다.

    if (isEditing) {
      return <div>editing...</div>;
    } else {
      return (
         <WrapItem key={id}>
           <ItemLabel style={getStyled(completed)} key={id}>
             <Checkbox
              type="checkbox"
               defaultChecked={false}
               onChange={() => handleCompletedChange(id)}
             />
             {title}
           </ItemLabel>
           <BtnEdit onClick={() => setIsEditing(true)}>EDIT</BtnEdit>
           <BtnDelete onClick={() => handleClick(id)}>X</BtnDelete>
         </WrapItem>
      );
    }

🖥️ 결과물

네 다행히 EDIT버튼을 클릭한 해당 할 일 아이템의 모습만 바뀝니다.

✔️ 오늘의 교훈 : map() 돌릴 땐 상위 컴포넌트에서 돌리자(...)

 

3️⃣ 수정할 form - input 작성

Add 컴포넌트와 마찬가지로 submit되는 input을 위해 form으로 감싸주는 구조를 만듭니다.

이전에 작성했던 코드를 재사용하되, ItemLabel 상위에 form으로 감싸주고,

Checkbox의 type은 text로 변경 및 value와 타이핑 이벤트에 따라 변경될 것을 적용시켜줄 onChange함수도 적어줍니다.

    if (isEditing) {
      return (
        <WrapItem key={id}>
          <form>
            <ItemLabel>
              <Checkbox
                type="text"
                value={editedTitle}
                onChange={hadleEditChange}
              />
            </ItemLabel>
          </form>
          <BtnEdit type="submit">SAVE</BtnEdit>
          <BtnDelete onClick={() => setIsEditing(false)}>X</BtnDelete>
        </WrapItem>
      );
    } else ...

🖥️ 결과물

4️⃣ onChange함수 - handleEditChange 작성

Add 컴포넌트의 input과 비슷합니다.

  // 할 일 아이템 추가
  
  const [value, setValue] = useState("");
  
  
  const handleChange = (e) => {
    setValue(e.target.value);
  };

앞서 만든 useState를 사용합니다.

    const [isEditing, setIsEditing] = useState(false);
    ...
    const hadleEditChange = (e) => {
      setEditedTitle(e.target.value);
    };

🖥️ 결과물

수정이 됩니다.

5️⃣ 수정 사항을 저장하기

form 태그에 적용할 onSubmit 함수를 만듭니다.

이 때 저장을 위해 save 버튼을 클릭해야 onSubmit이 일어나므로

save버튼에도 onClick 함수를 만들되, 그 이름이 onSubmit함수와 동일합니다.

이 경우는 form 안에 버튼이 함께 있지 않아서 각 태그별로 따로 지정해주면서 동시에 동일한 함수를 공유합니다.

        <WrapItem key={id}>
          <form onSubmit={handleEditSubmit}>
            <ItemLabel>
              <Checkbox
                type="text"
                value={editedTitle}
                onChange={hadleEditChange}
              />
            </ItemLabel>
          </form>
          <BtnEdit type="submit" onClick={handleEditSubmit}>
            SAVE
          </BtnEdit>
          <BtnDelete onClick={() => setIsEditing(false)}>X</BtnDelete>
        </WrapItem>

handleEditSubmit 함수를 작성합니다.

newTodoData를 선언하고, todoData를 map() 돌리는데,

이벤트가 일어나는(타이핑하는) item.id 와 id가 동일하다면

item.title은 앞서 수정했던 input(Checkbox type="text")의 value인 editedTitle이 되도록 하고 리턴합니다.

setTodoData를 통해 newTodoData(item.title이 수정된 todoData로 저장)로 상태 변경해주고,

수정하는 input의 화면을 전환하기 위해 setIsEditing은 false로 바꿔줍니다.

    const handleEditSubmit = (e) => {
      e.preventDefault();
      let newTodoData = todoData.map((item) => {
        if (item.id === id) {
          item.title = editedTitle;
        }
        return item;
      });
      setTodoData(newTodoData);
      setIsEditing(false);
    };

🖥️ 결과물

수정사항이 잘 저장됩니다.

자잘한 스타일 수정도 합니당!

🧐 수정하는 부분이 많이 어렵네요 ㅠㅠ

아직 어떻게 풀어나가야 할 지 스스로 생각하는 힘이 부족한 것 같습니당..

 

관련글 더보기