Search code examples
react-nativereact-navigationaws-amplify

AWS Amplify Auth / react-navigation: How to access auth state?


In my React Native mobile application, I would like to authenticate my users using AWS Amplify Authentication and manage navigation between screens using React Navigation. I tried to implement everything "by the book", i.e. as per the docs and my App conceptually looks like this:

// import statements skipped

class FirstTabScreen extends React.Component {
  render = () => {
    {/* access some data passed in screenProps */}
    const someData = this.props.screenProps.someData;
    {/* TODO: how to access AWS Amplify auth data here ?!? */}
    {/* this.props contains screenProps and navigation objects */}
    console.log("this.props: ", this.props);
    {/* this.props.authState is undefined */}
    console.log("this.props.authState: ", this.props.authState);
    return <View><Text>First Tab - some data: {someData}</Text></View>
  };
}

class SecondTabScreen extends React.Component {
  render = () => {
    const otherData = this.props.screenProps.otherData;
    return <View><Text>Second Tab - other data: {otherData}</Text></View>
  };
}

const AppNavigator = createBottomTabNavigator({
  FirstTab: { screen: FirstTabScreen },
  SecondTab: { screen: SecondTabScreen },
});

const AppContainer = createAppContainer(AppNavigator);

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      someData: null,
      otherData: null,
    };
  }

  componentDidMount = () => {
    this.setState({ someData: 'some data', otherData: 'other data' });
  };

  render = () => {
    return (
      <Authenticator>
        <AppContainer
          screenProps={{
            someData: this.state.someData,
            otherData: this.state.otherData,
          }}
        />
      </Authenticator>
    );
  };
}

export default App;

The code sample above skips a few important details; I have created a full Expo Snack.

The AWS Amplify Authenticator wraps the application and makes authentication data available to embedded components as e.g. this.props.authState; see docs.

My problem now is that I don't understand how to access this auth data in e.g. FirstTabScreen: In that component, this.props contains screenProps and navigation objects, but this.props.authState is undefined. With AppContainer and AppNavigator in between Authenticator and e.g. FirstTabScreen, I don't see how to pass auth data as props or transport them by some other means.

Furthermore, I just pass all data to all embedded components in screenProps; this does work, but not all screens require all data. How would I go about passing them only the data they actually require ? I know about passing parameters to routes, but all the code samples in there have a Button and use this.props.navigation.navigate(...) in the onPress handler. I don't have any buttons on those screens and don't need any - after all, navigating to a different tab is what the tabs in the bottom tab nav bar are for ?!?

Could anyone kindly shed some light on this for me ?


Solution

  • using screeProp is not encouraged on react navigation v3

    I believe it's no longer encouraged to put navigation such as TabNavigator nested within a component. I'm not quite sure how to handle such cases gracefully in V3. I wanted to upgrade from V1 to V3 recently and gave up on the upgrade since I couldn't find enough info on what to do with the nested navigators I have.

    another idea from esipavicius

    do not wrap screens to stack navigator which include the tab navigator. Maybe you Can use navigationAction setState or setParams with params and navigator key. When You Can dispatch it. And automaticaly changes state with params to which scren you dispatched.

    OPTION 1 - USING SCREEN PROP

    Your option of passing screen props

    class App extends React.Component {
    
      componentDidMount = () => {
        this.setState({ someData: 'some data', otherData: 'other data' });
      };
    
      render = () => {
        return (
          <Authenticator>
            <AppContainer
              screenProps={{ myProp: "test" }}
            />
          </Authenticator>
        );
      };
    }
    

    OPTION 2 - USING THE REACT CONTEXT API

    Using the Context api and wrapping it around your <AppContainer>.

    class App extends React.Component {
      render = () => {
        return (
          <FormContext.Provider value={this.state}>
            <AppContainer />
          </FormContext.Provider>
        );
      };
    }
    

    Then consume it in each one of your screens.

    class FirstTabScreen extends React.Component {
      render(){
        return(
          <FormContext.Consumer>
            { (context) => ( 
                 // your logic 
            )}
          </FormContext.Consumer>
      };
    }
    

    Option 3 - TAB NAVIGATOR OBJECT

    You can pass a TabNavigatorObject

    createBottomTabNavigator(RouteConfigs, BottomTabNavigatorConfig);
    

    You can pass an extra option to the BottomTabNavigator in the BottomTabNavigatorConfig.

    const BottomTabNavigator = StackNavigator(routesConfig, BottomTabNavigatorConfig)
    

    The BottomTabNavigatorConfig is an option and you can check the api docs

    { initialRouteName: 'Home', navigationOptions: .... }
    

    Option 4 - RETRIEVE PARAM FROM PARENT

    as explained on github and in the api docs, you can retrieve the parent component and then use getParam.

    const parent = navigation.dangerouslyGetParent();
    const yourParam = parent.getParam('yourParamName', 'default value')
    

    You can find many discussion about passing props with tab navigator on github