리액트 랜더링 관련한 이슈를 접했었다.
먼저 어떤 문제가 있었는지 알아보면, 내가 담당하는 MainPage에서 API로 데이터를 가져온 후 랜더링하는 과정에서 특정 컴포넌트에서 계속 특정 객체 값(description)이 계속 없어서 랜더링할때 오류를 뿜어내는 상황이었다.
MainPage > NavChannel = MainContentsContainer 관계에있다.
MainPage에서 API로 받아온 값을 Prop로 내려주는 상황에서 발생한 문제이다.
결론적으로 React 랜더링을 완벽하게 이해하지 못했기에 발생한 문제라고 생각한다.
막연하게 당연히 API로 데이터를 받고 목데이터와 교체해주면 될 줄 알았다.
하지만 당연하지 않았다.
API로 데이터를 받고 그 데이터를 상태로 업데이트하고 이를 Porp으로 뿌려주는데 API 로직을 useEffect안에 위치시켜 컴포넌트가 연결되었을때 1번만 동작하게 만들고 싶었다.
하지만 API로 데이터를 받고 상태가 변경되지 전에 자식 컴포넌트로 빈배열의 Prop가 전달되었고 MainContentsContainer에서는 빈배열에 대해서 filter를 사용하고 있는 상황에서 description을 찾으려 하니 에러가 나는거 였다.
애초에 빈배열로 들어온 데이터를 filter를 이용해 거르고 그 아무것도 없는 객체에 description으로 접근하려고하니 에러를 발생시키는 것이었다.
다른 컴포넌트에서는 위와 같은 이슈가 없었기에 문제가 있는 MainContentsContainer 컴포넌트를 주석처리하면 나머지 하나의 컴포넌트는 정상적으로 출력이되는걸 볼 수 있었다.
이유는 해당 컴포넌트는 문제가 있었던 컴포넌트와 달리, 빈배열에서 무언가 타고 들어가는 추가로직이 없었기 때문에 이 상태에서 API 로직 실행 후 Prop으로 받은 데이터가 변경되기 때문에 이에 따라 리랜더링이 정상적으로 일어났다.
초반에는 처음 빈배열로 전달하는게 맞나?? 라는 생각이 들었었지만, 멘토님의 시원한 답변으로 이는 정상인 것으로 판명이 났다. 처음 빈배열로 전달되는건 당연하며 로직 실행 후 빈배열에서 어떠한 값이 할당되어 변화가 일어났기 때문에 리랜더링이 되는 것이다.
이에 대한 해결 방법으로 크게 2가지를 멘토님이 제안해주셨다.
가장 좋다고 생각하는 1번 방법
- MainContentsContainer에서 사용된 코드를 그대로 채용하여 알아보자.
아래와 같은 상황에서는 빈배열에 대해 filter를 진행하고 있고 거기에 값을 가져오려하는 상황이다.

여기에 옵셔널체이닝을 적용하여 아래와 같이 로직을 수정하는 방법이다.
{channels && channels?.filter((_, index) => index === selectId)[0]?.description}
위와 같이 옵셔널 체이닝은
?.
은 ?.
'앞’의 평가 대상이 undefined
나 null
이면 평가를 멈추고 undefined
를 반환한다.따라서 데이터에 안전하게 접근할 수 있게 된다.
여기서 팁으로 옵셔널 체이닝 연산자가 등장하게 된 배경은 아래 코드 한줄로 표현이 가능할 것 같다.
아래와 같이 계속 타고타고 코드가 길어지는 문제가 발생하게 되므로 옵셔녈 체이닝이 등장하였다고 한다.
{channels && channels.filter && channnels.filter.description && channels.filter((_, index) => index === selectId)[0].description
옵셔널 체이닝 남용에 주의하자.
?.
는 존재하지 않아도 괜찮은 대상에만 사용해야 합니다. 사용자 주소를 다루는 위 예시에서 논리상user
는 반드시 있어야 하는데address
는 필수값이 아닙니다. 그러니user.address?.street
를 사용하는 것이 바람직합니다. 실수로 인해user
에 값을 할당하지 않았다면 바로 알아낼 수 있도록 해야 합니다. 그렇지 않으면 에러를 조기에 발견하지 못하고 디버깅이 어려워집니다. 출처: https://ko.javascript.info/optional-chaining
2번 초기 State에서 어떠한 값이 없을때를 미리 예측하여 이를 초기 State에 넣어주는 방법
const MainPage = React.memo(() => { const [channels, setChannels] = useState([[{ filter: [{ description: '' }] }]])
거의 한 4시간을 이것만 고치는데 투자했는데 좋은 경험이었다...
함께 고민해주신 승록님 다슬님 그리고 기동멘토님 감사합니다!!!
이번 계기를 통해 리액트에서 화면 전체의 랜더링이 아닌 컴포넌트가 기준이되는 랜더링에 대해 조금은!! 이해하고 체감할 수 있었다.