React Native无法从其他组件的函数体内更新组件

时间:2020-09-30 11:35:07

标签: reactjs react-native react-redux react-navigation

如果我想检查用户是否登录,我会收到错误消息:无法从其他组件的函数主体内部更新组件。

我有组件:

App.js

import {View} from 'native-base';
import React, {Component} from 'react';
import {StyleSheet, SafeAreaView} from 'react-native';
import {Provider} from 'react-redux';
import {createStore, applyMiddleware} from 'redux';
import ReduxThunk from 'redux-thunk';
import reducers from './reducers';
import StartUpRouter from './routers/StartUpRouter';

class App extends Component {
    render() {
        const store = createStore(reducers, {}, applyMiddleware(ReduxThunk));
        return (
            <SafeAreaView style={styles.container}>
                <Provider store={store}>
                    <StartUpRouter />
                </Provider>
            </SafeAreaView>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
    },
});

export default App;

-----------------------------------路由器文件夹---------- --------------------------

StartUpRouter.js

import React, {Component, useEffect} from 'react';
import {View, Text, ActivityIndicator, StyleSheet} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';
import * as RootNavigation from '../RootNavigation';
import {loginUser} from '../actions';
import {connect} from 'react-redux';
import LoginRouter from './LoginRouter';

const Stack = createStackNavigator();

function HomeScreen() {
    return (
        <View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
            <Text>Home Screen</Text>
        </View>
    );
}

class StartUpRouter extends Component {
    componentDidMount() {
        this.props.loginUser();
    }

    render() {
        const {auth} = this.props;
        if (auth.loading) {
            return (
                <View style={styles.loading}>
                    <ActivityIndicator />
                </View>
            );
        } else {
            if (auth.isUserLogin) {
                return (
                    <NavigationContainer ref={RootNavigation.navigationRef}>
                        <Stack.Navigator
                            screenOptions={{
                                headerTitleStyle: {
                                    fontWeight: 'bold',
                                },
                            }}
                            initialRouteName="HomeScreen">
                            <Stack.Screen
                                name="HomeScreen"
                                component={HomeScreen}
                                options={{title: 'Please login'}}
                            />
                        </Stack.Navigator>
                    </NavigationContainer>
                );
            } else {
                return <LoginRouter {...this.props} />;
            }
        }
    }
}

const styles = StyleSheet.create({
    loading: {
        flex: 1,
        justifyContent: 'center',
    },
});

const mapStateToProps = (state) => {
    const {auth} = state;
    return {auth};
};

export default connect(mapStateToProps, {loginUser})(StartUpRouter);

LoginRouter.js

import React, {Component} from 'react';
import {Text, View, StyleSheet} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';
import * as RootNavigation from '../RootNavigation';
import DefaultLogin from '../components/LoginScreens/DefaultLogin';

const Stack = createStackNavigator();

class LoginRouter extends Component {
    render() {
        return (
            <NavigationContainer ref={RootNavigation.navigationRef}>
                <Stack.Navigator initialRouteName="DefaultLogin">
                    <Stack.Screen
                        name="DefaultLogin"
                        component={DefaultLogin}
                        options={{title: 'Please login'}}
                    />
                </Stack.Navigator>
            </NavigationContainer>
        );
    }
}

const styles = StyleSheet.create({});

export default LoginRouter;

----------------------------------- / Routers文件夹--------- ---------------------------

----------------------------------- Reducers文件夹---------- --------------------------

AuthReducer.js

import * as types from '../actions/types';
const INITIAL_STATE = {
    loginUser: null,
    isUserLogin: null,
    loading: false,
};

export default (state = INITIAL_STATE, action) => {
    switch (action.type) {
        case types.LOGIN_USER:
            return {...state, loading: true};
        case types.LOGIN_USER_SUCCESS:
            return {
                ...state,
                ...INITIAL_STATE,
                isUserLogin: true,
                loginUser: action.payload,
            };
        case types.LOGIN_USER_FAIL:
            return {
                ...state,
                loading: false,
                isUserLogin: false,
            };

        default:
            return state;
    }
};

index.js

import {combineReducers} from 'redux';
import AuthReducer from './AuthReducer';

export default combineReducers({
    auth: AuthReducer,
});

----------------------------------- / Reducers文件夹--------- ---------------------------

-----------------------------------动作文件夹---------- --------------------------

AuthActions.js

import * as types from './types';
import AsyncStorage from '@react-native-community/async-storage';
import * as RootNavigation from '../RootNavigation';

export const loginUser = () => async (dispatch) => {
    dispatch({type: types.LOGIN_USER});
    const loginUser = await AsyncStorage.getItem('loginUser');
    if (loginUser) {
        loginUserSuccess(dispatch, loginUser);
    } else {
        loginUserFail(dispatch);
    }
};

const loginUserFail = (dispatch) => {
    dispatch({type: types.LOGIN_USER_FAIL});
};

const loginUserSuccess = (dispatch, loginUser) => {
    dispatch({
        type: types.LOGIN_USER_SUCCESS,
        payload: loginUser,
    });
};

index.js

export * from './AuthActions';

types.js

export const LOGIN_USER = 'login_user';
export const LOGIN_USER_SUCCESS = 'login_user_success';
export const LOGIN_USER_FAIL = 'login_user_fail';

----------------------------------- / Actions文件夹--------- ---------------------------

-------------------------- componenst文件夹/普通文件夹---------------- ------------

index.js

export * from './SimpleHeader';

SimpleHeader.js

import React, {Component} from 'react';
import {Text, View, StyleSheet} from 'react-native';

class SimpleHeader extends Component {
    render() {
        const {headerStyle, headerTextStyle} = styles;
        const {headerText} = this.props;
        return (
            <View style={headerStyle}>
                <View>
                    <Text style={headerTextStyle}>{headerText}</Text>
                </View>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    headerStyle: {
        alignItems: 'center',
        flexDirection: 'row',
        justifyContent: 'flex-start',
    },
    headerTextStyle: {
        fontSize: 25,
        paddingLeft: 25,
    },
});

export {SimpleHeader};

-------------------------- / componenst_folder / common_folder ----------------- -----------

-------------------------- componenst_folder / LoginScreens_folder ------------------ ----------

DefaultLogin.js

import React, {Component} from 'react';
import {Text, View, StyleSheet, TouchableOpacity} from 'react-native';
import {SimpleHeader} from '../common';
import LinearGradient from 'react-native-linear-gradient';
import MaskedView from '@react-native-community/masked-view';
import Config from 'react-native-config';
import {Button} from 'native-base';

class DefaultLogin extends Component {
    render() {
        const {navigation} = this.props;
        {* if I comment navigation.setOption() function the error disappear *}
        navigation.setOptions({
            headerTitle: (props) => <SimpleHeader headerText="Login" />,
        });

        const {
            container,
            maskViewContainer,
            maskViewObj,
            linearGradientObj,
            qrButtonContainer,
            qrCodeText,
            button,
            buttonTextStyle,
            footerStyle,
            footerTextStyle,
        } = styles;

        return (
            <View style={container}>
                <View style={maskViewContainer}>
                    <MaskedView
                        style={maskViewObj}
                        maskElement={
                            <View>
                            </View>
                        }>
                        <LinearGradient
                            colors={['#00A8B0', '#78BE20']}
                            style={linearGradientObj}
                            start={{x: 0, y: 0}}
                            end={{x: 1, y: 0}}
                        />
                    </MaskedView>
                </View>
                <View style={qrButtonContainer}>
                    <Text style={qrCodeText}>
                        For login please scan QR CODE
                    </Text>
                    <Button
                        block
                        onPress={() =>
                            this.props.navigation.navigate('barcodeLogin')
                        }
                        style={button}>
                        <Text style={buttonTextStyle}>Login by QR CODE</Text>
                    </Button>
                </View>
                <View style={footerStyle}>
                    <Text style={footerTextStyle}>
                        Use username and password
                    </Text>
                </View>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        marginTop: 50,
    },
    maskViewObj: {
        flex: 1,
        flexDirection: 'row',
    },
    linearGradientObj: {
        flex: 1,
    },
    maskViewContainer: {
        alignItems: 'center',
        height: 250,
    },
    qrButtonContainer: {
        height: 100,
        flex: 1,
        width: null,
        marginLeft: 10,
        marginRight: 10,
        marginTop: 60,
        alignItems: 'center',
    },
    qrCodeText: {
        color: '#525F6B',
        fontSize: 14,
        marginBottom: 10,
    },
    button: {
        backgroundColor: '#005691',
        borderRadius: 0,
    },
    buttonTextStyle: {
        color: '#FFFFFF',
        fontSize: 22,
        marginLeft: 30,
        marginRight: 30,
    },
    footerStyle: {
        height: 30,
        flex: 1,
        width: null,
        justifyContent: 'flex-end',
        alignItems: 'center',
        marginBottom: 30,
    },
    footerTextStyle: {
        color: '#005691',
    },
});

export default DefaultLogin;

-------------------------- / componenst_folder / LoginScreens_folder ----------------- -----------

基本上这是Entyre应用程序。

如果我评论功能

navigation.setOptions({
            headerTitle: (props) => <SimpleHeader headerText="Login" />,
        });

错误:无法从其他组件的功能主体内部更新组件消失

从控制台登录:

Warning: Cannot update a component from inside the function body of a different component.
    in DefaultLogin (at SceneView.tsx:122)
    in StaticContainer
    in StaticContainer (at SceneView.tsx:115)
    in EnsureSingleNavigator (at SceneView.tsx:114)
    in SceneView (at useDescriptors.tsx:150)
    in RCTView (at View.js:34)
    in View (at CardContainer.tsx:221)
    in RCTView (at View.js:34)
    in View (at CardContainer.tsx:220)
    in RCTView (at View.js:34)
    in View (at CardSheet.tsx:33)
    in ForwardRef(CardSheet) (at Card.tsx:563)
    in RCTView (at View.js:34)
    in View (at createAnimatedComponent.js:165)
    in AnimatedComponent (at createAnimatedComponent.js:215)
    in ForwardRef(AnimatedComponentWrapper) (at Card.tsx:545)
    in PanGestureHandler (at GestureHandlerNative.tsx:13)
    in PanGestureHandler (at Card.tsx:539)
    in RCTView (at View.js:34)
    in View (at createAnimatedComponent.js:165)
    in AnimatedComponent (at createAnimatedComponent.js:215)
    in ForwardRef(AnimatedComponentWrapper) (at Card.tsx:535)
    in RCTView (at View.js:34)
    in View (at Card.tsx:529)
    in Card (at CardContainer.tsx:189)
    in CardContainer (at CardStack.tsx:558)
    in RCTView (at View.js:34)
    in View (at Screens.tsx:69)
    in MaybeScreen (at CardStack.tsx:551)
    in RCTView (at View.js:34)
    in View (at Screens.tsx:48)
    in MaybeScreenContainer (at CardStack.tsx:461)
    in CardStack (at StackView.tsx:458)
    in KeyboardManager (at StackView.tsx:456)
    in RNCSafeAreaProvider (at SafeAreaContext.tsx:74)
    in SafeAreaProvider (at SafeAreaProviderCompat.tsx:42)
    in SafeAreaProviderCompat (at StackView.tsx:453)
    in RCTView (at View.js:34)
    in View (at StackView.tsx:452)
    in StackView (at createStackNavigator.tsx:84)
    in StackNavigator (at LoginRouter.js:14)
    in EnsureSingleNavigator (at BaseNavigationContainer.tsx:390)
    in ForwardRef(BaseNavigationContainer) (at NavigationContainer.tsx:91)
    in ThemeProvider (at NavigationContainer.tsx:90)
    in ForwardRef(NavigationContainer) (at LoginRouter.js:13)
    in LoginRouter (at StartUpRouter.js:61)
    in StartUpRouter (created by ConnectFunction)
    in ConnectFunction (at App.js:16)
    in Provider (at App.js:15)
    in RCTSafeAreaView (at SafeAreaView.js:51)
    in ForwardRef(SafeAreaView) (at App.js:14)
    in App (at renderApplication.js:45)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:106)
    in RCTView (at View.js:34)
    in View (at AppContainer.js:132)
    in AppContainer (at renderApplication.js:39)

有人可以帮忙吗?

谢谢

4 个答案:

答案 0 :(得分:1)

componentDid 对我有用。 使用无状态组件:


const DefaultLogin = ({ navigation }) => {
  useEffect(() => {
    navigation.setOptions({
      headerTitle: () => <SimpleHeader headerText="Choose login" />,
      headerBackground: () => <BoschBottomStrip />,
    });
  }, [navigation]);

  return (
    // ...
  )
}

答案 1 :(得分:0)

我找到了解决方案,但不满意,因为我无法从组件DefaultLogin.js中更新/设置组件SimpleHeader.js,而只能从父组件LoginRouter.js中更新/设置

我更改了:

DefaultLogin.js(已删除功能Navigation.setOption({....}))

import React, {Component} from 'react';
import {Text, View, StyleSheet, TouchableOpacity} from 'react-native';
import {SimpleHeader} from '../common';
import LinearGradient from 'react-native-linear-gradient';
import MaskedView from '@react-native-community/masked-view';
import Config from 'react-native-config';
import {Button} from 'native-base';

class DefaultLogin extends Component {
    render() {
        const {navigation} = this.props;
        {* if I comment navigation.setOption() function the error disappear *}
        navigation.setOptions({
            headerTitle: (props) => <SimpleHeader headerText="Login" />,
        });

        const {
            container,
            maskViewContainer,
            maskViewObj,
            linearGradientObj,
            qrButtonContainer,
            qrCodeText,
            button,
            buttonTextStyle,
            footerStyle,
            footerTextStyle,
        } = styles;

        return (
            <View style={container}>
                <View style={maskViewContainer}>
                    <MaskedView
                        style={maskViewObj}
                        maskElement={
                            <View>
                            </View>
                        }>
                        <LinearGradient
                            colors={['#00A8B0', '#78BE20']}
                            style={linearGradientObj}
                            start={{x: 0, y: 0}}
                            end={{x: 1, y: 0}}
                        />
                    </MaskedView>
                </View>
                <View style={qrButtonContainer}>
                    <Text style={qrCodeText}>
                        For login please scan QR CODE
                    </Text>
                    <Button
                        block
                        onPress={() =>
                            this.props.navigation.navigate('barcodeLogin')
                        }
                        style={button}>
                        <Text style={buttonTextStyle}>Login by QR CODE</Text>
                    </Button>
                </View>
                <View style={footerStyle}>
                    <Text style={footerTextStyle}>
                        Use username and password
                    </Text>
                </View>
            </View>
        );
    }
}
.
.
.

收件人:

class DefaultLogin extends Component {
    render() {
        const {
            container,
            maskViewContainer,
            maskViewObj,
            linearGradientObj,
            qrButtonContainer,
            qrCodeText,
            button,
            buttonTextStyle,
            footerStyle,
            footerTextStyle,
        } = styles;

        return (
            <View style={container}>
                <View style={maskViewContainer}>
                    <MaskedView
                        style={maskViewObj}
                        maskElement={
                            <View>
                            </View>
                        }>
                        <LinearGradient
                            colors={['#00A8B0', '#78BE20']}
                            style={linearGradientObj}
                            start={{x: 0, y: 0}}
                            end={{x: 1, y: 0}}
                        />
                    </MaskedView>
                </View>
                <View style={qrButtonContainer}>
                    <Text style={qrCodeText}>
                        For login scan QR CODE
                    </Text>
                    <Button
                        block
                        onPress={() =>
                            this.props.navigation.navigate('barcodeLogin')
                        }
                        style={button}>
                        <Text style={buttonTextStyle}>Login by QR CODE</Text>
                    </Button>
                </View>
                <View style={footerStyle}>
                        <Text style={footerTextStyle}>
                            Use username and password
                        </Text>
                </View>
            </View>
        );
    }
}
.
.
.

然后我从父组件LoginRouter.js设置标头

import React, {Component} from 'react';
import {Text, View, StyleSheet} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';
import * as RootNavigation from '../RootNavigation';
import DefaultLogin from '../components/LoginScreens/DefaultLogin';
import {SimpleHeader} from '../components/common';

const Stack = createStackNavigator();

class LoginRouter extends Component {
    render() {
        return (
            <NavigationContainer ref={RootNavigation.navigationRef}>
                <Stack.Navigator initialRouteName="DefaultLogin">
                    <Stack.Screen
                        name="DefaultLogin"
                        component={DefaultLogin}
                        options={{
                            headerTitle: (props) => (
                                <SimpleHeader headerText="Login" />
                            ),
                        }}
                    />
                </Stack.Navigator>
            </NavigationContainer>
        );
    }
}

const styles = StyleSheet.create({});

export default LoginRouter;

为什么我不能在组件DefaultLogin.js内使用功能Navigation.setOptions({...})来设置标头,而只能从父组件LoginRouter.js来设置标头?

感谢大家的回答

答案 2 :(得分:0)

最后我找到了解决方法:)这很简单:)

我通过componentDidUpdate(){}包装了navigation.setOption({...})

DefaultLogin.js

class DefaultLogin extends Component {
    componentDidMount() {
        const {navigation} = this.props;
        navigation.setOptions({
            headerTitle: () => <SimpleHeader headerText="Choose login" />,
            headerBackground: () => <BoschBottomStrip />,
        });
    }

    render() {
        const {
            container,
            maskViewContainer,
.
.
.
.

现在我可以从使用SimpleHeader的组件中设置标头。

答案 3 :(得分:0)

另一种解决方案是不在子屏幕内调用navigation.setOptions,而是将其移动到父组件屏幕的options中。

所以以前我们有:

父母:

<NavigationContainer>
  <Stack.Navigator>
    <Stack.Screen
      name="Root"
      component={BottomTabNavigator}
    />
  </Stack.Navigator>
</NavigationContainer>

和孩子(BottomTabNavigator.jsx):

const BottomTabNavigator = ({ navigation }) => {
  navigation.setOptions({
    headerShown: false,
  });
}

现在,我们可以删除子项中的setOptions调用,并将其放入父项中:

<NavigationContainer>
  <Stack.Navigator>
    <Stack.Screen
      name="Root"
      component={BottomTabNavigator}
      options={{
        headerShown: false,
      }}
    />
  </Stack.Navigator>
</NavigationContainer>