React Native - 警告:无法从不同组件的函数体内部更新组件

React Native - Warning: Cannot update a component from inside the function body of a different component

我知道这里还有其他答案,但我找不到适合我的应用程序结构的答案。我使用他们的文档使用 Firebase 身份验证设置了我的应用程序,但是一旦它显示了如何创建持久登录,它就没有显示如何注销和重新呈现主屏幕。

这是我无法解决的警告:

警告:无法从不同组件的函数体内更新组件。 在 [native code]:null in dispatchAction 在 node_modules/@react-navigation/core/src/useNavigationCache.tsx:100:22 在 acc.route.key.setOptions 在 node_modules/@firebase/webchannel-wrapper/dist/index.js:42:151 在 Rb$argument_0 在 [本机代码]:null 在 performSyncWorkOnRoot 在 [native code]:null in forEach 在 node_modules/react-native/Libraries/Core/setUpReactRefresh.js:43:6 在 Refresh.performReactRefresh 在 [本地代码]:callFunctionReturnFlushedQueue

中为 null

这是我的 App.js 文件:

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>
  );
}

这是我的 Home.js 文件,它根据用户是否登录显示登录或注销:

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;

如果用户已登录,我会将他们带到注销屏幕要求确认,当我返回主屏幕时它仍然显示 'Logout' 而不是 '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;

非常感谢任何对此警告的帮助。

您的代码在最常见的用例中没有问题,但导航选项是在第一次渲染时构建的,因此每次带有道具更改的新渲染都不会更新它们。

您需要在 useEffect 挂钩上管理 navigation.setOption,因此每次更改 useEffect 依赖项时,您都会更新导航选项:

    React.useEffect(()=>{
        nav.setOptions({
            headerLeft: () => (
                <View>
                  {renderLoginLogout()}
                </View>
              ),
        })
    },[user])