React

다이어리 검색기 만들기(5) - 재사용 위한 컴포넌트 추출

joy_lee 2021. 10. 11. 21:51

만들다가 여러 컴포넌트에서 동일한 키워드를 공유하기 위해 최상위 컴포넌트에서 keyword 목록을 관리하게 됐다. 최종 형태는 이 글에서 확인할 수 있고, 아래 글은 참고하면 좋을 것 같다.

https://joylee-developer.tistory.com/125

 

React - State 끌어올리기

State 끌어올리기 React에서는 기본적으로 하향식 데이터 흐름을 추천한다. 상위 컴포넌트에서 state를 관리하고, 하위 컴포넌트에서는 전달된 props를 처리하도록 한다. 그런데 동일한 데이터를 여

joylee-developer.tistory.com

 


 

위 사진에서 모든 부분이 Category 컴포넌트에 속해 있었는데 아래쪽의 SelectedKeywordLists를 따로 분리했다.

아래 그림처럼 다이어리 정보를 키워드와 함께 표시할 예정인데, 같은 컴포넌트를 재활용하고 싶었기 때문이다.

그래서 Category에서 selectedKeywordLists와 관련된 코드는 제거해주고, SelectedKeywordLists를 불러와 props를 넘겨주었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Category.js
import SelectedKeywordLists from "./SelectedKeywordLists.js";
 
// ...
function Category() {
  // ...
  return (
    <div className="category">
      <div>
        {lists}
      </div>
      <button onClick={handleResetBtn}>RESET!</button>
      <SelectedKeywordLists
        selectedKeywords={selectedKeyword}
        onChange={(keywords) => setKeyword(keywords)}
        ></SelectedKeywordLists>
    </div>
  );
}
cs

Category에서도 키워드를 선택할 수 있고, 하위 컴포넌트에서 제거한 키워드 리스트도 받아야 한다.

props는 selectedKeywords를 통해 선택된 키워드 전달하고, onChange를 통해 하위컴포넌트에서 버튼을 눌러 제거했을 때 변화한 키워드 목록을 전달받도록 한다.

 

selectedKeywordLists.js는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import React from 'react';
 
function SelectedKeywordLists(props) {
  let keywords = props.selectedKeywords;
 
  // selectedKeywordLists
  const keywordLists = [];
  let i = 0;
  while(i < keywords.length) {
    keywordLists.push(
      <li
        className="keyword isSelected"
        key={i}>
        <button 
          className="KeywordDeleteBtn"
          id={keywords[i]}
          onClick={handleDelBtn}>
            <span></span>
            {keywords[i]}
            <i className="fas fa-times"></i>
        </button>
      </li>
    )
    i = i + 1;
  }
 
  // DeleteBtn
  function handleDelBtn(e) {
    let id = e.target.id;
    if(id === '') {
      id = e.target.parentElement.id;
    };
    keywords = keywords.filter(keyword => keyword !== id);
    props.onChange(keywords);
  }
 
  return (
    <div className="selectedKeywordLists">
      {keywordLists}
    </div>
  )
}
 
export default SelectedKeywordLists;
cs

keywords는 state로 설정하지 않고, let keywords = props.selectedKeywords; 로 props를 받아서 대입해준다.

keywordLists는 Category에서 분리하기 전과 같이 만들어주고, handleDelBtn도 비슷하다.

그런데 이번에는 keywords가 state가 아니기 때문에 useEffect를 사용하지 않고,

handleDelBtn안에서 바로 설정해준다.

keywords = keywords.filter(keyword => keyword !== id);

props.onChange(keywords);

를 하면 변화한 keywords가 상위의 Category로 이동되고, setKeyword()를 통해 state값을 변화시킬 수 있다.

 

컴포넌트를 따로 분리한 후에도 잘 작동하는 것을 확인했다.

 

 

* 하면서 새롭게 알게된 것

처음에는 최하위 컴포넌트인 SelectedKeywordLists에서 keyword를 state로 설정했었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 오류난 코드
function SelectedKeywordLists(props) {
  const [keywords, setKeywords] = useState(props.selectedKeywords);
  // 올바른 코드 : let keywords = props.selectedKeywords;
 
  useEffect(() => {
    props.onChange(keywords);
  }, [keywords, props]);
  // 올바른 코드
  // function handleDelBtn(e) {
  //  let id = e.target.id;
  //  if(id === '') {
  //    id = e.target.parentElement.id;
  //  };
  // keywords = keywords.filter(keyword => keyword !== id);
  // props.onChange(keywords);
  // }
}
cs

그런데 이렇게 하니까 상위 Category에서 선택한 것들이 제대로 반영되지 않았다. 처음에 초기값만 반영되고, 삭제만 가능한 상황이었다.

잘 안되길래 props와 state의 정의에 대해 다시 확인해봤다.

props는 상위 컴포넌트로부터 전달받은 변하지 않는 내용이고,

state는 하위 컴포넌트가 구현에 필요한 데이터들이다.

 

그래서 props로 전달받은 자료는 keywords에 저장하고, useEffect대신 삭제하는 과정안에서 keywords를 변경시키고, props.onChange(keywords)를 통해 상위 컴포넌트에 전달하도록 했다.

이렇게 수정했더니 원하는 대로 잘 작동했다.