React Native - BackHandler with Alert

Viewed 6993

I'm attempting to override the back button on a screen with a prompt to Logout. See the following:

import React, { Component } from "react";
import { Alert, BackHandler } from "react-native";

export default class Dashboard extends Component {

    constructor(props) {
        super(props);
    }

    componentDidMount() {

        BackHandler.addEventListener("hardwareBackPress",this.handleBackPress);
    }

    componentWillUnmount() {
        BackHandler.removeEventListener("hardwareBackPress", this.handleBackPress);
    }

    handleBackPress() {
        Alert.alert(
            "Logout",
            "Are you sure you want to logout?",
            [
                {
                    text: "Cancel",
                    onPress: () => {
                        console.log("Cancel Pressed");
                    },
                    style: "cancel"
                },
                { text: "Logout", onPress: () => this.handleLogout() }
            ],
            { cancelable: false }
        );
    }

    handleLogout() {
        this.props.navigation.navigate("Login");
    }
}

As you can see, on mounting change, I'm binding and unbinding "hardwareBackPress" to this.handleBackPress. Note, I have to use .bind(this), otherwise I get

_this2.handleLogout is not a function

when I press the Logout in the Alert. Expected functionality is:

  • Back button is pressed
  • Functionality disabled (doesn't navigate)
  • Alert is displayed
  • Ok is pressed
  • Navigates back
  • Back button on previous screen has default action

But what actually happens is:

  • Back button is pressed
  • Navigates back
  • Alert is displayed
  • (Subsequent steps don't matter)

I noticed I don't have return true; anywhere in my handleBackPress(), so I added that:

handleBackPress() {
    Alert.alert(
        "Logout",
        "Are you sure you want to logout?",
        [
            {
                text: "Cancel",
                onPress: () => {
                    console.log("Cancel Pressed");
                },
                style: "cancel"
            },
            {
                text: "Logout",
                onPress: () => {
                    return this.handleLogout();
                }
            }
        ],
        { cancelable: false }
    );

    return true;
}

But what happens now is:

  • Back button is pressed
  • Functionality disabled (doesn't navigate)
  • Alert is displayed
  • Ok is pressed
  • Navigates back
  • Back button press on previous screen results in Alert being displayed

I have verified that componentDidUnmount() is called, but it doesn't seem to have removed the event listener.

Has anyone encountered this issue before? For now, I've just resorted to globally disabling the back button by adding this handler at the entry point of the app, but that's not a long-term solution.

Edit: I noticed there's a lifecycle alternative, so I tried implementing that:

componentDidMount() {
    this.backHandler = BackHandler.addEventListener("hardwareBackPress", () => {
        Alert.alert("Logout", "Are you sure you want to logout?", [{ text: "Cancel", onPress: () => {}, style: "cancel" }, { text: "Logout", onPress: () => this.handleLogout() }], { cancelable: false });
        return true;
    });
}

componentWillUnmount() {
    this.backHandler.remove();
}

And while this makes it work (somehow), it has yet another side-effect. As soon as I navigate forward (which doesn't trigger componentDidUnmount(), due to stacked navigation), and navigate back, the back button behaves as such:

  • Back button is pressed
  • Functionality disabled (doesn't navigate)
  • Alert doesn't show up

The screen I'm navigating forward to has it's back button overridden, and seems to not play well with this alternative lifecycle. Will attempt to implement different approach on all subsequent screens; see if it behaves properly then.

1 Answers

Using the lifecycle alternative from the Documentation (https://facebook.github.io/react-native/docs/backhandler) seems to handle odd behaviour with Alert and return true;:

Dashboard.js

componentDidMount() {
    this.backHandler = BackHandler.addEventListener("hardwareBackPress", () => {
        Alert.alert("Logout", "Are you sure you want to logout?", [{ text: "Cancel", onPress: () => {}, style: "cancel" }, { text: "Logout", onPress: () => this.handleLogout() }], { cancelable: false });
        return true;
    });
}

componentWillUnmount() {
    this.backHandler.remove();
}

handleLogout() {
    global.screenName = "Dashboard";
    return this.props.navigation.navigate("Login");
}

As long as all subsequent screens that need the back button overridden also use the same logic:

Detail.js (subsequent screen in Stack navigator):

componentDidMount() {
    this.backHandler = BackHandler.addEventListener("hardwareBackPress", () => {
        return this.props.navigation.navigate("Dashboard");
    });
}

componentWillUnmount() {
    this.backHandler.remove();
}
Related