react-native componentWillUnmount not working while navigating - react-native

I am doing this simple steps but unmount was not calling I don't know why. Please I need a solution for this I need unmount to be get called while navigating to another screen...
class Homemain extends Component {
constructor(props) {
super(props);
}
componentWillMount(){
alert('willMount')
}
componentDidMount(){
alert('didMount')
}
componentWillUnmount(){
alert('unMount')
}
Details = () => {
this.props.navigation.navigate('routedetailsheader')
}
render() {
return(
<View style={styles.container}>
<TouchableOpacity onPress={() => this.Details()} style={{ flex: .45, justifyContent: 'center', alignItems: 'center', marginTop: '10%', marginRight: '10%' }}>
<Image
source={require('./../Asset/Images/child-notification.png')}
style={{ flex: 1, height: height / 100 * 20, width: width / 100 * 20, resizeMode: 'contain' }} />
<Text
style={{ flex: 0.5, justifyContent: 'center', fontSize: width / 100 * 4, fontStyle: 'italic', fontWeight: '400', color: '#000', paddingTop: 10 }}>Details</Text>
</TouchableOpacity>
</View>
);
}
}
export default (Homemain);
This is my RouteConfiguration in this way I am navigating to the next screen. Can someone please help me for this error so that i can proceed to the next steps
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { addNavigationHelpers, NavigationActions } from 'react-navigation';
import { connect } from 'react-redux';
import { BackHandler } from 'react-native';
import { Stack } from './navigationConfiguration';
const getCurrentScreen = (navigationState) => {
if (!navigationState) {
return null
}
const route = navigationState.routes[navigationState.index]
if (route.routes) {
return getCurrentScreen(route)
}
return route.routeName
}
class StackNavigation extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
navigation: PropTypes.shape().isRequired,
};
constructor(props) {
super(props);
BackHandler.addEventListener('hardwareBackPress', this.backAction);
}
//backAction = () => this.navigator.props.navigation.goBack();
backAction = () => {
const { dispatch, navigation } = this.props;
const currentScreen = getCurrentScreen(navigation)
if (currentScreen === 'Homemain') {
return false
}
else
if (currentScreen === 'Login') {
return false
}
dispatch(NavigationActions.back());
return true;
};
render() {
const { dispatch, navigation } = this.props;
return (
<Stack
ref={(ref) => { this.navigator = ref; }}
navigation={
addNavigationHelpers({
dispatch,
state: navigation,
})
}
/>
);
}
}
export default connect(state => ({ navigation: state.stack }))(StackNavigation);

Routing to a new screen does not unmount the current screen.
For you usecase you instead of writing the code in componentWillUnmount you can continue by writing it after calling navigate in Details itself.
If you are looking for a callback when you press back from the new screen to come back to the current screen. Use goBack as shown in https://github.com/react-navigation/react-navigation/issues/733

If you are using a stack navigator, then routing to a new view loads the new view above the old one. The old view is still there for when you navigate back.

As I understand from your question and code you are using redux with navigation and want to unmount a screen. So what I did I just added a screen component inside another component to make my screen component as child.
e.g. below is the snippet that I am using to unmount the PushScreen from PushedData component.
I render PushScreen and inside it there is component PushedData that originally making the view. On PushedData `componentWillMount I am just doing some conditional functionality and on success I am just unmounting PushData from PushScreen.
class PushScreen extends Component{
state ={ controllerLaunched: false };
updateControllerLauncher = () => {
this.setState({ controllerLaunched: true });
}
render (){
if(this.state.controllerLaunched){
return null;
} else {
return <PushedData handleControllerLauncher={this.updateControllerLauncher} />;
}
}
}
class PushedData extends Component{
componentWillMount(){
this.unmountPushData();//calling this method after some conditions.
}
unmountPushData = () => {
this.props.handleControllerLauncher();
}
render(){
return (
<View><Text>Component mounted</Text></View>
);
}
}
Let me know if you need more information.

When you use Stack Navigator then routing to a new view loads the new view above the old one as Rob Walker said. There is a workaround. You can bind blur event listener on componentDidMount using navigation prop:
componentDidMount() {
this.props.navigation.addListener('blur', () => {
alert('screen changed');
})
}
So when your screen goes out of focus the event listener is called.
You can find more about events right here.

If you want to go next Screen use (.replace instead .navigate) where you want to call componentWillUnmount. and if you want to go back to one of previous screens use .pop or .popToTop.

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 .

Related

How do I navigate to a sub-screen after the first componentDidMount?

If I have a screen that receives route params, does some processing, and then re-routes to a sub-screen, this works if the screen was previously mounted but I get the following error if I try this after the first componentDidMount:
The action 'NAVIGATE' with payload {"name":"Chat","params":{"name":"Person2"}} was not handled by any navigator.
Do you have a screen named 'Chat'?
If you're trying to navigate to a screen in a nested navigator, see https://reactnavigation.org/docs/nesting-navigators#navigating-to-a-screen-in-a-nested-navigator.
This is a development-only warning and won't be shown in production.
[...]
Here are the highlights:
Tab navigator (App) with home (HomeScreen) and chats (ChatsScreenStack) tabs.
The chats tab is a stack navigator with a chats list to list all chats (ChatsListScreen) and a chat screen to show a particular chat (ChatScreen).
The chats tab stack navigator (ChatsScreenStack) has a componentDidUpdate which checks if the name prop has been updated, and, if so, it navigates to the chat tab.
The chats tab stack navigator also has a constructor which checks if it was created with a name prop, and, if so, it saves it off to a field and does the same navigation as above in componentDidMount.
Item 3 works but Item 4 doesn't work. Is this because react-navigation hasn't built up its navigation state at the time of the first componentDidMount? If so, how do I get a callback when react-navigation is ready?
Below is a reproduction (Snack link, Github link). If you launch, and click on ChatsTab, click back on HomeTab, and then click on the button it works. However, if you launch, and immediately click on the HomeTab button, it gives the error (in development mode; on the snack, it will navigate to the chats list rather than the chat screen).
import * as React from 'react';
import { Button, FlatList, Text, View } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createBottomTabNavigator } from '#react-navigation/bottom-tabs';
import { createStackNavigator } from '#react-navigation/stack';
class ChatScreen extends React.Component {
render() {
return (
<View style={{ padding: 10 }}>
<Text>Chat with {this.props.route.params.name}</Text>
</View>
);
}
}
class ChatsListScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<FlatList
data={[ {name: "Person1", key: "1"}, {name: "Person2", key: "2"}]}
renderItem={(data) => {
return (
<View key={data.item.key} style={{ margin: 10 }}>
<Button
title={data.item.name}
onPress={() => this.props.navigation.navigate("Chat", { name: data.item.name })}
/>
</View>
);
}}
/>
</View>
);
}
}
const ChatsStack = createStackNavigator();
class ChatsScreenStack extends React.Component {
constructor(props) {
super(props);
if (props.route && props.route.params && props.route.params.name) {
this.pendingReroute = props.route.params.name;
}
}
componentDidMount() {
if (this.pendingReroute) {
this.props.navigation.navigate("Chat", { name: this.pendingReroute });
}
}
componentDidUpdate(prevProps) {
let updated = false;
if (this.props.route && this.props.route.params.name) {
updated = true;
if (prevProps.route && prevProps.route.params && prevProps.route.params.name == this.props.route.params.name) {
updated = false;
}
}
if (updated) {
this.props.navigation.navigate("Chat", { name: this.props.route.params.name });
}
}
render() {
return (
<ChatsStack.Navigator>
<ChatsStack.Screen name="Chats" component={ChatsListScreen} />
<ChatsStack.Screen name="Chat" component={ChatScreen} />
</ChatsStack.Navigator>
);
}
}
class HomeScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Button
title="Navigate to Person2"
onPress={() => this.props.navigation.navigate("ChatsTab", { name: "Person2" })}
/>
</View>
);
}
}
const Tabs = createBottomTabNavigator();
export default class App extends React.Component {
render() {
return (
<NavigationContainer>
<Tabs.Navigator>
<Tabs.Screen name="HomeTab" component={HomeScreen} />
<Tabs.Screen name="ChatsTab" component={ChatsScreenStack} />
</Tabs.Navigator>
</NavigationContainer>
);
}
}
Yes, this was related to react-navigation not being ready in componentDidMount. I needed to handle the focus event:
class ChatsScreenStack extends React.Component {
constructor(props) {
super(props);
if (props.route && props.route.params && props.route.params.name) {
this.pendingReroute = props.route.params.name;
}
}
componentDidMount() {
this.props.navigation.addListener(
"focus",
this.onFocus
);
}
onFocus = () => {
if (this.pendingReroute) {
const name = this.pendingReroute;
this.pendingReroute = null;
this.props.navigation.navigate("Chat", { name: this.pendingReroute });
}
}
[...]

How i can get the route param in react native

How i can get the route param in react native.
onPress={() => props.navigation.navigate('SingleTour', { item } )}
This is the way you can send the item eg index.js
<TouchableOpacity onPress={() => props.navigation.navigate('SingleTour', { item } )}>
<Text>SingleTour</Text>
</TouchableOpacity>
in SingleTour.js you can get this item like this
const { item } = this.props.route.params;
I assume that you need to pass param with navigation and set param to the new screen's state.
Assuming that this is your onPress event on base screen
onPress={() => props.navigation.navigate('SingleTour', { item } )}
So this is how you set param in new screen's state
SingleTour.js
import React, { Component } from 'react';
import {
View,
StyleSheet
} from 'react-native';
export default class SingleTour extends Component {
constructor(props) {
super(props);
this.state = {
item : this.props.navigation.state.params.item,
}
}
render() {
return (
<View style={styles.container}>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
}
});
Hope you have clear about this :)
This is your solution.
onPress={() => {
/* 1. Navigate to the Details route with params */
navigation.navigate('Details', {
itemId: 86,
otherParam: 'anything you want here',
});
}}
If any problem occurs, you can refer to this Official Documentation
Refer this Expo Snack Online Demo

setState in an interval consuming huge memory

I tried create a countdown timer and found that setState in an interval consuming a lot of memory and it will grow until it crash.
I tried creating a fresh new react-native app using react-native init intervaltest
then I got this in App.js
import React, { Component } from "react";
import { StyleSheet, Text, View } from "react-native";
type Props = {};
export default class App extends Component<Props> {
state = {
countdownNumber: 10000000
};
componentDidMount = () => {
if (!this.interval) {
this.interval = setInterval(() => {
this.setState(prevState => ({
countdownNumber: prevState.countdownNumber - 1
}));
}, 10);
}
};
render() {
const { countdownNumber } = this.state;
return (
<View style={styles.container}>
<Text style={styles.welcome}>{countdownNumber}</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
backgroundColor: "#F5FCFF"
},
welcome: {
fontSize: 20,
textAlign: "center",
margin: 10
},
});
It is a very simple code anyway. Is there any way to solve this memory thirsty behavior of react-native?
In your code setInterval is called in componentDidMount and componetDidMount will be called once in whole component life-cycle. So, the function within setInterval will be called once only. i.e. just after the first render but upon successive render, the componentDidMount won't be called.
Simple solution is:
export default class App extends Component<Props> {
state = {
countdownNumber: 10000000
};
componentDidMount = () => {
if (!this.interval) {
this.interval = setInterval(() => {
this.setState(prevState => ({
countdownNumber: prevState.countdownNumber - 1
}));
}, 10);
}
};
componentDidUpdate(){
if(this.state.countdownNumber === 1){
clearInterval(this.interval);
}
}
componentWillUnmount(){
clearInterval(this.interval);
}
render() {
const { countdownNumber } = this.state;
return (
<View style={styles.container}>
<Text style={styles.welcome}>{countdownNumber}</Text>
</View>
);
}
}
Memory leak due to setInterval::If we unmount the component before calling clearInterval, there is a memory leak because the interval that is set in componentDidMount starts timer and the timer is not stopped when component unmount's. React provides the componentWillUnmount life-cycle method as an opportunity to clear anything that needs to be cleared when the component is unmounted / removed.

React Navigation autoFocus when using TabNavigator

In react-navigation, what is the best way to handle a tab that has a form with an autoFocus input that automatically pulls up the keyboard?
When the Navigator initializes all the screens, it automatically displays the keyboard even though the screen without the autoFocus element is showing first.
I want it to open the keyboard when I'm on the tab with the form, but close it when I leave that view.
Here is an example (and an associated Gist):
App.js
const AppNavigator = TabNavigator( {
listView: { screen: TheListView },
formView: { screen: TheFormView }
} )
TheFormView.js
const TheFormView = () => {
return (
<View style={{ marginTop: 50 }}>
<TextInput
autoFocus={ true }
keyboardType="default"
placeholder="Blah"
/>
</View>
)
}
TheListView.js
const TheListView = () => {
return (
<View style={{ marginTop: 50 }}>
<Text>ListView</Text>
</View>
)
}
You should use lazy on TabNavigator config: https://github.com/react-community/react-navigation/blob/master/docs/api/navigators/TabNavigator.md#tabnavigatorconfig
This prevents the screen from being initialised before it's viewed.
Also consider having some kind of state management or look for Custom Navigators (https://reactnavigation.org/docs/navigators/custom) for setting the autoFocus prop as true only when TheFormView is navigated to.
This answer was out of date for me as of April 2020, but this worked for me:
import { useFocusEffect, } from "#react-navigation/native"
import React, { useEffect, useState, } from "react"
...
const CreateProfileScreen = ({ navigation, }) => {
const [safeToOpenKeyboard, setSafeToOpenKeyBoard] = useState(false)
...
useFocusEffect(
React.useCallback(() => {
console.log("Navigated to CreateProfileScreen")
setSafeToOpenKeyBoard(true)
return () => {
console.log("Navigated away from CreateProfileScreen")
setSafeToOpenKeyBoard(false)
}
}, [])
)
...
return (<TextInput autoFocus={safeToOpenKeyboard}/>)
}

Flux (alt), TabBarIOS, and Listeners and tabs that have not yet been touched / loaded

I've got a problem that I'm sure has a simple solution, but I'm new to React and React Native so I'm not sure what I'm missing.
My app has a TabBarIOS component at its root, with two tabs: TabA and TabB. TabB is subscribed to events from a Flux store (I'm using alt) that TabA creates. TabA basically enqueues items that TabB plays. This part of the code is fine and works as expected.
The problem is that TabA is the default tab so the user can use TabA an enqueue items, but because TabB hasn't been touched/clicked the TabB component hasn't been created so it's listener hasn't been registered. Only when TabB is pressed does it get created and correctly receive events.
So how can I ensure the TabB component gets created when the TabBarIOS component is rendered? Do I need to something hacky like set the active tab to TabB on initial load and flip it back to TabA before the user does anything?
Yes, you'll need to do something hacky if you're not using a Navigator component. If you're using Navigatoryou can specify a set of routes to initially mount with the initialRouteStackprop. This is however going to need you to modify a bit the way your app works I think.
If not using Navigator, you'll indeed have to do something hacky as you suggested. I've set up a working example here based on RN's TabBar example.
Below you'll find the code of this example, check the console.log (they don't seem to work on rnplay) to see that that components are mounted on opening the app.
Example Code
var React = require('react-native');
var {
AppRegistry,
Component,
Image,
StyleSheet,
TabBarIOS,
Text,
View
} = React;
import _ from 'lodash';
var base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';
class StackOverflowApp extends Component {
constructor(props) {
super(props);
this.state = {
selectedTab: 'blueTab',
notifCount: 0,
presses: 0
};
}
_renderContent = (color, pageText, num) => {
return (
<View style={[styles.tabContent, {backgroundColor: color}]}>
<Text style={styles.tabText}>{pageText}</Text>
<Text style={styles.tabText}>{num} re-renders of the {pageText}</Text>
</View>
);
};
componentWillMount() {
this.setState({selectedTab: 'redTab'});
}
componentDidMount() {
this.setState({selectedTab: 'blueTab'});
}
render () {
return (
<View style={{flex: 1}}>
<TabBarIOS
tintColor="white"
barTintColor="darkslateblue">
<TabBarIOS.Item
title="Blue Tab"
icon={{uri: base64Icon, scale: 3}}
selected={this.state.selectedTab === 'blueTab'}
onPress={() => {
this.setState({
selectedTab: 'blueTab',
});
}}>
<Page1 />
</TabBarIOS.Item>
<TabBarIOS.Item
systemIcon="history"
badge={this.state.notifCount > 0 ? this.state.notifCount : undefined}
selected={this.state.selectedTab === 'redTab'}
onPress={() => {
this.setState({
selectedTab: 'redTab'
});
}}>
<Page2 />
</TabBarIOS.Item>
</TabBarIOS>
</View>
);
};
}
class Page1 extends Component {
static route() {
return {
component: Page1
}
};
constructor(props) {
super(props);
}
componentWillMount() {
console.log('page 1 mount');
}
componentWillUnmount() {
console.log('page 1 unmount');
}
render() {
return (
<View style={styles.tabContent}>
<Text style={styles.tabText}>Page 1</Text>
</View>
);
}
}
class Page2 extends Component {
static route() {
return {
component: Page2
}
};
constructor(props) {
super(props);
}
componentWillMount() {
console.log('page 2 mount');
}
componentWillUnmount() {
console.log('page 2 unmount');
}
render() {
return (
<View style={styles.tabContent}>
<Text style={styles.tabText}>Page 2</Text>
</View>
);
}
}
const styles = StyleSheet.create({
tabContent: {
flex: 1,
backgroundColor: 'green',
alignItems: 'center',
},
tabText: {
color: 'white',
margin: 50,
},
});
AppRegistry.registerComponent('StackOverflowApp', () => StackOverflowApp);