BackAndroid not unmounting - React Native (Android) - javascript

I am working with React Native 0.29 with Android. For a particular view/activity/screen of my app, I want to add an event listener for BackAndroid button, which is available with react native. I already have a global BackAndroid event listener added to my app (in my index.android.js file) which pop out any view from the stack if it's not the main screen.
The event listener is activated with componentDidMount() lifecycle method and it works. It override the global one and works as expected. Now the problem is, it doesn't get removed when componentWillUnmount() lifecycle method get fired. So when back from that particular screen, the event listener still remains and cause trouble. Here is what I did:
componentDidMount() {
BackAndroid.addEventListener('backBtnPressed', this._handleBackBtnPress.bind(this))
}
componentWillUnmount() {
BackAndroid.removeEventListener('backBtnPressed', this._handleBackBtnPress.bind(this))
}
I don't understand why it's not working. Please help me to understand why it's not working and what should I do to solve this issue.

I spend hours to solve this problem. I think it would help other developers if I share this.
The main problem here is with the .bind(this) statement, bind always returns a new function. So in this code, this._handleBackBtnPress.bind(this) are not same functions in addEventListener and removeEventListener. They are different functions referring different first-class objects. That's why removeEventListener is not removing the listener.
To solve this issue, we can add the following statement to our constructor method - this._handleBackBtnPress = this._handleBackBtnPress.bind(this) and remove .bind(this) from both addEventListener and removeEventListener. So our code will look something like this:
constructor(props) {
super(props)
this._handleBackBtnPress = this._handleBackBtnPress.bind(this)
}
componentDidMount() {
BackAndroid.addEventListener('backBtnPressed', this._handleBackBtnPress)
}
componentWillUnmount() {
BackAndroid.removeEventListener('backBtnPressed', this._handleBackBtnPress)
}
Now both of them will refer same function and will work as expected.

Related

if I add anything inside componentWillUnmount nothing happening

I am trying to learn react myself.
I am trying to set session inside my componentWillUnmount.
but if I add anything inside componentWillUnmount nothing happening.
i debugged by adding console and debugger still nothing happening.
can you guys tell how to make my componentWillUnmount to work
so that in future I will fix it myself.
providing my relevant code snippet and sandbox below.
all my code is in RecipeReviewCard.js
https://codesandbox.io/s/1vqkz1own7
componentDidMount() {
console.log("componentDidMount---->");
}
componentWillUnmount() {
console.log("componentwillUnmount---->");
debugger;
window.sessionStorage.setItem(
"favoriteValues",
JSON.stringify(this.props.benchMarks)
);
}
As the name suggests, 'componentWillUnmount' will fire when the component is about to be taken out of the DOM (eg. when you switch tabs for example) and in your example the console.log indeed does fire. Use 'componentWillMount' to run the function when the component is loaded into the DOM

setOnNavigatorEvent callback not fired when switching tabs

I am working on an app powered by react-native#0.55.4 and react-native-navigation#1.1.474.
Initially, there is only a login screen (using Navigation.startSingleScreenApp). When the user logs in I call Navigation.startTabBasedApp (one of the tab components is my NavScreen). Whenever the user changes to another tab the root of the tab's stack is supposed to be displayed so I tried something like this:
class NavScreen extends React.PureComponent {
constructor(props) {
super(props)
this.props.navigator.setOnNavigatorEvent(this.toRootOnTabSelect.bind(this))
}
toRootOnTabSelect(event) {
const {id} = event
if (["bottomTabSelected", "bottomTabReselected"].includes(id)) {
this.props.navigator.popToRoot({
animated: true,
animationType: "fade",
})
}
}
render() {
return <Text>Whatever...</Text>
}
}
But for some reason my toRootOnTabSelect event handler method is not being called when I change tabs (by clicking on them - not by calling the switchToTab API method).
There are multiple posts I found online (i.e. https://stackoverflow.com/a/51159091/6928824, https://github.com/wix/react-native-navigation/issues/648) that indicate that it should work so I don't know what I'm missing. :/
Any help is greatly appreciated! :)
One of the reasons that can cause that is using setOnNavigatorEvent in conjuction with addOnNavigatorEvent, if you have a screen wrapper component that implement addOnNavigatorEvent your current listener will not work.
As mentioned in documentation
setOnNavigatorEvent Can not be used in conjuction with addOnNavigatorEvent
Also
Bear in mind that you can't use both addOnNavigatorEvent and setOnNavigatorEvent. addOnNavigatorEvent returns a function, that once called will remove the registered handler.
I would suggest trying addOnNavigatorEvent instead of setOnNavigatorEvent
This seems to be a bug in react-native-navigation#^1.1.474 (note the caret): see my issue on GitHub.
An according pull request has been opened but not yet merged. :(

Angular2, doe's EventEmitter require zone.run?

I'm using ionic2, I implemented a class:
import {EventEmitter, Injectable} from 'angular2/core';
#Injectable()
export class LocalPushClear extends EventEmitter<number> {
constructor() {
super();
}
}
The class is used by on of my components to connect cordova plugin event to another component which subscribe to LocalPushClear, I listen to clear events, ones it fires, I emit using LocalPushClear and some other component subscribes:
this._LocalPushClear.subscribe(data => {
// Some action is taken here
});
The thing is that, I was expecting automatic change detection to be executed upon subscription callback execution(when its done), but it seems like there is no change detection execution at all, I have to do something like click a button or wrap my Some action with zone.run, I'm not sure if its a valid behavior or maybe I'm doing something wrong.
Edit:
I traces the code and it leads to Subject, so its basically custom event emitter that angular NgZone don't know about(at least I think), but I'm sure, if anyone could confirm, maybe future explain I will be very thankful.
You definitely should not extend EventEmitter. EventEmitter is only supposed to be used for #Output()s. Just use a Subject instead.
Angular doesn't get notified about values emitted by EventEmitter (when used this way) or Subject. Normally the code that causes the Observable (Subject) to emit new values is executed by code that causes change detection when completed for example when called from an event handler or setTimeout.
In your case the cause seems to be that the code that emits new values using LocalPushClear runs outside Angulars zone.
You can use one of the methods explained in https://stackoverflow.com/a/34829089/217408 to trigger change detection after the Observable emits an event.

this.setState is not a function, react native

I am aware there are similar issues but none of them helped me to fix mine.
So here is my issue.
I am working with react native and using the flux dispatcher. Dispatch and register of my app dispatcher works fine. My issue is that when I want to change/set the state inside the dispatch register function I always get the error message that this.setState() is not a function. Of course I thought this must be a binding issue then (writing in es6), so I tried all sorts of binding "this" but I still can't get it work. Has anyone any idea why?
Here is that bit of code that doesn't work:
testDispatcher() {
AppDispatcher.register( (action) => {
if ( action.action === TEST_ACTION ) {
// I tried setting state inside here
this.setState({
view: action.view
}).bind(this); // with or without this bind doesn't make a difference
// I also tried having a function outside of this function where I set the state.. this doesn't work either.
//this.updateView('home').bind(this);
console.log('dispatch register');
}
});
}
I also tried to console log "this" inside my register function and "this" does return my app class.
The => binds this to the scope of testDispatcher(). That's probably not what you want. In this case I think you should drop the => notation but simply a regular anonymous function.
Also, this.setState(...args...).bind(this) is totally wrong. 1) The this. part indicates that the .bind(this) is redundant. 2) The syntax for binding is like: foo.setState.bind(notFoo, ...args...).
My class didn't extend from React Component, therefore this.setState was not a function.

Preventing Circular Dependencies with Exported Singleton Class

I have a question regarding a scenario I keep running into building HTML5 games resulting in difficult to manage circular dependencies.
I understand completely why the circular dependency is occuring and where it is occurring. However, I can't seem to figure out a convenient way to get around it, so I assume my logic / approach is fundamentally flawed.
Here's a little bit of context.
I have a game that has a single point of entry (compiled with Webpack) called Game.js. I have a basic event manager that allows for two functions on(key, callback) and fire(key, parameters).
The event manager simply creates an object, sets the supplied key of on as a property with an array value populated with any callback functions registered to that key. When the fire method is called that property is retrieved and all of the fuctions defined in it's array value are invoked.
What I'm trying to do
I want to be able to instance the event manager on Game.js and export an instance of Game that other classes can import and subsequently register callbacks to the Game instances event manager.
class Game {
constructor() {
this.events = new EventManager();
window.addEventListener('resize', this.resize.bind(this));
}
resize(event) {
if(window.innerWidth < window.innerHeight) {
this.events.fire('orientation-change', 'vertical');
} else {
this.events.fire('orientation-change', 'horizontal');
}
}
}
export default new Game();
Then for example a Button class may need to respond to an orientation change event fired by the Game. Please note the above is simply an example of a circumstance in which the event manager may fire an event, but this condition could be anything.
import Game from '../core/Game';
class Button {
constructor() {
Game.events.on('orientation-change', this.reorient.bind(this));
}
reorient() {
// ...
}
}
export default Button;
The above class is a UI component called Button that needs to know when the orientation-change event is fired, again please note this event could be anything.
What's the problem?
Nothing looks particularly wrong with the above, however, because Game.js is the entry point, at some point an instance of Button is created whether it be directly in Game.js or through another class which is subsequently instanced via Game.js which of course causes a circular dependency because even if not directly, Game imports Button and Button imports Game.
What I've tried
There are two main solutions that I have found that work (to some degree). The first being simply waiting for the export to be available using an interval check of the value of Game in the constructor of Button, like this:
import Game from '../core/Game';
class Button {
constructor() {
let check = setInterval(() => {
if(Game !== undefined) {
Game.events.on('orientation-change', this.reorient.bind(this));
clearInterval(check);
}
}, 100);
}
reorient() {
// ...
}
}
export default Button;
This will typically resolve in a single iteration.
The second solution being to use dependency injection and pass reference of Game to Button when it's instanced, which again works great, but the prospect of having to repeatedly do this per class seems unintuitive. The interval check works fine too, but seems hacky.
I'm feel like I'm completely missing something and that the solution isn't a difficult as I'm making it.
Thanks for any help regarding this.

Categories

Resources