How to add Android Back Handler for a particular screen? - react-native

I have three screens MyVault, Add Doc and Add Repo. From Myvault there is one button add new doc by clicking on that Add Doc will open. Now in Add doc if user presses Back button then I want a confirmation pop up. I have one button inside add doc screen which opens Add repo screen where user can select one repo and when they click add that screen will be popped and add doc screen will be refreshed with the repo data. If I add a listener in ComponentDidMount and then remove it in ComponentWillUnmount then the issue is that even when I press back on Add repo then also the popup comes. I don't want popup on any other screens, I just want it on Add doc.
Note: I am using react native router flux for routing
I have posted this issue on this link also : https://github.com/facebook/react-native/issues/15248

As per react-native-router-flux documentation, event handlers can be added to buttons. You can use onExit, onLeft or onRight. Something like:
<Scene
key="AddDoc"
component={AddDoc}
onExit={() => console.log('your modal pop up logic')}
/>

I was able to do it with the help of onEnter and onExit props of react-native-router-flux

Try the below method
import React from 'react';
import {View, Text, AlertPlatform,BackAndroid} from 'react-native';
class App extends React.Component {
constructor(props) {
super(props);
}
onBackAndroid = () => {
backButtonPressedOnceToExit ? BackAndroid.exitApp() : "";
backButtonPressedOnceToExit = true;
setTimeout(() => {backButtonPressedOnceToExit = false;}, 2000);
return true;
}
componentWillMount = () => BackAndroid.addEventListener('hardwareBackPress', this.onBackAndroid.bind(this));
componentWillUnmount = () => {
BackAndroid.removeEventListener('hardwareBackPress', this.onBackAndroid.bind(this));
}
render() {
return (
// Your code
);
}
}
export default App;

Related

React Native - Triggering useeffect from another screen

I have a table that has items in it. When I click a specific item's edit button. It goes to edit page but when I submit an edit of that item. It goes back to main table page but the table page won't be updated. So, I think I need to trigger the useeffect function. To do that I need to update the state in main table page from another screen that is edit page screen.
my apps are not class-based. all of them functional. Here are my codes.
Main Table Page:
//I created a state to update
const [reload,
setReloader] = useState('');
I try to send the state to change it in edit item screen.
<TouchableOpacity onPress={() => navigation.navigate('addProduct', [reload])}>
Edit Item Page:
const [reload,
setReloader] = useState(route.params.reload); //I tried to pass state but it didn't work like that.
Pass a function through navigation
Pass the function in which you will be updating the state. i.e. setReloader
<TouchableOpacity onPress={() => navigation.navigate('addProduct', {setReloader})}>
On addProduct screen get the function by
const setReloader = route.params.setReloader;
once you have edited simply call the setReloader function on the edit page and then go back to the main screen
Using the navigation lifecycle method.
On the main screen, you can add a focus lister
const focusListner = navigation.addListener('focus', () => {
//call the API to fetch the updated data from the server
});
Focus listener is called whenever the return to that screen
You can do something like:
//I created a state to update
const [reload, setReloader] = useState('');
const updateState = (value) => {
setReloader(value);
}
Send `setReloader` as callback to update on edit screen
<TouchableOpacity onPress={() => navigation.navigate('addProduct', {callback: updateState})}>
Edit Item Page:
const callback = route.params.callback;
// call `callback` which will update view on parent screen
import { useIsFocused } from "#react-navigation/native";
const isFocused = useIsFocused();
Well it is worked for me. To trigger.
useEffect not called in React Native when back to screen

How unmount a hook after going to new screen with navigate

The context is a simple React Native app with React Navigation.
There are 3 screens.
The first simply displays a button to go to second screen using navigation.navigate("SecondScreen").
The Second contains a hook (see code below) that adds a listener to listen the mouse position. This hook adds the listener in a useEffect hook and removes the listener in the useEffect cleanup function. I just added a console.log in the listener function to see when the function is triggered.
This screen contains also a button to navigate to the Third screen, that only shows a text.
If I go from first screen to second screen: listener in hook start running. Good.
If I go back to the first screen using default react navigation 's back button in header. the listener stops. Good.
If I go again to second screen, then listener runs again. Good.
But if I now go from second screen to third screen, the listener is still running. Not Good.
How can I unmount the hook when going to third screen, and mount it again when going back to second screen?
Please read the following before answering :
I know that:
this is due to the fact that react navigation kills second screen when we go back to first screen, and then trigger the cleanup function returned by the useEffect in the hook. And that it doesn't kill second screen when we navigate to third screen, and then doesn't trigger the cleanup function.
the react navigation's hook useFocusEffect could be used to resolve this kind of problem. But it can't be used here because it will involve to replace the useEffect in the hook by the useFocusEffect. And I want my hook to be usable in every context, even if react navigation is not installed. More, I'm using here a custom hook for explanation, but it's the same problem for any hook (for example, the native useWindowDimensions).
Then does anyone know how I could manage this case to avoid to have the listener running on third screen ?
This is the code of the hook sample, that I take from https://github.com/rehooks/window-mouse-position/blob/master/index.js, but any hook could be used.
"use strict";
let { useState, useEffect } = require("react");
function useWindowMousePosition() {
let [WindowMousePosition, setWindowMousePosition] = useState({
x: null,
y: null
});
function handleMouseMove(e) {
console.log("handleMouseMove");
setWindowMousePosition({
x: e.pageX,
y: e.pageY
});
}
useEffect(() => {
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, []);
return WindowMousePosition;
}
module.exports = useWindowMousePosition;
the react navigation's hook useFocusEffect could be used to resolve this kind of problem. But it can't be used here because it will involve to replace the useEffect in the hook by the useFocusEffect. And I want my hook to be usable in every context, even if react navigation is not installed
So your hook somehow needs to know about the navigation state. If you can't use useFocusEffect, you'll need to pass the information about whether the screen is focused or not (e.g. with an enabled prop).
function useWindowMousePosition({ enabled = true } = {}) {
let [WindowMousePosition, setWindowMousePosition] = useState({
x: null,
y: null
});
useEffect(() => {
if (!enabled) {
return;
}
function handleMouseMove(e) {
console.log("handleMouseMove");
setWindowMousePosition({
x: e.pageX,
y: e.pageY
});
}
window.addEventListener("mousemove", handleMouseMove);
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, [enabled]);
return WindowMousePosition;
}
And then pass enabled based on screen focus:
const isFocused = useIsFocused();
const windowMousePosition = useWindowMousePosition({ enabled: isFocused });
Note that this approach will need the screen to re-render when it's blurred/focused unlike useFocusEffect.

MaterialTopTabNavigator dynamic route configs

I want to create via createBottomTabNavigator. It has 5 tabs. Each tab is a StackNavigator.
One of these tabs has a top tab bar. I create the top tab bar via createMaterialTopTabNavigator
But I know tab count after http request. How can I add tab dynamically? The doc says that
There are workarounds if you absolutely need dynamic routes but you can expect some additional complexity
I am confused about this task.
How can I do that?
Related react-navigation issue: https://react-navigation.canny.io/feature-requests/p/dynamic-routes-for-navigators
I think you can create a component that returns a tabNavigator. You can then access props or do whatever you want to dynamically add or remove tabs. Here I am using the latest version of react-navigation.
import React, { Component } from 'react-native';
import { createAppContainer, createMaterialTopTabNavigator } from 'react-navigation';
class DynamicTabs extends Component {
render() {
// I am using a prop here to update the Tabs but you can use state to update
// when the network request has succeeded or failed
const { shouldRenderTab } = this.props;
const TabNavigator = createMaterialTopTabNavigator({
Tab1: Tab1Component,
Tab2: Tab2Component,
// Create a tab here that will display conditionally
...(shouldRenderTab ? { Tab3: Tab3Component } : {}),
});
const ContainedTabNavigator = createAppContainer(TabNavigator);
return <ContainedTabNavigator />;
}
}
export default DynamicTabs;
This is the current solution I am using adapted from the original solution posted on github

Keyboard Handling in React Native

How to make your app respond gracefully on keyboard appearance?
So far I have tried keyboard-aware-scroll, keyboardspacer and keyboard Avoiding view
Keyboard avoiding view didn't help at all I have tried it several times but it doesn't even respond to keyboard appearance.
Keyboardspacer gracefully works but in many cases it destroys the whole UI by crushing other view
keyboardaware scroll works when there is no scroll in the app but for long forms it doesn't work.
android:windowSoftInputMode="adjustPan" only works for android
What are the other options that we have for the app to gracefully respond when keyboard appears.
What do you use in your apps?
If none of these libraries does what you need, you can adjust your view manually by using the Keyboard module (docs at https://facebook.github.io/react-native/docs/keyboard)
With it you can react when you know a keyboard opens or closes, like so:
import * as React from 'react';
import { Keyboard } from 'react-native';
class MyComponent extends React.Component {
componentDidMount() {
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this.keyboardDidHide);
this.keyboardDidShowListener = Keyboard.addListener('keyboardDidShow', this.keyboardDidShow);
}
componentWillUnmount() {
this.keyboardDidHideListener.remove();
this.keyboardDidShowListener.remove();
}
keyboardDidShow = () => {
//Fix your view for when a keyboard shows
};
keyboardDidHide = () => {
//Fix your view for when a keyboard hides
};
//Rest of component...
}
For my projects I use react-native-keyboard-aware-scroll-view as well as KeyboardAvoidingView (try to play with behavior prop, it depends on your styling).
Take a look in Android configuration section in docs of react-native-keyboard-aware-scroll-view. I think it's something that you're looking for.
You can find following usefull answer related your question.
Q.How to change the Softkeyboard “Enter” button Text in android?
https://stackoverflow.com/a/53098939/6477946
Q. How to close or hide SoftKeyBoard
https://stackoverflow.com/a/53077131/6477946

NavigatorIOS - Is there a viewDidAppear or viewWillAppear equivalent?

I'm working on porting an app to React-Native to test it out. When I pop back to a previous view in the navigator stack (hit the back button) I'd like to run some code. Is there a viewWillAppear method? I see on the Navigator there is a "onDidFocus()" callback which sounds like it might be the right thing.. but there doesn't appear to be anything like that on NavigatorIOS
I find a way to simulate viewDidAppear and viewDidDisappear in UIKit,
but i'm not sure if it's a "right" way.
componentDidMount: function() {
// your code here
var currentRoute = this.props.navigator.navigationContext.currentRoute;
this.props.navigator.navigationContext.addListener('didfocus', (event) => {
//didfocus emit in componentDidMount
if (currentRoute === event.data.route) {
console.log("me didAppear");
} else {
console.log("me didDisappear, other didAppear");
}
console.log(event.data.route);
});
},
For people who are using hooks and react navigation version 5.x, I think you can do this to expect similar behavior of viewDidAppear:
import React, {useCallback } from "react";
import { useFocusEffect } from "#react-navigation/native";
const SomeComponent = () => {
useFocusEffect(
useCallback(() => {
//View did appear
}, [])
);
//Other codes
}
For more information, refer https://reactnavigation.org/docs/use-focus-effect/
Here is a solution to simulate viewDidAppear with latest React Navigation version:
componentDidMount() {
var currentRoute = this.props.navigation.state.routeName;
this.props.navigation.addListener('didFocus', (event) => {
if (currentRoute === event.state.routeName) {
// VIEW DID APPEAR
}
});
}
Thanks Jichao Wu for the idea :)
If you are using React Navigation, use this:
componentDidMount(){
this.props.navigation.addListener('focus', () => {
// put your code here
});
}
Basically you are adding a focus event when component is first mounted. It will be called whenever (including the first time too) the component is focused. Ideally you'd also need to remove listener on unmount by capturing the value returned from addListener call and call that returned value (which is actually the unsubscribe function).
I've created a custom button with onLeftButtonPress to handled the back to run code as per https://github.com/facebook/react-native/issues/26
The way to get around it is to either set your custom back button on the left side, or to implement - viewWillDisappear: in iOS.
You can use ComponentWillMount or if you're leaving the view you can use ComponentWillUnmount which will run some code on exit.