Remove listener AppState change react native - react-native

Good evening everyone, I am facing a problem.
I am developing an app in react-native and I need that, every time a user sends the app in the background or in an inactive state, when he returns to the app I force him to go to a certain screen (Loading) where I perform certain checks (such as if he is a blocked user, deleted, etc ...).
I have now written the following function
const [appState, setAppState] = useState(AppState.currentState);
useEffect(() => {
getAttivita();
getBanner();
const appStateListener = AppState.addEventListener(
"change",
(nextAppState) => {
setAppState(nextAppState);
if (nextAppState === "active") {
navigation.dispatch(
CommonActions.reset({
index: 0,
routes: [{ name: Routes.Loading }],
})
);
}
}
);
return () => {
appStateListener?.remove();
};
}, []);
I put this listener in the Screen Diary (which represents my home).
Now if from the screen Diary, I minimize the app, then I have no problems and everything works as it should.
However if I go to another screen and minimize the app, then I get the following error
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
in Diary (at SceneView.tsx:122)
Then when I log back into the app I realize that the listener for the app status is still active (so it is as if the remove () had not worked) and in fact I am pushed back into my loading screen.
So I'm wondering, is it the listener that isn't actually being removed?
Or am I doing something wrong?
Thanks in advance to who will answer me .

Related

React Native Navigation: Navigate to a screen from outside a React component

https://wix.github.io/react-native-navigation/docs/basic-navigation#navigating-in-a-stack
indicates that pushing a new screen to the stack requires the current screen's componentId. My use case is to allow navigating based on certain events emitted by a native module. As such, componentId won't be available to me, since the event listener would reside outside any React component screen. Is there any way to navigate to a screen from outside in RNN? Or even get the current componentId.
I ended up adding a command event listener to store the current componentId in closure.
let currentComponentId;
Navigation.events().registerCommandListener((name, params) => {
if (name === 'push') {
currentComponentId = params.componentId;
}
});
Navigation.events().registerAppLaunchedListener(() => {
Navigation.setRoot(rootRouteConfig);
EventEmitter.addListener('navigate', (name) => {
Navigation.push(currentComponentId, {
component: {
name,
},
});
});
});

What is the difference between navigation.goBack and navigation.dispatch(e.data.action) in react native android

I am new to react native and I have react native app for android and I am using Navigation 5 for navigating to the next page. The application workflow is loginScreen -> HomeScreen -> ExistingStockScreen -> ScanSkidBarcodeScreen -> ScanItemsScreen.
I am handling backbutton press with this code on ScanItemScreen.
navigation.addListener('beforeRemove', (e) => {
// Prevent default behavior of leaving the screen
e.preventDefault();
if (this.state.items[0].barCode !== "" && this.state.items[5].barCode !== "") {
Alert.alert(
'Error!',
'You can only scan six items at a time.',
[
{ text: "OK", style: 'cancel', onPress: () => { } },
]
);
}
else if (this.state.items[0].barCode !== "") {
Alert.alert(
'Attention!',
'You have unsaved items. Please click on UPDATE STATUS first and go back.',
[{ text: "OK", style: 'cancel', onPress: () => { } },
{
text: 'Go Back',
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),
},]
);
} else {
navigation.dispatch(e.data.action)
}
})
}
However navigation.dispatch(e.data.action) code is going to back screen but it logs out the user for some reason.
So I am thinking to use navigation.goBack() instead.
What's the difference between navigation.goBack() and navigation.dispatch(e.data.action) ?
The beforeRemove event is triggerred when the screen is being removed, which can happen because of various reasons (such as a reset or another action). While going back is one of the ways to trigger it, it's not the only way.
From the docs:
The user pressed back button on a screen in a stack
The user performed a swipe back gesture
Some action such as pop or reset was dispatched which removes the screen from the state
The object in e.data.action refers to the action which triggered that event. By dispatching that action again with navigation.dispatch(e.data.action), you continue the action after performing whatever check you needed e.g. prompting the user.
If you don't dispatch the event again and instead try to goBack():
It'll be incorrect behaviour - if a reset triggered the event, you should continue the reset (or whaetver triggered the event), not perform a different action entirely
It won't work anyway, because goBack will just trigger the event, so you'll be stuck in a loop of prompts
However navigation.dispatch(e.data.action) code is going to back screen but it logs out the user for some reason
React Navigation doesn't interact with your app state. If your user is being logged out on navigation, that's a bug in your code that you need to debug and fix.

How to reset state of redux store while using react-navigation when react-navigation reset does not immediately unmount screens from stack?

I'm trying to do auth sign-out with react-native and am experiencing an issue where I want to reset the state of the redux store but, because I am using react-navigation, I have a bunch of redux-connected screens that are still mounted that re-render when the state tree is reset to it's initialState causing a bunch of exception errors. I tried to unmount them on sign-out with a react-navigation reset which redirects the user to the signup/login screen but I have no way of knowing when these screens are actually unmounted in order to call the RESET_STATE action. Initially I was dispatching the action via saga.
sagas/logout.js
import { LOGOUT, RESET_STATE } from 'Actions/user';
// clear localstorage once user logs out.
const clearData = function* clearData(action) {
AsyncStorage.removeItem('user');
yield put(
NavigationActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'SignedOut' })
],
})
);
// causes re-renders, screens still mounted
yield put({type: RESET_STATE});
}
export default function* logoutSaga () {
yield all([
yield takeEvery(LOGOUT, clearData),
]);
}
I also tried to reset once user reaches the SignedOut screen in it's componentDidMount cycle but unfortunately the screens unmount at some point well after componentDidMount is triggered:
screens/SignedOut.js
import { resetState } from 'Actions/user';
import ActionButton from 'Components/FormElements/ActionButton';
class SignedOut extends Component {
// screens are still mounted, causing screens from
// previous screens to throw exception errors
componentDidMount() {
this.props.dispatch(resetState());
}
componentWillUnmount() {
// never called
}
handleSignup = () => {
this.props.navigation.navigate('Signup');
}
handleLogin = () => {
this.props.navigation.navigate('Login');
}
render() {
return(
<Container>
<ActionButton
text="Sign Up"
handleButtonPress={this.handleSignup}
/>
<ActionButton
text="Log In"
handleButtonPress={this.handleLogin}
/>
</Container>
);
}
}
export default connect()(SignedOut);
My question is, can anyone think of a way to reset state of redux store after all of my screens have finally unmounted by the react-navigation reset action?
The issue is you're using navigate to navigate to the login/signup screen which leaves all your other components mounted, you should probably use back or reset to unmount all the components and show the login screen.
After thinking about this for a long time, I figured out that maybe I should have been focusing on the errors thrown instead of why I was getting errors. (I did learn a lot though).
Although figuring out how to listen for when all the screens are completely unmounted after calling a reset would have been super helpful, it would have just been a shortcut to bypass the real issue, the initialState for certain branches of my redux state was wrong. After correcting this no more errors, no matter when react-navigation decides to unmount the old screens.
As i have no idea how ur state looks like, which is always the issue here, why not try to use componentWillUnmount on all those components, to set a state variable and check that when you want to reset navigation?

react native push notification that can be removed after button click

I am using react native v0.45.1.
How can I add to my application notification (no matter if the app is in the background or foreground) that the user can remove only after click an acknowledge button.
I don't want the user to swipe the notification aside without notice it.
how can it be done?
I am not sure https://github.com/wix/react-native-notifications will do what I need.
Edit
I want to have a notification that will act like:
'USB debugging connected
'Touch to disable USB debugging'
The notification can't be removed unless the user actively do something, in my case it will be 'click' on a button
after a lot of digging the solution I implemented was:
setting repeat interval for the notification.
for android:
repeatType: 'time',
repeatTime: timeSpan,
for iOS:
repeatType: 'minute',
when the user click the notification (decided its not the right approached to add actions - buttons - to notification):
PushNotification.configure({
onNotification: (notification) => {
console.log('NOTIFICATION:', notification);
const clicked = notification.userInteraction;
if (clicked) {
if (Platform.OS === 'ios') {
PushNotification.cancelLocalNotifications({ id: notification.data.id });
} else {
PushNotification.cancelLocalNotifications({ id: notification.id });
}
}
},
});
I am aware that this might not be the exact answer to this question. However,
I was looking for a similar solution earlier; how to remove notifications that hasn't been dismissed or opened by a user.
Sometimes the user might open the application / screen, without clicking on the notification. The notification will still be displayed in that case.
Using React Native with Expo, this is the solution I created for removing old notifications created using their Push Notification API.
import * as Notifications from "expo-notifications";
//...
// Remove notifications that exists for this conversation!
useEffect(() => {
Notifications.getPresentedNotificationsAsync().then(res => {
for (let k in res) {
if (
res[k].request.content.data &&
res[k].request.content.data.screen === "Conversations" &&
res[k].request.content.data.conversationId === conversationId
) {
Notifications.dismissNotificationAsync(res[k].request.identifier);
console.log("removed a notification for this conversation");
}
}
});
}, []);
By using the data sent to the Push Notification API, we can determine which notifications should be removed. in this case all notifications containing a conversationId matching the current value from the route are dismissed.

Waiting for async before register component in react-native

I need to wait for async storage and then init app, because I store auth token here and want to show correct scene for user if he was authorised:
(async () => {
const viewer = JSON.parse(await AsyncStorage.getItem('viewer'));
// ...
const RootContainer = () => (
// ...
);
AppRegistry.registerComponent('yawaloo', () => RootContainer);
})();
I have moved to react-native 0.40.0 from 0.34.1 and now have an error "Module AppRegistry is not a registered callable".
In previous version everything were ok. How can I wait for some actions and then start render RootContainer?
One idea is to use splash screen. More specifically use a state in your RootContainer to determine whether to show a splash screen or your main UI. Set the state to false (show splash) initially then after you read the token from async storage, then set the state to true.
Part of why apps have splash screens is to deal with situation like this. HTH