개발/Project(React-다이어리)

React - 리스트 렌더링 / 데이터 추가, 삭제, 수정 / useEffect (useEffect... 내용 저장 안함... )

잇(IT) 2023. 9. 19. 16:46
728x90

https://insoobaik.tistory.com/505

 

React - 사용자 입력 처리 / DOM 조작(useRef) / API 호출

* 아래 코드를 확인하기 전 알아야 할 것이 있다. React는 같은 레벨에서 데이터를 주고 받을 수 없기 때문에 부모 컴포넌트로 데이터를 전달하고, 부모 데이터로부터 데이터를 전달 받아야 한다.

insoobaik.tistory.com

- 앞서서 사용자 입력, DOM 조작(useRef), API 호출에 대해 알아보았다.


- 리스트 렌더링 / 데이터 추가, 삭제, 수정

- 이번에는 받아온 데이터 리스트를 렌더링하고, 데이터 추가, 삭제, 수정 하는 것에 대해 알아 볼 것이다.


- App.js

......

return (
    <div className="App">
      <Lifecycle />
      <DiaryEditor onCreate={onCreate} />
      <DiaryList onEdit={onEdit} onRemove={onRemove} diaryList={data} />
    </div>
  );
}

- DiaryList 컴포넌트로 onEdit, onRemove, data 3개의 prop을 전달하는 것을 볼 수 있다.

 

.......

const onRemove = (targetId) => {
    console.log(`${targetId}가 삭제되었습니다.`);
    const newDiaryList = data.filter((it) => it.id !== targetId);
    setData(newDiaryList);
  };

  const onEdit = (targetId, newContent) => {
    setData(
      data.map((it) =>
        it.id === targetId ? { ...it, content: newContent } : it
      )
    );
  };
  
  .......

- onRemove, onEdit 컴포넌트는 위와 같다. 

- 또 useState를 통해 변경되는 data가 diaryList라는 변수로 전달된다.


- DiaryList.js

import DiaryItem from "./DiaryItem";

const DiaryList = ({ onEdit, onRemove, diaryList }) => {
  return (
    <div className="DiaryList">
      <h2>일기 리스트</h2>
      <h4>{diaryList.length}개의 일기가 있습니다.</h4>
      <div>
        {diaryList.map((it) => (
          <DiaryItem key={it.id} {...it} onEdit={onEdit} onRemove={onRemove} />
        ))}
      </div>
    </div>
  );
};

DiaryList.defaultProps = {
  diaryList: [],
};
export default DiaryList;

- DiaryList는 prop으로 전달된 3개를 받고, onEdit, onRemove 컴포넌트의 경우 DirayItem 컴포넌트로 전달한다.

- data의 값을 가지고 있는 diaryList는 map을 통해 diaryList의 객체를 순회하며, DiaryItem 컴포넌트를 순회하며 생성한다.

- map을 통해 순회를 하며 컴포넌트를 전달하기 때문에, 변경사항이 컴포넌트 간 영향을 끼치면 안되기 때문에 각 컴포트에 대한 key 값이 필요하기 때문에 위와 같이 map으로 전달하는 컴포넌트에 key prop을 전달하는 것이다. 

 

- 즉, DiaryList는 DiaryItem 컴포넌트를 통해 각 데이터들을 화면에 렌더링 하는 코드 페이지이다.


- DiaryItem.js

import { useRef, useState } from "react";

const DiaryItem = ({
  onEdit,
  onRemove,
  author,
  content,
  created_date,
  emotion,
  id,
}) => {
  const [isEdit, setIsEdit] = useState(false);
  const toggleIsEdit = () => setIsEdit(!isEdit);
  const [localContent, setLocalContent] = useState(content);
  const localContentInput = useRef();

  const handleRemove = () => {
    if (window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) {
      onRemove(id);
    }
  };

  const handleQuitEdit = () => {
    setIsEdit(false);
    setLocalContent(content);
  };

  const handleEdit = () => {
    if (localContent.length < 5) {
      localContentInput.current.focus();
      return;
    }

    if (window.confirm(`${id}번 째 일기를 수정하시겠습니까?`)) {
      onEdit(id, localContent);
      toggleIsEdit();
    }
  };

  return (
    <div className="DiaryItem">
      <div className="info">
        <span>
          작성자 : {author} | 감정점수 : {emotion}
        </span>
        <br />
        <span className="date">{new Date(created_date).toLocaleString()}</span>
      </div>
      <div className="content">
        {isEdit ? (
          <>
            <textarea
              ref={localContentInput}
              value={localContent}
              onChange={(e) => setLocalContent(e.target.value)}
            />
          </>
        ) : (
          <>{content}</>
        )}
      </div>
      {isEdit ? (
        <>
          <button onClick={handleQuitEdit}>수정 취소</button>
          <button onClick={handleEdit}>수정 완료</button>
        </>
      ) : (
        <>
          <button onClick={handleRemove}>삭제하기</button>
          <button onClick={toggleIsEdit}>수정하기</button>
        </>
      )}
    </div>
  );
};

export default DiaryItem;

- onEdit, onRemove의 경우 특정 동작을 수행하는 컴포넌트들이 넘어온 것이고 실직적으로 데이터가 담겨져 넘어온 것은 diaryList에 포함되어 있는 author, content, created_date, id 값들이다.

 

const [isEdit, setIsEdit] = useState(false);
  const toggleIsEdit = () => setIsEdit(!isEdit);
  const [localContent, setLocalContent] = useState(content);
  const localContentInput = useRef();

- isEdit : 수정 상태인지 아닌지 판별하기 위한 useState이다.

- toggleIsEdit : 수정 상태를 변경하기 위한 함수이다.

- localContent : 수정 상태에서 변경할 내용을 담을 useState이다.


 return (
    <div className="DiaryItem">
      <div className="info">
        <span>
          작성자 : {author} | 감정점수 : {emotion}
        </span>
        <br />
        <span className="date">{new Date(created_date).toLocaleString()}</span>
      </div>
      <div className="content">
        {isEdit ? (
          <>
            <textarea
              ref={localContentInput}
              value={localContent}
              onChange={(e) => setLocalContent(e.target.value)}
            />
          </>
        ) : (
          <>{content}</>
        )}
      </div>
      {isEdit ? (
        <>
          <button onClick={handleQuitEdit}>수정 취소</button>
          <button onClick={handleEdit}>수정 완료</button>
        </>
      ) : (
        <>
          <button onClick={handleRemove}>삭제하기</button>
          <button onClick={toggleIsEdit}>수정하기</button>
        </>
      )}
    </div>
  );
};

export default DiaryItem;

- DiaryItem.js의 전체 코드이며 아래 코드에 대한 설명을 덧붙이겠다.

 

<div className="content">
        {isEdit ? (
          <>
            <textarea
              ref={localContentInput}
              value={localContent}
              onChange={(e) => setLocalContent(e.target.value)}
            />
          </>
        ) : (
          <>{content}</>
        )}
      </div>

- className="content"의 경우 isEdit 즉, 수정 상태에 따라 다른 화면을 렌더링한다.

1. isEdit이 true인 경우, <textarea>를 사용하여 사용자가 내용을 입력하고 이벤트가 발생하는 화면이 나오도록 렌더링 한다. 변경된 값은 localContent / useState에 의해 내용이 변경된다.

2. isEdit의 기본 값은 false이며, false인 경우 원래 전달 받은 content 데이터를 화면에 렌더링 하여 보여준다.

 

{isEdit ? (
        <>
          <button onClick={handleQuitEdit}>수정 취소</button>
          <button onClick={handleEdit}>수정 완료</button>
        </>
      ) : (
        <>
          <button onClick={handleRemove}>삭제하기</button>
          <button onClick={toggleIsEdit}>수정하기</button>
        </>
      )}

- 위 코드는 isEdit이 true인 경우 즉, 수정 상태인 경우 수정 취소, 수정 완료 버튼이 보이도록, isEdit이 false인 경우 즉, 수정 상태가 아닌 경우 삭제하기, 수정하기 버튼이 보이도록 하는 코드이다.

 

const handleRemove = () => {
    if (window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) {
      onRemove(id);
    }
  };

  const handleQuitEdit = () => {
    setIsEdit(false);
    setLocalContent(content);
  };

  const handleEdit = () => {
    if (localContent.length < 5) {
      localContentInput.current.focus();
      return;
    }

    if (window.confirm(`${id}번 째 일기를 수정하시겠습니까?`)) {
      onEdit(id, localContent);
      toggleIsEdit();
    }
  };

- 먼저 삭제하기 버튼을 누른 경우 handleRemove 함수가 실행되고,

const onRemove = (targetId) => {
    console.log(`${targetId}가 삭제되었습니다.`);
    const newDiaryList = data.filter((it) => it.id !== targetId);
    setData(newDiaryList);
  };

App.js에서 작성한 onRemove에 해당 id가 파라미터로 전달되고, filter() 함수를 통해 전달 받은 id와 일치하지 않은 값들을 불러오고 setData를 통해 새로운 state를 전달하고 리렌더링 한다.

 

- 그 다음 수정하기 버튼을 누르게 되면, toggleIsEdit() 함수가 실행되어 isEdit의 state를 반대로 변경한다. 즉, 최초의 isEdit의 값은 false이기 때문에 true로 바꾸는 것이다.

 

- 수정 화면에서 수정 취소 버튼을 누르게 되면, handleQuitEdit 함수가 실행되고,

const handleQuitEdit = () => {
    setIsEdit(false);
    setLocalContent(content);
  };

isEdit의 상태가 다시 false 즉, 수정이 아닌 상태로 변경되며, 수정 변경 내용에 해당하는 localContent의 파라미터로 최초에 전달받은 content 값을 넣음으로서, 내용을 수정했더라도 취소 버튼을 누르게 되면 최초의 content 내용이 보이도록 코드를 작성하였다.

 

- 다음으로 내용을 수정하고, 수정 완료 버튼을 누르게 되면, handleEdit 함수가 실행되고,

const handleEdit = () => {
    if (localContent.length < 5) {
      localContentInput.current.focus();
      return;
    }
    if (window.confirm(`${id}번 째 일기를 수정하시겠습니까?`)) {
      onEdit(id, localContent);
      toggleIsEdit();
    }

- 해당 함수는 내용의 길이에 대한 조건이 붙어 있으며, 해당 조건을 충족 시키지 못하면 useRef()를 통해 생성된 변수를 통해 focus()가 잡히게 된다.

- 해당 조건을 충족 시키게 되면 수정을 할 것인지에 대한 팝업 창을 한 번 보여준 뒤 확인 버튼을 누르게 되면, App.js에서 작성된 onEdit에 id, localContent 파라미터를 넘긴다.

const onEdit = (targetId, newContent) => {
    setData(
      data.map((it) =>
        it.id === targetId ? { ...it, content: newContent } : it
      )
    );
  };

onEdit 함수는 전달 받은 파라미터를 통해, 해당 id와 일치하는 객체 속성을 찾아, spread를 통해 나머지 속성은 그대로 받아오고, content의 속성만 전달받은 newContent로 변경한다. 또, id가 일치하지 않은 객체는 그대로 반환한다.

- 마지막으로 내용 수정을 변경하고 난 뒤 toggleIsEdit() 함수를 실행시켜 수정 상태에서 벗어나도록 한다.

 

728x90