* 비동기 작업을 처리하는 과정에서 발생한 에러
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}
/>
...
);
}
...