0

I cannot figure out how to get rid of this warning when loading my react-native/expo app. I am using AWS Amplify's authentication with @react-navigation bottom tabs & a stack for the authorized screens & logged in screens. I can SignIn & SignOut with no problems, the auth & app stacks are replaced and displayed correctly on those actions, so it looks like my call to Hub.listen('auth', (data) => is working.

However... when I first open the project using expo start and the user is already authenticated/logged in I receive this error: Warning: Can't perform a React state update on an unmounted component.

If I refresh the page, no error! Or if I signout, then signin again, no error! It only happens on the initial load of the app from expo start.

Here is my code for the navigation.js component:

import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Ionicons } from '@expo/vector-icons';

import { useDispatch } from 'react-redux'
import { updateUser } from '../features/user/userSlice'

import Welcome from '../screens/Welcome';
import SignIn from '../screens/SignIn';

import Home from '../screens/Home';
import Profile from '../screens/Profile';
import Colors from '../screens/Colors';
import Loading from '../screens/Loading';

import { Auth, Hub } from 'aws-amplify';

const AuthTabs = createBottomTabNavigator();
const AuthTabsScreen = () => (
  <AuthTabs.Navigator>
    <AuthTabs.Screen
      name="Welcome"
      component={Welcome}
      options={{
        tabBarIcon: (props) => (
          <Ionicons name="home" size={props.size} color={props.color} />
        ),
      }}
    />
    <AuthTabs.Screen
      name="Sign In"
      component={SignIn}
      options={{
        tabBarIcon: (props) => (
          <Ionicons name="home" size={props.size} color={props.color} />
        ),
      }}
    />
  </AuthTabs.Navigator>
);

const AppStack = createStackNavigator();
const AppStackScreen = () => (
  <AppStack.Navigator
    screenOptions={{ animationEnabled: false, presentation: "modal", headerShown: true }}
  >
    <AppStack.Screen
      name="Home"
      component={Home}
      options={{
        headerTitle: 'Home',
      }}
    />
    <AppStack.Screen name="Red"
      options={{
        headerTitle: 'Red Food',
      }}
    >{props => (<Colors {...props} color={"red"} />)}</AppStack.Screen>
    <AppStack.Screen name="Orange">{props => (<Colors {...props} color={"orange"} />)}</AppStack.Screen>
    <AppStack.Screen name="Yellow">{props => (<Colors {...props} color={"yellow"} />)}</AppStack.Screen>
    <AppStack.Screen name="Green">{props => (<Colors {...props} color={"green"} />)}</AppStack.Screen>
    <AppStack.Screen name="Purple">{props => (<Colors {...props} color={"purple"} />)}</AppStack.Screen>
    <AppStack.Screen name="Blue"
      options={{
        headerTitle: 'Water',
      }}
    >
      {props => (<Colors {...props} color={"blue"} />)}
    </AppStack.Screen>
  </AppStack.Navigator>
);

const AppTabs = createBottomTabNavigator();
const AppTabsScreen = () => (
  <AppTabs.Navigator
    screenOptions={{ animationEnabled: false, presentation: "modal", headerShown: false }}>
    <AppTabs.Screen
      name="AppHome"
      component={AppStackScreen}
      options={{
        tabBarIcon: (props) => (
          <Ionicons name="home" size={props.size} color={props.color} />
        ),
      }}
    />
    <AppTabs.Screen
      name="My Stuff"
      component={Profile}
      options={{
        tabBarIcon: (props) => (
          <Ionicons
            name="checkmark-circle-outline"
            size={props.size}
            color={props.color}
          />
        ),
      }}
    />
  </AppTabs.Navigator>
);

const RootStack = createStackNavigator();
const RootStackScreen = () => {
  const dispatch = useDispatch()
  const [isLoading, setIsLoading] = React.useState(true);
  const [user, setUser] = React.useState(null);

  React.useEffect(() => {

    Hub.listen('auth', (data) => {
      const { payload } = data
      //console.log('A new auth event has happened: ', payload)
      if (payload.event === 'signIn') {
        console.log('A user has signed in!', payload.data.username)
        setUser(payload.data.username)
        dispatch(updateUser(payload.data.username))
      }
      if (payload.event === 'signOut') {
        //console.log('A user has signed out!')
        setUser()
      }
    })

    return () => {
      Hub.remove('auth')
    }
  }, []);

  React.useEffect(() => {    
    setIsLoading(!isLoading);
    checkUser()
  }, []);

  async function checkUser() {
    try {
      const thisUser = await Auth.currentAuthenticatedUser()
      //console.log('thisUser', thisUser.username)      
      if (thisUser) {
        setUser(thisUser.username)
        dispatch(updateUser(thisUser.username))
      }
    } catch (err) {
      //console.log(' User is not signed in', err)
      setUser()
    }
  }

  return (
    <RootStack.Navigator
      screenOptions={{ animationEnabled: false, presentation: "modal", headerShown: false }}
    >
      {isLoading ? (
        <RootStack.Screen name="Loading" component={Loading} />
      ) : user ? (
        <RootStack.Screen name="AppTabsScreen" component={AppTabsScreen} />
      ) : (
        <RootStack.Screen name="AuthTabsScreen" component={AuthTabsScreen} />
      )}
    </RootStack.Navigator>
  );
};

export default () => {
  return (
    <NavigationContainer>
      <RootStackScreen />
    </NavigationContainer>
  );
};

Do I just ignore this warning? Will it affect my ios build/app? Any help would be greatly appreciated.

IRiley
  • 1
  • 1
  • 1
    Does this answer your question? [Can't perform a React state update on an unmounted component](https://stackoverflow.com/questions/53949393/cant-perform-a-react-state-update-on-an-unmounted-component) – LuckyTuvshee Feb 08 '22 at 10:35

1 Answers1

0

My understanding of this is that the warning occurs each time the Amplify Auth API responds after the component has already unmounted. This blog helped me fix it.

Say you have a screen which you render wrapped inside the withAuthenticator() HOC which contains a signOut button.

const signOut = async () => {
  try{
    await Auth.signOut({global: true})
    navigation.navigate("SplashScreen")
  }
  catch(error){
  ...
  }
}

A button is wired to it as follows:

<TouchableOpacity onPress={signOut}>
    <Text>Sign Out</Text>
</TouchableOpacity>

If you have this signout function coded to navigate back to a splash screen as above, a signout and subsequent sign in causes the error in the question.

Therefore,

  1. Add a local variable to track whether the user is currently logged in as per Amplify Auth. If so, this variable should be set to true.

`

const [isLogged, setIsLogged]=useState(false)
    const updateLoggedStatus = async () => {
      const userName = await getCurrentUserName
      let newLoggedStatus = false
      if(userName){
        newLoggedStatus = true
      }
      setIsLogged(newLoggedStatus)
    }

and the getCurrentUserName could be added to a helper class that's exported (for reusability):

import {Auth} from 'aws-amplify' 

export const getCurrentUserName = () => {
  return new Promise((resolve, reject)=>{
    Auth.currentAuthenticatedUser()
    .then(user=>{
     if(user.userName) resolve(user.userName)
     else resolve(null)
  })
  .catch(err=> resolve(null))
  })
}
  1. Once we have this, using useEffect(), we can have the Hub (import {Hub} from aws-amplify) register and de-register when the component first mounts and when it at last unmounts respectively.

`

useEffect(() => {
      //only when component first renders
      updateLoggedStatus()
      Hub.listen('auth', updateLoggedStatus)
      return () => {
        Hub.remove('auth') // only when component finally unmounts
      }
    }, [])
basecamp
  • 81
  • 1
  • 5