React Native/React Native_error

[React Native - ios / android] Can't perform a React state update on an unmounted component

bocoder
728x90
반응형

* 비동기 작업을 처리하는 과정에서 발생한 에러

 ERROR  Warning: Can't perform a React state update on an unmounted component. 
 This is a no-op, but it indicates a memory leak in your application. 
 To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.

" 언마운티드된 컴포넌트에 대해서는 상태를 업데이트 할 수 없다.
해당 작업은 수행되지 않지만 메모리 누수가 발생된다.
해결방법으로 useEffect 의 cleanup function을 이용해라. "

 

 

** 에러 발생 배경 **

 

해당 에러 발생 시 작성했던 코드 및 순서는 아래와 같다.

1) 회원가입이 완료된 정보로 ID/PW 입력 후 <SignButtons /> 버튼을 클릭해 로그인 시도 

2) 비동기로 로그인 검증 시, setLoading(true) 로 로딩 ON

3-1) 로그인 성공 시 setUser(profile) 을 호출하게 되고, user 가 셋팅되면 'MainTab' 으로 화면으로 전환

3-2) 로그인 실패 시 화면이동 없이 에러 메시지 출력

4) 로그인 성공/실패 검증 후, setLoading(false) 로 로딩 OFF

 

3-1)번 단계에서 로그인이 성공하면서 화면이 전환되었지만,

finally { setLoading(false) } 이 실행되면서 loading 변수를 false 로 변경 시도하여 에러가 발생했다.

 

# ... > SignInScreen

...

function SignInScreen({navigation, route}) {
  ...
  const onSubmit = async () => {           // 비동기 처리
    ...
    setLoading(true);                      // 로딩 ON
    
    try {                                  // 로그인 시도
      const {user} = isSignUp ? await signUp(info) : await signIn(info);
      const profile = await getUser(user.uid); 
      if (!profile) {
        navigation.navigate('Welcome', {uid: user.uid});  // 로그인 성공 시 화면 전환
      } else {
        setUser(profile);
      }
    } catch (e) {                          // 로그인 실패                   
      const message = {
        ...   
      };
      const msg = message[e.code] || `${isSignUp ? '가입' : '로그인'} 실패`;
      Alert.alert('실패', msg);
    } finally {
      setLoading(false);                   // 로딩 OFF
    }
  };

  return (
    ...
      <SignButtons
        isSignUp={isSignUp}
        onSubmit={onSubmit}
        loading={loading}
      />
    ...   
  );
}

...

# ... > RootStack.js

...

function RootStack() {
  const {user} = useUserContext();
  return (
    <Stack.Navigator>
      {user ? (
        <>
          <Stack.Screen
            name="MainTab"
            component={MainTab}
            options={{headerShown: false}}
          />
        </>
      ) : (
        <>
          <Stack.Screen
            name="SignIn"
            component={SignInScreen}
            options={{headerShown: false}}
          />
          <Stack.Screen
            name="Welcome"
            component={WelcomeScreen}
            options={{headerShown: false}}
          />
        </>
      )}
    </Stack.Navigator>
  );
}

export default RootStack;

 

** 해결 방안 **

 

방안 1) setLoading(false) 호출 위치 변경

로그인 성공/실패 여부와 관계없이 수행되는 finally { } 구문을 삭제하고,

로그인 실패 시에만 수행되는 catch (e) {  } 에 setLoading(false) 을 넣어줌으로 해결 된다.

 

방안2) useEffect 의 cleanup function 이용

useEffect Hook 을 추가하고 return 함수를 작성하면,

unmount 시점에 수행된다.

* reference : https://react.vlpt.us/basic/16-useEffect.html

 

...

function SignInScreen({navigation, route}) {

  // ** 방안 2 - 추가 **
  useEffect(() => {
  	...  // 컴포넌트가 mounted 될 때 실행 됨
    return () => setLoading(false); // 컴포넌트가 unmount 될 때 실행 됨 (cleanup function) 
  }, []);

  ...
  const onSubmit = async () => {           // 비동기 처리
    ...
    setLoading(true);                      // 로딩 ON
    
    try {                                  // 로그인 시도
      const {user} = isSignUp ? await signUp(info) : await signIn(info);
      const profile = await getUser(user.uid); 
      if (!profile) {
        navigation.navigate('Welcome', {uid: user.uid});  // 로그인 성공 시 화면 전환
      } else {
        setUser(profile);
      }
    } catch (e) {                          // 로그인 실패                   
      const message = {
        ...   
      };
      const msg = message[e.code] || `${isSignUp ? '가입' : '로그인'} 실패`;
      Alert.alert('실패', msg);
      setLoading(false);                   // ** 방안 1 - 추가 ** 
    } finally {
      // setLoading(false);                   // 로딩 OFF ** 방안 1 - 삭제 ** 
    }
  };

  return (
    ...
      <SignButtons
        isSignUp={isSignUp}
        onSubmit={onSubmit}
        loading={loading}
      />
    ...   
  );
}

...
728x90
반응형