Show Card Overlay in React-Native - react-native

I want when the user is not verified to show an overlay at the Top of the screen that the user can click on close on verify button to get rid of it.
Also, I want it to show permanently above all screens until the user dismiss it.
How can I do that functionality ?

You can Alert in every screen. You can do it on your navigation folder by using <NavigationContainer> . I think for navigation , you already used it. So, add something on onReady and onStateChange of NavigationContainer.
Customise the Alert as your expectations. You can do the desired task inside onPress of Alert. In described code, the notVerify value is what you have set to check the user verification.
A demo is :
const routeNameRef = useRef<string | undefined>('');
const navigationRef = useNavigationContainerRef();
<NavigationContainer
ref={navigationRef}
onReady={() => {
routeNameRef.current = navigationRef.current?.getCurrentRoute()?.name;
if(notVerify){
Alert.alert(
"Alert Title",
"My Alert Msg",
[
{
text: "Cancel",
onPress: () => console.log("Cancel Pressed"),
style: "cancel"
},
{ text: "OK", onPress: () => console.log("OK Pressed") }
]
);
}
}}
onStateChange={async () => {
const previousRouteName = routeNameRef.current;
const currentRouteName = navigationRef.current?.getCurrentRoute()?.name;
if (previousRouteName !== currentRouteName) {
if(notVerify){
Alert.alert(
"Alert Title",
"My Alert Msg",
[
{
text: "Cancel",
onPress: () => console.log("Cancel Pressed"),
style: "cancel"
},
{ text: "OK", onPress: () => console.log("OK Pressed") }
]
);
}
}
routeNameRef.current = currentRouteName;
}}
>
<RootNavigator /> //This is the screens section(Stack.screen function) of your app. I did it inside a function name RootNavigator, so I passed this inside this.
</NavigationContainer>
);

Related

BackHandler's HardwareBackPress event gets triggered on every screen of the navigator

I'm using BackHandler on the "home" screen of my app to alert confirmation to the user to exit the app. I've 2 sets of screen Authentication and Home on my root navigator and isLogged bool determines which set gets shown.
Problem: The first render of the app works fine(be it Auth or Home set of screens) but when isLogged is changed and the set of screens changes, BackHandler starts triggering on every screen of the changed set. This is only fixed after restarting the app. Working example - https://snack.expo.dev/#msaxena92/11fd51
Expected result: Pressing back inside a navigator should take you to the initialRoute or first screen of the navigator and only after that when there are no more screens in the navigation stack it exits the app.
You have 2 options:
Use the useFocusEffect hook instead of useEffect which will make sure that the effect is run only when you're on this screen:
useFocusEffect(
React.useCallback(() => {
const backAction = () => {
Alert.alert("Hold on!", "Are you sure you want to exit?", [
{ text: "Cancel" },
{ text: "Yes", onPress: () => BackHandler.exitApp() }
]);
return true;
};
const backHandler = BackHandler.addEventListener(
"hardwareBackPress",
backAction
);
return () => backHandler.remove();
}, [])
);
Alternatively, you can also check for focus inside the effect:
React.useEffect(() => {
const backAction = () => {
if (!navigation.isFocused()) {
return false;
}
Alert.alert("Hold on!", "Are you sure you want to exit?", [
{ text: "Cancel" },
{ text: "Yes", onPress: () => BackHandler.exitApp() }
]);
return true;
};
const backHandler = BackHandler.addEventListener(
"hardwareBackPress",
backAction
);
return () => backHandler.remove();
}, [navigation]);
Also see https://reactnavigation.org/docs/custom-android-back-button-handling/

How to detect user leaving screen in React Native and act accordingly

How to detect user leaving a screen in React Native and act accordingly ?
For an example when user tries to leave current screen alert should popup and say You have unsaved changes, are you sure you want to leave?. If yes user can leave the screen, if no user should be in same screen.
import { useFocusEffect } from '#react-navigation/native';
import {Alert} from 'react-native'
const Profile = (props) => {
useFocusEffect(
React.useCallback(() => {
// Do something when the screen is focused
return () => {
// Do something when the screen is unfocused
// Useful for cleanup functions
Alert.alert(
'Want to leave',
'You have unsaved changes, are you sure you want to leave?',
[
{
text: 'yes',
onPress: () => {
// should leave to the screen user has navigate
},
},
{ text: 'no', onPress: () => null },
],
true
)
};
}, [])
);
return <ProfileContent />;
}
export default Profile
Currently I'm facing few problems with this code.
Alert will popup once user already navigate to selected navigation screen. But I want Alert to be popup before user navigate to the selected screen.
If user selected no, User should be remain in the same screen.
Is there a way to achieve these things ?
Thanks.
Answer
function EditText({ navigation }) {
const [text, setText] = React.useState('');
const hasUnsavedChanges = Boolean(text);
React.useEffect(
() =>
navigation.addListener('beforeRemove', (e) => {
if (!hasUnsavedChanges) {
// If we don't have unsaved changes, then we don't need to do anything
return;
}
// Prevent default behavior of leaving the screen
e.preventDefault();
// Prompt the user before leaving the screen
Alert.alert(
'Discard changes?',
'You have unsaved changes. Are you sure to discard them and leave the screen?',
[
{ text: "Don't leave", style: 'cancel', onPress: () => {} },
{
text: 'Discard',
style: 'destructive',
// If the user confirmed, then we dispatch the action we blocked earlier
// This will continue the action that had triggered the removal of the screen
onPress: () => navigation.dispatch(e.data.action),
},
]
);
}),
[navigation, hasUnsavedChanges]
);
return (
<TextInput
value={text}
placeholder="Type something…"
onChangeText={setText}
/>
);
}
for more information refer: https://reactnavigation.org/docs/preventing-going-back/
This will prevent user to going back from one screen to another. But this does not prevent tab navigation because screen does not get removed.
prevent from tab navigation
<Tab.Screen
name={'search'}
component={SearchNavigator}
options={{
tabBarIcon: ({ focused, color }) => (
<View>
<Icon
name='search1'
color={
focused
? 'black'
: 'white'
}
size={25}
/>
</View>
),
}}
listeners={{
tabPress: (e) => {
if (true) {
// Prevent default action
e.preventDefault()
// Prompt the user before leaving the screen
Alert.alert(
'Discard changes?',
'You have unsaved changes. Are you sure to discard them and leave the screen?',
[
{
text: "Don't leave",
style: 'cancel',
onPress: () => {},
},
{
text: 'Discard',
style: 'destructive',
// If the user confirmed, then we dispatch the action we blocked earlier
// This will continue the action that had triggered the removal of the screen
onPress: () =>
navigationRef.current?.navigate(
'search',
{}
),
},
]
)
}
},
}}
/>
You can add listener tabPress like above code and provide an Alert.

How to navigate to another screen on back key press in react native?

I am trying to make an alert when the user clicks back button where he will be offered with two options, Yes or No. If the user clicks "No" the user will stay on that screen and if the user presses "Yes" then, I want some different screen to be shown.
Basically I want to prevent user from going back to the previous screen and instead redirect the user to some another screen.
Here is the example useEffect code that I am trying to make this work:
useEffect(() => {
navigation.addListener('beforeRemove', (e) => {
e.preventDefault();
Alert.alert(
'Registration Process',
'Are you sure you want to cancel the registration process?',
[
{
text: 'Yes',
style: 'destructive',
// If the user confirmed, then we dispatch the action we blocked earlier
// This will continue the action that had triggered the removal of the screen
onPress: () => {
// navigation.dispatch(e.data.action);
navigation.navigate('SignUp'); // On pressing Yes, user should be shown this screen.
},
},
{text: 'No', style: 'cancel', onPress: () => {}},
],
);
});
}, [navigation]);
After running the app, when I press "Yes" I get treated with the alert box again and again.
You can create a hook and call it on backpress on when user tries to leave the page
Create a folder called hooks where your App.js is located.
Inside that create a file called useBackHandler.ts
Inside useBackHandler.ts paste this
import { useEffect } from 'react';
import { BackHandler } from 'react-native';
export function useBackHandler(handler: () => boolean) {
useEffect(() => {
BackHandler.addEventListener('hardwareBackPress', handler);
return () => BackHandler.removeEventListener('hardwareBackPress', handler);
}, [handler]);
}
Then in your RegisterScreen create a function to execute on backpress or when user wants to goBack like this
const AlertConfirmation = () => {
Alert.alert(
'Registration Process',
'Are you sure you want to cancel the registration process?',
[
{
text: 'Yes',
style: 'destructive',
onPress: () => {
navigation.navigate('ScreenOne');
},
},
{ text: 'No', style: 'cancel', onPress: () => {} },
]
);
};
I've created a Snack for you to see working example..
Check this out.

Go back from remote "menu" button on tvOS (Apple TV) with React Native

I need to implement back from remote "menu" button on tvOS (Apple TV) and the BackHandler from
React Native doesn't work.
I understand that I have to use TVMenuControl and disable the TV menu key and go back, but this doesn't work even if the docs says it should work.
const backAction = () => {
Alert.alert("Hold on!", "Are you sure you want to go back?", [
{
text: "Cancel",
onPress: () => null,
style: "cancel"
},
{ text: "YES", onPress: () => BackHandler.exitApp() }
]);
return true;
};
useEffect(() => {
BackHandler.addEventListener("hardwareBackPress", backAction);
return () =>
BackHandler.removeEventListener("hardwareBackPress", backAction);
}, []);
How can I do this?
Thanks in advance,
Claudiu
you need to enable the menu button functionality to make your code works
import { TVMenuControl, BackHandler } from 'react-native';
....
useEffect(() => {
TVMenuControl.enableTVMenuKey();
BackHandler.addEventListener("hardwareBackPress", backAction);
return () => {
TVMenuControl.disableTVMenuKey();
BackHandler.removeEventListener("hardwareBackPress", backAction);
}
}, []);

How to await for the response of Alert dialog in React Native?

From my observation, the Alert dialog seems built on top of the React Native app.
So it pops out everytime you call it, and doesn't to be in the render function.
The catch is it is not an async task so the code after Alert will continue to execute regardless the callback function.
The code below demonstrates a situation where the Alert dialog keeps popping out because it reads the same barcode over and over again.
(It is written in TypeScript. Just take my word, this is a valid snippet.)
import * as React from "react";
import Camera from "react-native-camera";
import { Alert } from "react-native";
export default class BarcodeScanSreen extends React.Component<any ,any> {
private _camera;
private _onBarCodeRead = e => {
if (e.type === "QR_CODE") {
Alert.alert(
"QRCode detected",
"Do you like to run the QRCode?",
[
{ text: "No", onPress: this._onNoPress },
{ text: "Yes", onPress: this._onYesPress }
],
{ cancelable: false }
);
}
};
private _onYesPress = () => { /* process the QRCode */ }
private _onNoPress = () => { /* close the alert dialog. */ }
render() {
return (
<Camera
onBarCodeRead={this._onBarCodeRead}
aspect={Camera.constants.Aspect.fill}
ref={ref => (this._camera = ref)}
>
{/* Some another somponents which on top of the camera preview... */}
</Camera>
);
}
}
Is there a way to pause the JS code and await the response from Alert?
React-native Alert doesn't stop the execution of code below it. By changing it to async function which resolves the promise on user action will work as ASYNC-Alert.
const AsyncAlert = async () => new Promise((resolve) => {
Alert.alert(
'info',
'Message',
[
{
text: 'ok',
onPress: () => {
resolve('YES');
},
},
],
{ cancelable: false },
);
});
await AsyncAlert();
Use react-native-alert-async
I've just published a package that does exactly this and permit to await for the choice of the user. It is compatible with Expo.
import AlertAsync from "react-native-alert-async";
const myAction = async () => {
const choice = await AlertAsync(
'Title',
'Message',
[
{text: 'Yes', onPress: () => 'yes'},
{text: 'No', onPress: () => Promise.resolve('no')},
],
{
cancelable: true,
onDismiss: () => 'no',
},
);
if (choice === 'yes') {
doSomething();
}
else {
doSomethingElse();
}
}
Original answer: I've made a PR to ReactNative for this feature: https://github.com/facebook/react-native/pull/20312
Here's a simple solution.
The trick used here is to make a function that calls the button's onPress function, then resolve the promise with the index of the button. Note that this requires the alert to be uncancellable.
showAsyncAlert = (title, message, buttons, options) => {
return new Promise((resolve, reject) => {
// We can't detect a cancellation, so make sure the Alert is not cancellable.
options.cancellable = false
buttons.forEach((button, index) => {
let onPress = button.onPress
button.onPress = option => {
if (onPress) {
onPress(option)
}
resolve(index)
}
})
Alert.alert(title, message, buttons, options)
})
}
Usage:
let option = await showAsyncAlert(title, message, buttons options)
if (option === 0) {
foo()
} else {
bar()
}
Alert does not pause the code. In this case JS is not the only problem - the Camera component also keeps running in the background which is native and it will trigger the onBarCodeRead listener, regardless if the Alert is present or not.
You could try to stop the camera at the beginning on _onBarCodeRead with the stopPreview() method mentioned in the docs.
Also note that react-native-camera is currently in a migration process from Camera (RCTCamera) to RNCamera and in the new RNCamera I don't see a stopPreview() method. Anyhow, a simple flag would also do the job.
I have some workaround for this,If you have alert function like below
Alert.alert(
'Delete comment?',
'Are you sure you want to delete this comment?',
[
{
text: 'Cancel',
onPress: () => console.log('Cancel Pressed'),
style: 'cancel',
},
{ text: 'yes', onPress:() => this.props.deleteComment(commentId),
],
{ cancelable: false },
);
//call after comment is deleted
refreshPage();
This code does not wait for alert's response, it will execute refreshPage() immediately.
so, you can do something like
Alert.alert(
'Delete comment?',
'Are you sure you want to delete this comment?',
[
{
text: 'Cancel',
onPress: () => console.log('Cancel Pressed'),
style: 'cancel',
},
{ text: 'yes', onPress: async () => {await this.props.deleteComment(commentId);refreshPage();},
],
{ cancelable: false },
);
If you need to return a promise:
const asyncAlert = (title, message, callback) => (new Promise((resolve) => {
Alert.alert(
title,
message,
[{ text: "Cancel" }, { text: "OK", onPress: () => resolve() }], { cancelable: false }
);
}).then(callback));
Usage:
return asyncAlert("My Title", "Are you sure?", () => SOME_PROMISE_FUNCTION())