React List 다루기

떡ol·2023년 8월 23일

단방향 흐름 (one-way flow)

react는 단방향 흐름을 가지고 있습니다. 이 말은 부모페이지가 자식에게 데이터를 보내주는게 가능하나 자식이 부모에게 전달하는 것은 불가능 합니다 예제를 확인해보죠

const dummyList = [
  {
    id: 1,
    author: "Bob",
    content: "Goood",
    emotion: 5,
    created_date: new Date().getTime()
  },
  {
    id: 2,
    author: "Bob",
    content: "Goood",
    emotion: 5,
    created_date: new Date().getTime()
  },
  {
    id: 3,
    author: "Bob",
    content: "Goood",
    emotion: 5,
    created_date: new Date().getTime()
  }
];

const App = () => {
  return (
    <div className="App">
      <DiaryEditor />
      <DiaryList diaryList={dummyList} /> //여기가 포인트
    </div>
  );
};
export default App;

조금 react를 찍먹해보시셨으면 느끼셨겠지만 diaryList={dummyList}로 App.js에서 컴포넌트는 보내지만 받는 방법은 없습니다. 이렇듯 재사용이 필요한 컴포넌트를 수정할 수 있게 하는 방법이 앞서 배웠던 useState를 이용하는 겁니다.

const App = () => {
  const [data, setData] = useState([]);
  
  const dataId = useRef(0);

  const onCreate = (author, content, emotion) => {
    const created_date = new Date().getTime();
    const newItem = {
      author,
      content,
      emotion,
      created_date,
      id: dataId.current
    };
    dataId.current += 1;
    setData([newItem, ...data]);
  };

  return (
    <div className="App">
      //DiaryEditor와 List는 App.js의 하단에 있는 레밸계층이 같습니다.
      //Editor에서는 onCreate함수를 넘겨주어 setData를 하게만듭니다.
      //setData에 의해 수정된 data 리스트는 DiaryList로 re-render 됩니다.
      <DiaryEditor onCreate={onCreate} />
      <DiaryList diaryList={data} />
    </div>
  );
};

List에 데이터 추가하기

자, 그럼 위에서 만든 소스에 DiaryEditor.DiaryList를 이어서 만들어봅니다.
이전 포스트의 내용에서 소스를 추가하시면 됩니다.

DiaryEditor

onCreate함수 프로퍼티만 넘겨받아 이를 사용하는, handler만 바꿔주면 되겠습니다.

const DiaryEditor = ({ onCreate }) => { // 이부분 prop 추가
  //생략...

const handleSubmit = () => { // handleSubmit를 수정하면 될거같습니다.
    if (state.author.length < 1) {
      authorInput.current.focus();
      return;
    }

    if (state.content.length < 5) {
      contentInput.current.focus();
      return;
    }

    onCreate(state.author, state.content, state.emotion); // 이부분 추가
    alert("저장 성공");
    setState({
      author: "",
      content: "",
      emotion: 1
    });
  };
  //생략...

그럼 위에 App.js에서 작성한 onCreate가 실행이 됩니다.

const App = () => {
  const [data, setData] = useState([]);
  //중략...
  const onCreate = (author, content, emotion) => {
    //중략...
    setData([newItem, ...data]);
    //중략...
  };
  //중략...
};

우리가 지정한 datasetData에 의해 새로운값을 앞에 배치하고, 뒤에 배열을 이어 붙히게 됩니다.

DiaryItem

DiaryItem.js는 App.js에서 diaryList받아옵니다. 이는 곧 data를 의미하죠

import DiaryItem from "./DiaryItem";

const DiaryList = ({ diaryList }) => {
  return (
    <div className="DiaryList">
      <h2>일기 리스트</h2>
      <h4>{diaryList.length}개의 일기가 있습니다.</h4>
      <div>
        {diaryList.map((it) => (
          <DiaryItem key={`diaryitem_${it.id}`} {...it} /> 
		  //map으로 list를 불러오게 되면 key라는게 필요합니다. 임의로 count하여 넣어주었습니다.
          //값이 없어도 렌더링은 됩니다만, 콘솔에 에러가 찍힙니다.
        ))}
      </div>
    </div>
  );
};

DiaryList.defaultProps = {
  diaryList: [] // 디폴트값은 빈 배열을 넣어줍니다.
};

export default DiaryList;

이렇게해서 가져온 List는 또다시 DiaryItem.js으로 묶어서 계층별로 관리하면 됩니다.

const DiaryItem = ({ id, author, content, emotion, created_date }) => {
  return (
    <div className="DiaryItem">
      <div className="info">
        <span className="author_info">
          | 작성자 : {author} | 감정점수 : {emotion} |
        </span>
        <br />
        <span className="date">{new Date(created_date).toLocaleString()}</span>
      </div>
      <div className="content">{content}</div>
    </div>
  );
};

export default DiaryItem;

List에 데이터 삭제하기

삭제를 합니다. 삭제는 앞에서 사용하였던 key 프로퍼티를 이용합니다. 마찬가지로 부모쪽의 데이터를 수정해야하므로, 함수 컴포넌트도 App.js에 작업을 합니다.
onDelete 함수는 아래와 같이 filter를 이용하여 List를 재가공합니다.

  //App.js에 onCreate 아래에 추가합니다.
  const onDelete = (targetId) => {
    const newDiaryList = data.filter(
      (it) => it.id !== targetId
    );
    setData(newDiaryList);
  };
  //return 쪽에 onDelete를 추가합니다.
  return (
    <div className="App">
      <DiaryEditor onCreate={onCreate} />
      <DiaryList diaryList={data} onDelete={onDelete} />
    </div>
  );

마찬가지로 DiaryListDiaryItem쪽으로 컴포넌트를 최종적으로 전달해주고 Item에 각각 delete버튼을 생성해서 사용하면 됩니다.

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

이제 아이템에도 실행할 수 있는 함수를 적용해주면 됩니다.

  const handleClickRemove = () => {
    if (window.confirm(`${id}번째 일기를 정말 삭제하시겠습니까?`)) {
      onDelete(id);
    }
  };
  
  //생략...
  
  return (
  //적절한 부분에 버튼을 만들어서 제공하면 됩니다.
      <button onClick={handleClickRemove}>
        	삭제하기
      </button>
  )

List에 데이터 수정하기

앞서 등록과 삭제를 설명하고 수정을 진행합니다. 수정이 좀까다로와서 마지막에 두었습니다.
수정은 기존에 작성한 원본을 input에 담아줄 수 있어야하며 있어야하며, 현상황에따라 게시물이 수정 상태인지, 그냥 게시 상태인지를 구분 할 줄도 알아야 합니다.

hooks 만들기

앞서 말씀드렸다시피 input에 기존글을 넣어서 보여주고 onChange시 새로운 입력 값을 받아줘야 합니다. 해당 컴포넌트는 DiaryItem에 작성하면됩니다.

  //새로운 content도 검증이 필요하고 focus도 필요하겠죠? useRef를 만듭니다.
  const localContentInput = useRef();
  //새로운 content용 useState도 만듭니다. 이때 초기값은 항상 content, 즉 원글이 항상 딸려옵니다.
  const [localContent, setLocalContent] = useState(content);
  // 현재 상태가 게시상태인지, 수정상태인지를 boolean으로 확인 합니다.
  const [isEdit, setIsEdit] = useState(false);
  // 토글 버튼입니다. 한번씩 실행할때마다 반전되는 효과를 가져옵니다.
  const toggleIsEdit = () => setIsEdit(!isEdit);

수정기능 함수정의

그리고 함수를 정의 합니다.

  const handleQuitEdit = () => { //수정취소시
    setIsEdit(false);
    setLocalContent(content);
  };

  const handleEdit = () => {// 수정완료 버튼 클릭시
    if (localContent.length < 5) {
      localContentInput.current.focus();
      return;
    }

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

이제 렌더링될 페이지만 만들면 됩니다. 3항식을 이용해서 상태에따라 화면에 보이는 구조를 바꿔서 보여줍니다.

 return (
    <div className="DiaryItem">
      <div className="info">
        <span className="author_info">
          작성자 : {author} | 감정 : {emotion}
        </span>
        <br />
        <span className="date">
          {new Date(created_date).toLocaleDateString()}
        </span>
      </div>
      <div className="content">
        {isEdit ? ( //참일시 수정을 위한 input tag를 생성합니다. 
          <textarea
            ref={localContentInput}
            value={localContent}
            onChange={(e) => setLocalContent(e.target.value)}
          />
        ) : ( // 아니면 그냥 글자만 보여줍시다.
          content
        )}
      </div>
      {isEdit ? (
        <>
          <button onClick={handleQuitEdit}>수정 취소</button>
          <button onClick={handleEdit}>수정 완료</button>
        </>
      ) : (
        <>
          <button onClick={handleClickRemove}>삭제하기</button>
          <button onClick={toggleIsEdit}>수정하기</button>
        </>
      )}
    </div>
  );
};
profile
하이

0개의 댓글