현재 서비스의 어드민을 작업중이고, 구현은 위 이미지처럼 쪼개어 구현중이다. (실제 구현에 대해서는 추후 블로그 포스팅을 작성할 예정이다.)
각 데이터들은 리액트 쿼리의 조회 요청을 통해 받은 값을 그대로 보이고, post나 patch 요청이 생기면 조회 데이터를 재조회하도록 하여 데이터를 렌더링하도록 하였다. 이 때, 분명 데이터가 변경되었는데 변경된 데이터가 렌더링되지 않는 요상한 버그가 있었다.
(무지한 나에게 있을 뿐...)
처음에는 어느 부분에서 data 변경이 반영되지 않아 리렌더링이 일어나지 않고 있다고 생각했다. 그래서, 각 컴포넌트를 따라 들어가보며 data가 어떻게 들어오고 있는지를 추적해보았다.
페이지 데이터 > table 데이터 > row 데이터 > 한 칸 데이터
를 따라들어가보며 console.log를 찍어본 결과, 원하는 대로 데이터가 받아와지고 있었다.
해당 데이터는 row 추가하기 버튼이 존재하여, 프론트 단에서 default 값으로 row를 추가할 수 있는 기능이 있었다. 이는 리액트 쿼리의 setQueryData
를 사용하여 쿼리 데이터에 원하는 값을 추가하여 구현하고 있었다. setQueryData
를 처음 사용해보았기 때문에, 재조회 요청이 날아가면 setQueryData로 인해 다시 한 번 이전 값으로 씌워져서 리렌더링이 일어나지 않은 것처럼 느껴지는 게 아닐까 의심했다. 하지만,
이를 토대로, 데이터는 정상적으로 뿌려졌으나 무슨 이유로 리렌더링이 일어나지 않았다는 사실을 알 수 있었다.
리액트 렌더링과 관련있을 만한 요소를 점검해보던 중 key가 문제의 원인이라는 사실을 깨달았다.
리액트 공식문서를 살펴보면 key는 형제 사이에서 고유한 값을 사용해야 한다고 적혀있다. 그래서 현 프로젝트에서는 컴포넌트 이름#index
로 key 값을 설정하기로 합의를 하였는데...
공식 문서에 index를 key로 사용하지 말라는 구절도 있었다.
자세한 원인을 알아보기 위해 리액트에서 key props를 사용하는 이유를 알아보자.
타 블로그 글들을 참고해보면, key를 파일 이름에 많이 비유한다. 마치 파일 이름처럼 유일하게 식별할 수 있는 key
를 사용하여 특정 요소가 추가된 요소인지, 기존 요소인지를 분간할 수 있다. 이 말은 key가 동일하다면 요소의 내용이 변경되었어도 이를 모르고 지나친다는 의미이다.
내 오류 상황의 경우, 단순하게 표현해본다면 다음 상황과 같았을 것이다.
// 변경 전
const data = ['A', 'B', 'C', 'D'];
// default 값 추가
const data = ['A', 'B', 'C', 'D', 'default'];
// api 요청 이후
const data = ['E', 'A', 'B', 'C', 'D'];
이 경우, 각 data 요소의 key를 인덱스로 삼으면, 변경 전 > default 값 추가
로 변할 때 추가된 default로 인해 4번이라는 index가 추가된다. 따라서, 해당 변화에 대한 렌더링이 일어난다.
반면, default 값 추가 > api 요청 이후
로 변할 때는 index가 [0, 1, 2, 3, 4]로 동일하다. 리액트는 key 값이 동일하면 내용이 변경되지 않았다고 생각하기 때문에 data는 변해도 렌더링이 일어나지 않는다.
key 값을 데이터마다 고유하게 사용하는 가장 쉬운 방법은 서버로부터 받은 고유한 id 값을 사용하는 것이다. 이를 사용하면 변경 전, default 값 추가, api 요청 이후에 상관 없이 고유한 id 값을 key로 사용하기 때문에 data에 값이 추가되었거나 순서가 바뀌었음을 리액트가 알 수 있어 문제를 해결할 수 있다.
리액트 key에 항상 서버로부터 전달받은 고유한 id를 사용할 수 있다면 좋겠지만, 고유한 id가 없는 경우도 종종 있다. 그런 경우 index를 key로 왕왕 사용하게 되는데, 오늘의 경험을 통해 배열에 순서가 변경되거나 추가/삭제가 일어나는 경우 index의 사용을 지양해야겠다는 소중한 깨달음을 얻을 수 있었다.
컴포넌트#index를 키로 사용하셨군요 ㅎㅎ 단순히 index만 사용할 경우 콘솔에 이번 글과 관련된 warning이 표시돼서 빠르게 찾았을 수도 있었을텐데 아쉽네요 🥲