DEV Community

Kay Gosho
Kay Gosho

Posted on • Updated on

Handle Stack Navigator's event in current screen with React Navigation

Overview

When we create our app using React Native + React Navigation, we often want to place buttons like "Save" in Navigation header.

image.png

In this situation, we may want to handle the press event in Screen Component, not in Navigation Action.
This is because React Navigation should focus on handling navigation so events and logic on the screen should be handled by the screen Component.

The official document says define NavigationActions and set Route parameters is a good way, but if we are in this way it obviously cause spaghetti code.

https://reactnavigation.org/docs/navigators/navigation-actions

We capsule data in the screen component, we would like to put the logic in it.

Some people think the same thing:

https://github.com/react-navigation/react-navigation/issues/145

Often times if you are editing or creating something, one of the header buttons will be 'Save'. I don't see how this is feasible with this library since there is no access to the state or props of the current screen component. The only (ugly) solution would be to keep the data to be saved outside of the component. Anyone have any suggestions how this pattern can be achieved with this library?

Solution

In the above issue, I found the following code works well.

import React from 'react'
import {
  View,
  Button,
} from 'react-native'

class MyScreen extends React.Component {
  static navigationOptions = ({ navigation }) => {
    const { state } = navigation
    return {
      headerTitle: 'New Task',
      headerRight: <Button title="Save" onPress={() => state.params.handleSave()} />,
    }
  }

  componentDidMount() {
    this.props.navigation.setParams({ handleSave: () => this.saveDetails() })
  }

  saveDetails() {
    alert('saved')
  }

  render() {
    return (
      <View />
    )
  }
}
Enter fullscreen mode Exit fullscreen mode

If we use Flow, we can annotate the type NavigationNavigator.

import { NavigationNavigator } from 'react-navigation'

  static navigationOptions = ({ navigation }: NavigationNavigator) => {
    return {
      headerTitle: navigation.state.params.intern.title
    }
  }
Enter fullscreen mode Exit fullscreen mode

Description

In the official docs, we should define NavigationOptions when creating the instance of StackNavigator,

import { StackNavigator } from 'react-navigation'
import HomeScreen from './components/HomeScreen'
import NewScreen from './components/NewScreen'

const navigator = StackNavigator({
  Home: {
    screen: HomeScreen,
    navigationOptions: ({ navigation }) => {
      const { navigate } = navigation
      return {
        headerTitle: 'Home',
        headerRight: <Button title="New" onPress={() => navigate('NewScreen')} />,
      }
    },
  },
Enter fullscreen mode Exit fullscreen mode

In fact, we can also define static navigationOptions: NavigationNavigator => void in screen Component.

import React from 'react'
import { Button } from 'react-native'

class HomeScreen extends React.Component {
  static navigationOptions = ({ navigation }) => {
    const { state } = navigation
    return {
      headerTitle: 'New Task',
      headerRight: <Button title="Save" onPress={() => state.params.handleSave()} />,
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

navigationOptions is a static function, so we should pass the action to navigation state.

The demerit is we should write almost useless code just to define navigation action.

Conclusion

If you are suffering from React Navigation's navigation header, you should try to create your own header.
Its navigation header is easy to use lightly, but as your app grows it is hard to customize header.

In my project, React Navigation was always my concern.
Do not rely highly on React Navigation, and you can do well with your own logic.

Top comments (0)