Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React-Navigation - Header interaction with its screen component, Failed prop type

I am following the React-Navigation tutorial, and got stuck on the section titled Header interaction with its screen component. The code in the tutorial work fine in the emulator provided at snack, but I discovered that when running locally I encountered the following error:

Warning: Failed prop type: The prop 'onPress' is marked as required in 'Button', but its value is 'undefined'.

I managed to get the code working on my local machine using expo-cli by changing the onPress event assignment in navigationOptions as follows (my snack here):

<Button
     onPress={()=>{navigation.getParam('increaseCount')()}}
   //onPress={navigation.getParam('increaseCount')} - as in tutorial
     title="+1"
     color={Platform.OS === 'ios' ? '#fff' : null}
/>

I am hoping someone might have some insight into why this is so. I checked and I am using the same version of Expo (v.32.0) locally.

App.js listing:

import React from 'react';
import { Button, Image, Platform, View, Text } from 'react-native';
import { createStackNavigator, createAppContainer } from 'react-navigation';

class LogoTitle extends React.Component {
  render() {
    return (
      <Image
        source={require('./spiro.png')}
        style={{ width: 30, height: 30 }}
      />
    );
  }
}

class HomeScreen extends React.Component {
  static navigationOptions = ({ navigation }) => {
    return {
      headerTitle: <LogoTitle />,
      headerRight: (
        <Button
          onPress={()=>{navigation.getParam('increaseCount')()}}
          //onPress={navigation.getParam('increaseCount')}
          title="+1"
          color={Platform.OS === 'ios' ? '#fff' : null}
        />
      ),
    };
  };

  componentWillMount() {
    this.props.navigation.setParams({ increaseCount: this._increaseCount });
  }

  state = {
    count: 0,
  };

  _increaseCount = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Home Screen</Text>
        <Text>Count: {this.state.count}</Text>
        <Button
          title="Go to Details"
          onPress={() => {
            /* 1. Navigate to the Details route with params */
            this.props.navigation.navigate('Details', {
              itemId: 86,
              otherParam: 'First Details',
            });
          }}
        />
      </View>
    );
  }
}

class DetailsScreen extends React.Component {
  static navigationOptions = ({ navigation, navigationOptions }) => {
    const { params } = navigation.state;

    return {
      title: params ? params.otherParam : 'A Nested Details Screen',
      /* These values are used instead of the shared configuration! */
      headerStyle: {
        backgroundColor: navigationOptions.headerTintColor,
      },
      headerTintColor: navigationOptions.headerStyle.backgroundColor,
    };
  };

  render() {
    /* 2. Read the params from the navigation state */
    const { params } = this.props.navigation.state;
    const itemId = params ? params.itemId : null;
    const otherParam = params ? params.otherParam : null;

    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Details Screen</Text>
        <Text>itemId: {JSON.stringify(itemId)}</Text>
        <Text>otherParam: {JSON.stringify(otherParam)}</Text>
        <Button
          title="Update the title"
          onPress={() =>
            this.props.navigation.setParams({ otherParam: 'Updated!' })
          }
        />
        <Button
          title="Go to Details... again"
          onPress={() => this.props.navigation.navigate('Details')}
        />
        <Button
          title="Go back"
          onPress={() => this.props.navigation.goBack()}
        />
      </View>
    );
  }
}

const RootStack = createStackNavigator(
  {
    Home: {
      screen: HomeScreen,
    },
    Details: {
      screen: DetailsScreen,
    },
  },
  {
    initialRouteName: 'Home',
    defaultNavigationOptions: {
      headerStyle: {
        backgroundColor: '#f4511e',
      },
      headerTintColor: '#fff',
      headerTitleStyle: {
        fontWeight: 'bold',
      },
    },
  }
);

const AppContainer = createAppContainer(RootStack);

export default class App extends React.Component {
  render() {
    return <AppContainer />;
  }
}
like image 421
Inda Vidjool Avatar asked Feb 19 '19 01:02

Inda Vidjool


People also ask

Why react navigation is not working?

This might happen if you have an old version of the metro-react-native-babel-preset package. Try upgrading it to the latest version. If you have @babel/core installed, also upgrade it to latest version.

How do I set a header in react?

Approach: First we will create a basic react app using some installations. We will make our new Header Component with some styling using Material-UI. To create a Header, we will use App Bar from Material UI which will provide screen titles, navigation, and actions.

How do I get routeName in react navigation?

You can import the function getCurrentRouteName and use this to get the current route name and its working in any nested navigators in React Navigation 5.


1 Answers

My guess is that this is not a fatal error, just a warning.

It will happen in any case. React Navigation docs state:

React Navigation doesn't guarantee that your screen component will be mounted before the header. Because the increaseCount param is set in componentDidMount, we may not have it available to us in navigationOptions. This usually will not be a problem because onPress for Button and Touchable components will do nothing if the callback is null. If you have your own custom component here, you should make sure it behaves as expected with null for its press handler prop.

So, navigationOptions function will be called twice:

  • First time before componentDidMount. Here, getParam will return undefined.
  • Second time after componentDidMount.

What Button is complaining about, is the first time. It does not like onPress set to undefined.

You can check this with console.log from navigationOptions:

class HomeScreen extends React.Component {
  static navigationOptions = ({ navigation }) => {
    console.log(navigation.getParam('increaseCount'))
    return {
      headerTitle: <LogoTitle />,
      headerRight: (
        <Button
          onPress={()=>{navigation.getParam('increaseCount')()}}
          //onPress={navigation.getParam('increaseCount')}
          title="+1"
          color={Platform.OS === 'ios' ? '#fff' : null}
        />
      ),
    };
  };

In my opinion, your code is correct, while the code from the docs simply ignores this issue.

like image 180
Nikola Mihajlović Avatar answered Oct 25 '22 01:10

Nikola Mihajlović