본문 바로가기
개발 관련 학습 및 문제해결

useEffect 무한 렌더링 문제, forceUpdate 후에도 계속 이전 텍스트가 화면에 남는 문제 [20221125 -TIL]

by 날파리1 2022. 11. 25.

오늘은 이전부터 자주 겪었던 useEffect 의 무한 렌더링 문제와 화면에 계속 이전 데이터가 남아있는 문제를 해결해서 적어보려고 한다. 사실 자주 겪었던 고통이라 꼭 한 번 정리했어야했지만 여태 잘 이해를 하지 못해 제대로 정리를 못했었다.

 

useEffect 무한루프 문제

useEffect 내부의 함수가 계속 렌더링 되어 콘솔을 찍어보면 한없이 렌더링되고 있는 것을 볼 수 있다. 이건 주로 2가지 문제로 나뉘는데

1. useEffect의 두번째 인자를 넣지 않음 ( 종속되는 배열이 없을때)

useEffect(() => {
    console.log('is Listening');
    forceUpdate();
  });

 

useEffect에 두번째 인잘르 넣지 않았을때에는 아래와 같은 상황에서 useEffect가 실행된다.

 

- 컴포넌트 생성 후 처음 화면에서 렌더링

- 컴포넌트에 새로운 props가 전달되며 렌더링 ( 컴포넌트에 새 props 전달되면서 새로고침)

- 컴포넌트의 state가 바뀌며 렌더링 (useState의 함수가 실행될때마다 화면 전체를 새로고침함으로)

 

그런데 이 경우에 왜 무한 루프가 발생할까?

useEffect에 종속인자가 없는 경우 는 위에서 언급했듯 state 가 바뀌면 useEffect 안의 함수를 다시 실행하는데 이때 useEffect안에 setState 가 포함된 함수를 넣어놓으면 무한루핑이 된다. 

1. 화면 첫 렌더링시 setState 실행 

2. effect가 다시 state 변경을 감지 

3 . 다시 재실행

4. useEffect 안의 setState 함수 다시 재실행 -> 무한 루핑

 

따라서 아래처럼 두번째 인자에 빈 배열을 주면 해결된다.

useEffect(() => {
    console.log('is Listening');
    forceUpdate();
  }, []);

2. useEffect 두번째 인자인 종속 배열이 빈 배열일 때

이때에는 화면이 처음 렌더링 될 때에만 useEffect 함수가 실행된다.

 

3. useEffect 두번째 인자인  종속 배열에 값이 있을 때

 

- 처음 화면에 렌더링

- 종속성 배열 내의 props 값이 변할 때

 

따라서 위와 같은 경우에도 종속 props 에 setState 함수를 넣어두면 무한 루핑이 된다.

props 에 state 변경을 감지하면 화면이 리렌더링 되고 이는 리렌더링된 첫화면을 다시 useEffect가 렌더링하기 때문

 

무한 루프 코드 예시

useEffect(() => {
      forceUpdate();
      console.log('forceUpdate!!');
    }, [forceUpdate]);
  useEffect(() => {
      setCount(count + 1);
      console.log('forceUpdate!!');
    }, [forceUpdate]);

따라서 useEffect 에 setState관련 함수를 effect 실행 파트와 두번째 종속인자에 둘다 넣으면 무한 루핑이 발생한다.

 

아래와 같이 하는 것은 괜찮다.

useEffect(() => {
    console.log('forceUpdate!!');
  }, [forceUpdate]);

 

forceUpdate 후에도 계속 이전 텍스트가 화면에 남는 문제

가져오려는 객체가 존재하지 않으면 스토어가 객체가 존재하지 않는 상태를 인식해 페이지에서 

"** 가 아직 존재하지 않습니다. 업데이트 될 예정입니다!" 라는 문구를 보여주고 있었다.

그런데 객체를 생성해서 백엔드에서 객체가 생성된 이후에도 "** 가 아직 존재하지 않습니다. 업데이트 될 예정입니다!" 라는 문구가 page 단에서 사라지지 않고 객체의 내용과 같이 보이는 상황이 발생했다.

command + R  새로고침 키로 화면을 새로고침을 하면 저 문구가 없어졌다. 

forceUpdate 로 화면을 갱신해주고 있는데 왜?

애초에 리액트에서 컴포넌트를 구성할때 Store 를 이용한 Pub - Sub 구조를 사용하고 있어서 어떠한 객체를 가져오거나 지우는 등의 업데이트가 있으면 publish 를 해준다. 따라서 업데이트 된 내용을 page(User Interface) 에서 다시 화면을 렌더링 시켜줄 필요가 없다.

그런데 왜 화면을 새로고침하면 되고 setState로 갱신하면 안돼?

이는 모두의 경우가 그런건 아니겠지만 새로고침했을때에는 이전에 화면에 가져오려는 객체가 없을때 받아오는 상황자체를 다시 지우고 객체가 있는 상황에서 가져와서 "**가 아직 존재하지 않습니다"라는 말이 보이지 않는 것이고 이 문구가 사라지지 않은 것은 스토어 측에서 이 문구를 나오는 조건의 상태를 객체를 가져올때 갱신을 안시켜줬을 확률이 높다.

 

나의 경우는 grammar 라는 객체를 가져오는 스토어에서 이것이 없을 때 grammarState 의 상태를 바꾸어 errorMessage 의 상태를 백엔드에서 보내주는 에러 메시지로 보여주도록 해놓았는데 객체를 다시 백엔드에서 정상적으로 가져오더라도 해당 에러메시지의 내용을 리셋을 시켜주지 않아 화면에 계속 남아있었다.

 

아래의 코드를 fetchGrammar() 라는 곳에 넣어주니 객체를 가져올때 에러메시지를 리셋시켜준후 publish 를 해서 깔끔하게 없애주었다.

    const message = '';

      this.changeGrammarState('found', { errorMessage: message });

Store 코드 전문

export default class GrammarStore extends Store {
  constructor() {
    super();

    this.grammar = {};

    this.grammarState = '';

    this.errorMessage = '';
  }

  async fetchGrammar() {
    try {
      this.grammar = await grammarApiService.fetchGrammar();

      const message = '';

      this.changeGrammarState('found', { errorMessage: message });

      this.publish();
    } catch (error) {
      const { message } = error.response.data;
      this.changeGrammarState('notFound', { errorMessage: message });
    }
  }

  changeGrammarState(state, { errorMessage = '' } = {}) {
    this.errorMessage = errorMessage;
    this.grammarState = state;
    this.publish();
  }

  get isNoGrammar() {
    return this.grammarState === 'notFound';
  }
}

export const grammarStore = new GrammarStore();

댓글