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

问题描述

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

这是我无法解决的警告:

警告:无法从不同组件的函数体内部更新组件。 at [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 at [native code]:null in performSyncWorkOnRoot 在 [native code]:null in forEach 在 node_modules/react-native/Libraries/Core/setUpReactRefresh.js:43:6 在 Refresh.performReactRefresh at [native code]:null in callFunctionReturnFlushedQueue

这是我的 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',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',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;

如果用户登录,我会将他们带到注销屏幕要求确认,当我返回主屏幕时,它仍然显示“注销”而不是“登录

import React,{ useState } from 'react';
import { Text,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;

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

解决方法

您的代码在最常见的用例中是没问题的,但导航选项是建立在第一次渲染上的,所以每次新渲染的 props 更改都不会更新它们。

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

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