I know there are other answers on here for this but I can't find one that works the way my app is structured. I set up my app with Firebase Authentication using their documentation but once it showed how to create a persistent log in, it didn't show how to logout and re-render the Home Screen.
This is the warning I can't resolve:
Warning: Cannot update a component from inside the function body of a different component. at [native code]:null in dispatchAction at node_modules/@react-navigation/core/src/useNavigationCache.tsx:100:22 in acc.route.key.setOptions at node_modules/@firebase/webchannel-wrapper/dist/index.js:42:151 in Rb$argument_0 at [native code]:null in performSyncWorkOnRoot at [native code]:null in forEach at node_modules/react-native/Libraries/Core/setUpReactRefresh.js:43:6 in Refresh.performReactRefresh at [native code]:null in callFunctionReturnFlushedQueue
Here is my App.js file:
import React, { useState, useEffect } from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { NavigationContainer } from '@react-navigation/native';
import { Login, Home, Events, CreateEvent, EventDetails } from './src/screens/';
import { Registration } from './src/navigation';
import { firebase } from './src/firebase/config';
import { TouchableOpacity, Image, View } from 'react-native';
import Logout from './src/screens/Logout/Logout';
const Stack = createStackNavigator();
export default function App() {
const [loading, setLoading] = useState(true)
const [user, setUser] = useState(null);
useEffect(() => {
const usersRef = firebase.firestore().collection('users');
firebase.auth().onAuthStateChanged(user => {
if (user) {
usersRef
.doc(user.uid)
.get()
.then((document) => {
const userData = document.data()
setLoading(false)
setUser(userData);
})
.catch((error) => {
setLoading(false)
});
} else {
setLoading(false)
}
});
}, [user]);
if (loading) {
return (
<></>
)
}
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Home"
options={{
headerTitle: 'Showday',
headerTitleAlign: 'center',
headerRight: () => (
<TouchableOpacity
onPress={() => alert('This is the menu button')}>
<Image source={require('./assets/menuicon.png')} />
</TouchableOpacity>
),
headerLeftContainerStyle: {marginLeft: 20},
headerRightContainerStyle: {marginRight: 20}}}>
{props => <Home {...props} extraData={user} />}
</Stack.Screen>
<Stack.Screen
name="Events"
options={{
headerTitle: 'Events',
headerTitleAlign: 'center',
headerRight: () => (
<TouchableOpacity
onPress={() => alert('This is the menu button')}
style={{ marginLeft: 20}} >
<Image source={require('./assets/menuicon.png')} />
</TouchableOpacity>
),
headerRightContainerStyle: {marginRight: 20}}}>
{props => <Events {...props} extraData={user} />}
</Stack.Screen>
<Stack.Screen
name='CreateEvent'
options={{
headerTitle: 'Create New Event',
headerTitleAlign: 'center',
headerRight: () => (
<TouchableOpacity
onPress={() => alert('This is the menu button')}
style={{ marginLeft: 20}} >
<Image source={require('./assets/menuicon.png')} />
</TouchableOpacity>
),
headerRightContainerStyle: {marginRight: 20}}}>
{props => <CreateEvent {...props} extraData={user} />}
</Stack.Screen>
<Stack.Screen name='EventDetails' options={{headerTitle: 'Event Details', headerTitleAlign: 'center'}}>
{props => <EventDetails {...props} />}
</Stack.Screen>
<Stack.Screen name='Login' component={Login}/>
<Stack.Screen name='Logout' component={Logout} />
<Stack.Screen name='Register' component={Registration} />
</Stack.Navigator>
</NavigationContainer>
);
}
Here is my Home.js file which displays Login or Logout depending if the user is signed in or not:
import React, { useState } from 'react';
import { useNavigation } from '@react-navigation/native';
import { Text, View, TouchableOpacity, Image } from 'react-native';
import { firebase } from '../../firebase/config';
import styles from './HomeStyles';
function Home(props) {
const [loginButtonDisplay, setLoginButtonDisplay] = useState(true)
const user = props.extraData;
const nav = useNavigation();
const login = () => {
nav.navigate('Login');
}
const logout = () => {
nav.navigate('Logout');
}
const renderLoginLogout = () => {
if (user) {
setLoginButtonDisplay(false);
}
if (loginButtonDisplay) {
return (
<TouchableOpacity
onPress={login}>
<Text>Login</Text>
{/* <Image source={require('../../../assets/login.png')} /> */}
</TouchableOpacity>)
} else {
return (
<TouchableOpacity
onPress={logout}>
<Text>Logout</Text>
{/* <Image source={require('../../../assets/logout.png')} /> */}
</TouchableOpacity>
);
}
}
nav.setOptions({
headerLeft: () => (
<View>
{renderLoginLogout()}
</View>
),
})
const onEventsPressed = () => {
nav.navigate('Events');
}
return (
<View style={styles.container}>
<View style={styles.button__row}>
<TouchableOpacity
style={styles.button}
onPress={onEventsPressed}>
<Text style={styles.button__text}>{'Upcoming\nEvents'}</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => null}>
<Text style={styles.button__text}>Shows Today</Text>
</TouchableOpacity>
</View>
<View style={styles.button__row}>
<TouchableOpacity
style={styles.button}
onPress={() => null}>
<Text style={styles.button__text}>Stats</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => null}>
<Text style={styles.button__text}>Locations</Text>
</TouchableOpacity>
</View>
<View style={styles.button__row}>
<TouchableOpacity
style={styles.button}
onPress={() => null}>
<Text style={styles.button__text}>Workouts</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.button}
onPress={() => null}>
<Text style={styles.button__text}>More</Text>
</TouchableOpacity>
</View>
</View>
);
}
export default Home;
If a user is logged in I take them to a logout screen asking to confirm and when I go back to Home Screen it still displays 'Logout' instead of 'Login'
import React, { useState } from 'react';
import { Text, View, TouchableOpacity, TextInput, Keyboard } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import Spinner from 'react-native-loading-spinner-overlay';
import { firebase } from '../../firebase/config';
import styles from './LogoutStyles';
function Logout () {
const [visible, setVisible] = useState(false);
const nav = useNavigation();
const yesPressed = () => {
setVisible(true);
firebase.auth().signOut().catch(error => alert(error.message));
nav.goBack();
}
const noPressed = () => nav.goBack();
return (
<View style={styles.container}>
<Spinner visible={visible} />
<Text style={styles.text}>Are you sure you want to log out?</Text>
<View style={styles.button__container}>
<TouchableOpacity
onPress={yesPressed}
style={styles.button}>
<Text style={styles.button__text}>Yes</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={noPressed}
style={styles.button}>
<Text style={styles.button__text}>No</Text>
</TouchableOpacity>
</View>
</View>
);
}
export default Logout;
Any help with this warning is greatly appreciated.
Your code is ok in the most common use cases, but navigation options are built on first render, so every new render with props changes does not update them.
You need to manage the navigation.setOption on a useEffect hook, so on each change of useEffect dependencies, you update the nav options:
React.useEffect(()=>{
nav.setOptions({
headerLeft: () => (
<View>
{renderLoginLogout()}
</View>
),
})
},[user])