From 44705fe11bd9bb12c8f71d1e50a7b48e0af6a38d Mon Sep 17 00:00:00 2001 From: BleemIs42 Date: Fri, 27 Dec 2024 04:37:57 -0800 Subject: [PATCH] - Fix BackHandle callback undefined cause crash issue (#48388) Summary: ### Error message: we got an error ``` t[n] is not a function. (In 't[n]()', 't[n]' is undefined) \n (index.bundle:317:168:317) ``` it related the BackHandle execute handle function. ### Investigation result our project has screen files`App.tsx`, `Dashboard.tsx`, and `Profile.tsx`. When launching the app, the screen order is `App.tsx` -> `Dashboard.tsx`, then user can switch to `Profile.tsx` For `App.tsx` and `Dashboard.tsx`, we just prevent the hardware button action use `usePreventHardwareBackPressEffect()` in the first line of screen code. ```js export const useHardwareBackPressEffect = (goBack?: () => boolean): void => { useEffect(() => { if (goBack) { BackHandler.addEventListener("hardwareBackPress", goBack); return () => { BackHandler.removeEventListener("hardwareBackPress", goBack); }; } return undefined; }, [ goBack ]); }; export const usePreventHardwareBackPressEffect = (): void => useHardwareBackPressEffect(() => true); ``` currently, `_backPressSubscriptions ` has 2 callback functions. then user switch to `Profile.tsx` screen, and has the below code for hardwareback button and the second doesn't `return true`: ```js // first one usePreventHardwareBackPressEffect(); ... // second one useEffect(() => { const backButtonListener = BackHandler.addEventListener( "hardwareBackPress", () => navigate(Navigation.Login); ); return () => backButtonListener.remove(); }); ``` currently, `_backPressSubscriptions ` has 4 callback functions, include previous 2 and new 2 of `Profile.tsx`. When the user press hardwareback button, it will navigate to the login screen, so the issue occurs: the latest callback will be executed first, then the navigation will let the screen unmount, which will destroy the effect, so the code removing 2 hardwareBackPress callback of `Profile.tsx` by executed ```js return () => { BackHandler.removeEventListener("hardwareBackPress", goBack); }; ``` After the navigation ends and the loop is restored, the init `i` is 3, then `i--`, `i` is 2, then `_backPressSubscriptions[2]` is `undefined` now and executes as a function, so the app crashes. ```js for (let i = _backPressSubscriptions.length - 1; i >= 0; i--) { if (_backPressSubscriptions[i]()) { return; } } ``` that's the issue I met. ## Changelog: [ANDROID] [FIXED] - Fix BackHandle callback undefined cause crash issue Pull Request resolved: https://github.com/facebook/react-native/pull/48388 Reviewed By: blakef Differential Revision: D67648077 Pulled By: rshest fbshipit-source-id: 5ca685b0c0c474ef11772fd803743968ec2d912e --- .../react-native/Libraries/Utilities/BackHandler.android.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/Libraries/Utilities/BackHandler.android.js b/packages/react-native/Libraries/Utilities/BackHandler.android.js index 6105a5341de..fedc3aeb4a3 100644 --- a/packages/react-native/Libraries/Utilities/BackHandler.android.js +++ b/packages/react-native/Libraries/Utilities/BackHandler.android.js @@ -19,7 +19,7 @@ const _backPressSubscriptions = []; RCTDeviceEventEmitter.addListener(DEVICE_BACK_EVENT, function () { for (let i = _backPressSubscriptions.length - 1; i >= 0; i--) { - if (_backPressSubscriptions[i]()) { + if (_backPressSubscriptions[i] && _backPressSubscriptions[i]()) { return; } }