I'm building a App with react-native v0.44.0 using redux v5.0.5 and react-navigation v1.0.0-beta.11. The routing is done with nested navigators, one main StackNavigator and DrawerNavigator.
I'm handling all navigation events in a navigation reducer, also the hardware back press on Android using BackHandler. Now comes the weird part (for me), I've implemented the BackHandler event handlers like so:
import { BackHandler, Modal, View } from 'react-native';
import { NavigationActions } from 'react-navigation';
import { HARDWARE_BACK_PRESS } from '../helpers/NavTypes';
constructor(props) {
super(props);
this.handleBack = this.handleBack.bind(this);
}
componentWillMount() {
BackHandler.addEventListener(HARDWARE_BACK_PRESS, this.handleBack);
}
componentWillUnmount() {
BackHandler.removeEventListener(HARDWARE_BACK_PRESS, this.handleBack);
}
handleBack() {
const navAction = NavigationActions.back();
this.props.navigation.dispatch(navAction);
return true;
}
In my navigation reducer I'm handling the Navigation/BACK action type and keep track of my state. Now, when I'm pressing the hardware back button on my Android device or in the emulator, I can see thanks to redux-logger and the React Native debugger that the navigation action is dispatched correctly and the previous shown screen appears, but the app closes anyway. This happens also when I alter the handleBack method to something like this:
handleBack() {
return true;
}
Every time the hardware back button is pressed, the App still closes. I did some step-debugging innode_modules/react-native/Libraries/Utilities/BackHandler.android.js, inside of RCTDeviceEventEmitter.addListener I can see that my event listeners are registered and invokeDefault is set to true in the loop. addListener is exited but the App still closes. Does anyone know if there is some point there react-navigation and redux are overriding the behaviour of the hardware back button at some top level I'm not aware of?
I've setup a second plain RN project without react-navigation and redux, implementing the same BackHandler event listeners (also returning true) and the app won't close. So, right now this leaves me a bit puzzled.
I'm using react-navigation and I also handle the os back button. It works fine for me.
May be you can try this out. Note that handleBack must return true if you are performing any other task than closing the app. If not it will close the app immediately.
componentWillMount() {
BackHandler.addEventListener(HARDWARE_BACK_PRESS, () => { return this.handleBack.bind(this)() });
}
I had the same problem, returning true wouldn't update. Running react-native run-android again after the changes fixed the problem for me.
Related
I do not want a user to click browser back button and forward button from my page because my page loads stored sessions every time. If user is clicking on these buttons then i simply want him to navigate to the homepage.
For security purpose, sessions are being maintained by the backend APIs so if user will fluctuate on browser's forward and back buttons then it will cause session disruption.
I have searched everywhere and tried lots of things but no solution is compatible with the v6 of react-router-dom. There must be solution for this. Please help me out in getting the same.
this was my question recently. I searched a lot but have nothing found to do this work with react-router v6. but you can handle browser back and forward buttons using window 'popstate' event. I wrote a hook for connecting, detect and handle this event(useBackButton):
import { useEffect, useState } from "react";
const useBackButton = (callback) => {
const [isBack, setIsBack] = useState(false);
const handleEvent = () => {
setIsBack(true);
callback();
window.history.go(1);
};
useEffect(() => {
window.addEventListener("popstate", handleEvent);
return () => window.removeEventListener("popstate", handleEvent);
});
return isBack;
};
export default useBackButton;
you can write your function that does your desired work and send it to this hook. then call this hook in every component that you want.
Is it possible to intercept the back action in react native on any route?
Lets say I am on a page and I want navigate a user on a different route when he/she performs a back action.
or even a popup confirm.
You can use React's [BackHandler][1] for instance
// import the back handler from react-native
import { BackHandler } from 'react-native'
// fire the listener on every render (including the componentWillUnmount)
useLayoutEffect(() => {
BackHandler.addEventListener(
"hardwareBackPress",
{
console.warn('about to go back....')
}
);
}, [])
[1]: https://reactnative.dev/docs/backhandler
I'm creating a react app with useState and useContext for state management. So far this worked like a charm, but now I've come across a feature that needs something like an event:
Let's say there is a ContentPage which renders a lot of content pieces. The user can scroll through this and read the content.
And there's also a BookmarkPage. Clicking on a bookmark opens the ContentPage and scrolls to the corresponding piece of content.
This scrolling to content is a one-time action. Ideally, I would like to have an event listener in my ContentPage that consumes ScrollTo(item) events. But react pretty much prevents all use of events. DOM events can't be caught in the virtual dom and it's not possible to create custom synthetic events.
Also, the command "open up content piece XYZ" can come from many parts in the component tree (the example doesn't completely fit what I'm trying to implement). An event that just bubbles up the tree wouldn't solve the problem.
So I guess the react way is to somehow represent this event with the app state?
I have a workaround solution but it's hacky and has a problem (which is why I'm posting this question):
export interface MessageQueue{
messages: number[],
push:(num: number)=>void,
pop:()=>number
}
const defaultMessageQueue{
messages:[],
push: (num:number) => {throw new Error("don't use default");},
pop: () => {throw new Error("don't use default");}
}
export const MessageQueueContext = React.createContext<MessageQueue>(defaultMessageQueue);
In the component I'm providing this with:
const [messages, setmessages] = useState<number[]>([]);
//...
<MessageQueueContext.Provider value={{
messages: messages,
push:(num:number)=>{
setmessages([...messages, num]);
},
pop:()=>{
if(messages.length==0)return;
const message = messages[-1];
setmessages([...messages.slice(0, -1)]);
return message;
}
}}>
Now any component that needs to send or receive messages can use the Context.
Pushing a message works as expected. The Context changes and all components that use it re-render.
But popping a message also changes the context and also causes a re-render. This second re-render is wasted since there is no reason to do it.
Is there a clean way to implement actions/messages/events in a codebase that does state management with useState and useContext?
Since you're using routing in Ionic's router (React-Router), and you navigate between two pages, you can use the URL to pass params to the page:
Define the route to have an optional path param. Something like content-page/:section?
In the ContentPage, get the param (section) using React Router's useParams. Create a useEffect with section as the only changing dependency only. On first render (or if section changes) the scroll code would be called.
const { section } = useParams();
useEffect(() => {
// the code to jump to the section
}, [section]);
I am not sure why can't you use document.dispatchEvent(new CustomEvent()) with an associated eventListener.
Also if it's a matter of scrolling you can scrollIntoView using refs
I'm running on Android and if I used the hardware back button to exit the application the componentWillUnmount function gets called. However, if I use the square button to show the list of running apps and swipe to close the function does not get called.
Any ideas on how to detect when the app is being closed so that I can clear timers, save data etc.
Thanks.
You can check AppState Api for information about detecting current state foreground or active. https://facebook.github.io/react-native/docs/appstate.html
I ran into the same problem. To solve it, I didn't attach an event listener to the stateChange. Instead I just look at AppState.currentState inside of the componentDidMount() function.
The problem was that eventListeners were getting attached when the component mounted, but not becoming detached at componentWillUnmount.
If you are using react-navigation 4.x you can use the code below (Copied from somewhere else).
I think this issue will be solved in react-navigation 5.x.
componentDidMount() {
const {navigation} = this.props;
this.focusListener = navigation.addListener('didFocus', () => {
const focused = navigation.isFocused();
if (focused) {
console.log('mount');
}
});
this.blurListener = navigation.addListener('willBlur', () => {
console.log('unmount');
});
}
componentWillUnmount() {
this.focusListener.remove();
this.blurListener.remove();
}
you can set condition for costume function that u write for hardware button , for example when ( for example for React Native Router Flux ) Actions.currentScene === 'Home' do something or other conditions u want .
I have a simple display which is based on some async data.
My code is as such:
componentDidMount() {
asynFunctionCall(result => {
this.setState({stateVariable: result});
});
}
And I use the state variable stateVariable to decide whether or not to show a <Text> component:
render() {
return (
<View>
{this.state.stateVariable &&
<Text> Special Message </Text>
}
</View>
);
}
What I want is for asyncFunctionCall to be run whenever the app comes to the foreground so that the user can leave the app, do something (which may affect the result of that async call), and come back to the app and have the display updated appropriately.
This seems like a standard app behavior, and I wonder if React Native's lifecycle API has a standard way to support it, but I'm not finding it.
How can I achieve this behavior, with a lifecycle function or otherwise?
Yes. you can use the AppState https://facebook.github.io/react-native/docs/appstate.html API.
I would suggest to add this somewhere at the start of the app and coordinate your views accordingly.
AppState should work mostly for iOS, but for Android background activity is triggered anytime a native module outside of your own code is triggered, e.g capturing a photo. It is way better to detect home or recent app button clicks. This is because fragments within your app from other apps like social media or photos will also trigger background state, which you don't want because they are still in the app adding a photo to a profile from the camera etc. You can easily detect home and recent app button clicks on Android with react-native-home-pressed. This library simply exposes the android button events.
First install the library with npm i react-native-home-pressed --save and then link it react-native link. Then rebuild your app and add the following snippet.
import { DeviceEventEmitter } from 'react-native'
class ExampleComponent extends Component {
componentDidMount() {
this.onHomeButtonPressSub = DeviceEventEmitter.addListener(
'ON_HOME_BUTTON_PRESSED',
() => {
console.log('You tapped the home button!')
})
this.onRecentButtonPressSub = DeviceEventEmitter.addListener(
'ON_RECENT_APP_BUTTON_PRESSED',
() => {
console.log('You tapped the recent app button!')
})
}
componentWillUnmount(): void {
if (this.onRecentButtonPressSub) this.onRecentButtonPressSub.remove()
if (this.onHomeButtonPressSub) this.onHomeButtonPressSub.remove()
}
}