I am developing react-native app for quite sometimes. During my development, almost everyday i am facing some error/warning. Among them, the most comment error I've faced is this-> Warning: Can't perform a React state update on an unmounted component. I've searched everywhere, but couldn't find a proper solution. And this log also not explaining that much. So is there anyone who can explain this which will be much more understandable, or point me out where should I dig into to solve this error or what more should I study to understand the situation. Here is the full screenshot of this error.
. And Here is the some code of one of my component:
//packages
import React, { useContext, useEffect, useState } from 'react';
import { ActivityIndicator, ImageBackground, Pressable, StyleSheet, Text, View } from 'react-native';
// third pirty packages
import { Button, HamburgerIcon, Menu, NativeBaseProvider, useToast } from 'native-base';
import Feather from 'react-native-vector-icons/Feather';
import Foundation from "react-native-vector-icons/Foundation";
import NetInfo from "#react-native-community/netinfo";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp,
listenOrientationChange as lor,
removeOrientationListener as rol
} from 'react-native-responsive-screen';
//assets and components
import { AuthContext } from './../context';
const MainBody = (props) => {
const { signOut } = useContext(AuthContext);
const [isLoading, setIsLoading] = useState(false);
const toast = useToast();
useEffect(() => {
lor();
return () => {
rol();
};
}, []);
const styles = StyleSheet.create({
wrapperView: {
height: hp('87%'),
paddingTop: hp('7%'),
alignItems: 'center',
backgroundColor: '#fff'
},
dashboardView: {
width: '80%',
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: hp('3%')
},
dashboardCategory: {
width: '45%',
height: hp('20%'),
borderRadius: 5,
elevation: 5,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
backgroundColor: '#FFFFFF'
},
iconStyle: {
color: 'grey',
fontSize: hp('5%'),
alignSelf: 'center'
},
buttonText: {
marginTop: 10,
color: '#4b2e80',
width: '100%',
textAlign: 'center',
fontSize: hp('2.7%')
},
headerButtonView: {
position: 'absolute',
top: hp('3%'),
right: wp('5%')
}
});
return (
<View>
<View style={styles.wrapperView}>
<View style={styles.dashboardView}>
<Button light style={styles.dashboardCategory}>
<Feather style={styles.iconStyle} name="users" />
<Text style={styles.buttonText}> Clip </Text>
</Button>
<Button light style={styles.dashboardCategory}>
<Foundation style={styles.iconStyle} name='pound' />
<Text style={styles.buttonText}> Balancing </Text>
</Button>
</View>
</View>
<View style={styles.headerButtonView}>
<Menu
trigger={(triggerProps) => {
return (
<Pressable accessibilityLabel="More options menu" {...triggerProps}>
<HamburgerIcon color="#fff" />
</Pressable>
)
}}
>
<Menu.Item onPress={() => signOut()}>Logout</Menu.Item>
</Menu>
</View>
</View>
);
}
export const DashboardScreen = ({ navigation }) => {
return (
<NativeBaseProvider>
<MainBody navigate={navigation.navigate} />
</NativeBaseProvider>
);
}
we need to unsubscribe the particular subscription before our components unmounts. it's a workaround but will get rid of the error.
useEffect(() => {
let mounted = true
if(mounted){
lor();
}
return () => {
rol();
mounted= false
};
}, []);
Related
I have a custom header in my app and I am trying to get the navigation to open the Drawer when clicked on the menu item in the header component. I've passed the navigation and the header text as props to the component. However the props are being returned as undefined.
This is my header
import React from 'react';
import {View, StyleSheet, Text, Image, Alert, TouchableOpacity } from 'react-native';
import Colors from '../constants/Colors';
import { MaterialIcons } from '#expo/vector-icons';
const Header = ({navigation, headerText}) => {
const openMenu = () => {
console.log({headerText}) **/// this prints Object { "headerText": undefined,}**
navigation.openDrawer() **/// this also throws an undefined error**
}
console.log(headerText)**/// this displays the headerText correct on loading**
return (
<View style={styles.header} >
<MaterialIcons onPress={openMenu} name='menu' size={30} style={styles.icon} />
<Text style={styles.logo}>My Home Page Header</Text>
</View>
);
};
const styles = StyleSheet.create({
header: {
paddingTop: 20,
width: '100%',
height: '10%',
flexDirection: 'row',
},
logo: {
height: '100%',
width: '90%',
fontFamily: 'pacifico-regular',
fontSize: 28,
paddingTop: 20,
paddingLeft: 20,
},
icon: {
marginTop:30,
paddingLeft: 10,
},
});
export default Header;
console.log(headerText)/// this displays the header text correctly when loading the component.
However trying to use the props in the Text or the View throws an undefined error.
Clicking on the MaterialIcon menu icon gives the error :
Object {
"headerText": undefined,
}
and
TypeError: undefined is not an object (evaluating 'navigation.openDrawer')
This is the homestack.js
import { createStackNavigator } from 'react-navigation-stack';
import NewFeaturedRecipes from '../screens/NewFeaturedRecipes';
import NewRecipeDetails from '../screens/NewRecipeDetails';
import Header from '../shared/Header';
import React from 'react';
import {navigation} from 'react-navigation'
// const navigation = navigation;
const screens = {
NewFeaturedRecipes: {
screen: NewFeaturedRecipes,
navigationOptions: ({navigation}) => {
return {
headerTitle: () => <Header navigation={navigation}
headerText='Testting headertext from hoomestack' />
}
}
},
NewRecipeDetails: {
screen: NewRecipeDetails,
navigationOptions: {
title: 'Recipe Details',
}
},
}
const HomeStack = createStackNavigator(screens, {
defaultNavigationOptions: {
headerStyle: {
height: 20,
},
}
});
export default (HomeStack);
Here the answer if anybody is interested. I've used React Navigation 5.x for the nested navigators and I am able to call the openDrawer() from the menu in the Header component.
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View, Button, DrawerLayoutAndroid } from 'react-native';
import { NavigationContainer, useNavigation } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import {createDrawerNavigator} from '#react-navigation/drawer';
import { MaterialIcons } from '#expo/vector-icons';
const Header = ({ headerText }) => {
const navigation = useNavigation()
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', flexDirection: 'row' }}>
<MaterialIcons onPress={()=> navigation.openDrawer()} name='menu' size={30} style={styles.icon} />
<Text>Header Text = {headerText}</Text>
</View>
)
}
const HomeScreen = ({ navigation }) => {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Home Screen</Text>
<Button onPress={() => navigation.navigate('Details')} title='Goto Details Screen' />
</View>
)
}
const DetailScreen = ({ navigation }) => {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<Text>Detail Screen</Text>
<Button onPress={() => navigation.navigate('Home')} title='Goto Home Screen' />
</View>
)
}
const HomeStack = () => {
return (
<Stack.Navigator>
<Stack.Screen
name='Home'
component={HomeScreen}
options={{
headerTitle: props => <Header headerText='Text from App' />
}}
/>
<Stack.Screen name='Details' component={DetailScreen} options={{
headerTitle: props => <Header headerText='Text from Details' />
}}
/>
</Stack.Navigator>
)
}
function NotificationsScreen({ navigation }) {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', flexDirection:'column' }}>
<Text>Notifications Screen</Text>
<Button onPress={() => navigation.goBack()} title="Go back home" />
</View>
);
}
const Stack = createStackNavigator()
const Drawer = createDrawerNavigator()
const App = () => {
return (
<NavigationContainer>
<Drawer.Navigator initialRouteName='Home'>
<Drawer.Screen name='Home' component={HomeStack}/>
<Drawer.Screen name='Notifications' component={NotificationsScreen}/>
</Drawer.Navigator>
</NavigationContainer>
)
}
export default App
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
I am trying to create a scroll view in react native.
I gave full-device-width and height for the scroll-view.
width is working but height is not working so the app is showing only on down part of the emulator,
I would like to know how can I make this showing up in fullscreen and also why this error occurring.
this is how it's loading on emulator.
you can find my react-native code below
import React, { Component } from 'react';
// import Counterpart from './Counterpart'
import contacts from './contacts'
import {
View,
Button,
ScrollView,
Switch,
Text,
Input,
StyleSheet,
Dimensions,
} from 'react-native';
const widthfull = Dimensions.get('window').width; //full width
const heightfull = Dimensions.get('window').height; //full height
const styles = StyleSheet.create({
mainwrap: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
zIndex:1,
},
countfont: {
fontSize: 120,
},
marginfromtop: {
display: 'flex',
flex: 1,
paddingTop: 50,
},
ScrollViewstles: {
display: 'flex',
flex: 1,
margin:0,
padding:0,
zIndex:2,
width:widthfull,
height:heightfull,
paddingLeft:30
}
});
export default class App extends Component {
state = {
showCounter: true
}
toggglecounter = () => {
this.setState(() => ({showCounter: !this.state.showCounter}))
}
render() {
if (this.state.showCounter) {
return (
<View style={styles.mainwrap}>
<View style={[styles.marginfromtop, styles.countfont]}>
<Button
style={{ marginTop: 50 }}
onPress={this.toggglecounter}
title="Toggle Contacts"
/>
</View>
<View style={styles.mainwrap}>
<ScrollView style={styles.ScrollViewstles}>
{contacts.map(c => (
<Text key={c.key}>{c.name}</Text>
))}
</ScrollView>
</View>
</View>
);
} else {
return (
<View style={[styles.marginfromtop, styles.countfont]}>
<Button
style={{ marginTop: 50 }}
onPress={this.toggglecounter}
title="Toggle Contacts"
/>
</View>
);
}
}
}
remove flex: 1 from marginfromtop
marginfromtop: {
paddingTop: 50,
},
I want to update initialParams of react native navigation on clicking of the menu item
import React, { useState } from 'react';
import {createStackNavigator} from '#react-navigation/stack'
import { NavigationContainer } from '#react-navigation/native';
import userMain from './../../components/users/userMain';
import { View, Icon } from 'native-base';
export const UserStack = () => {
const Stack = new createStackNavigator()
const [toggleSearch, setToggleSearch] = useState(true)
const changeStyleToggle = () => {
setToggleSearch( !toggleSearch )
// console.log('toggle search here ==== ', toggleSearch)
}
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="User Homepage"
component={userMain}
initialParams={{ toggleSearch: toggleSearch}}
options = {{
title: 'My home',
headerStyle: {
backgroundColor: '#f4511e',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
headerRight: () => (
<View style={{flexDirection:'row', justifyContent:'space-evenly', width:120}}>
<Icon name='home' onPress={() => changeStyleToggle()} />
<Icon name='home' />
<Icon name='home' />
</View>
),
}}
/>
</Stack.Navigator>
</NavigationContainer>
)
}
the component i am calling is userMain where i am calling initialParams value and on behalf of that i want to change style
import React from 'react'
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native'
import { Input } from 'native-base';
const userMain = (props) => {
const {toggleSearch} = props.route.params;
console.log(toggleSearch)
const check = toggleSearch == true ? 'true!!' : 'false!!'
return (
<View style={styles.container}>
<Input
placeholder="search"
style={styles.searchInput}
/>
<Text>user homepage here</Text>
<Text>{check}</Text>
<TouchableOpacity style={styles.btn}>
<Text style={styles.btnText}> + </Text>
</TouchableOpacity>
</View>
)
}
const styles = StyleSheet.create({
container: {
alignItems: 'center', flex: 1, justifyContent: 'center',
},
btn: {
position: 'absolute', justifyContent: 'center', alignItems: 'center',
bottom:10, right:10, width:60, height: 60,
backgroundColor: '#fff', borderRadius: 50, borderColor: '#000',borderWidth:1,
},
btnText: {
fontSize: 50, color: 'black',
},
searchInput: {
position:'absolute', top: 0, borderWidth: 1, width: 300, opacity:0
}
})
export default userMain
i have checked on clicking the icon state which is toggleSearch is updating but it is not updating the initialParams hence it is not working on userMain component as well .
I have a little problem in my react-native mobile app.
There is a Space between the header and the Background Image.
How can I remove it, like the Background Image is next to the Header ?
Image
App.js
import AppNavigator from './navigation/AppNavigator';
import Header from './components/Header';
export default function App(props) {
const [isLoadingComplete, setLoadingComplete] = useState(false);
if (!isLoadingComplete && !props.skipLoadingScreen) {
return (
<AppLoading
startAsync={loadResourcesAsync}
onError={handleLoadingError}
onFinish={() => handleFinishLoading(setLoadingComplete)}
/>
);
} else {
return (
<View style={styles.container}>
<Header title="Header" />
{Platform.OS === 'ios' && <StatusBar backgroundColor="#C2185B" barStyle="default" />}
<AppNavigator />
</View>
);
}
}
Header.js
import React from 'react';
import { View, Text, StyleSheet } from 'react-native';
const Header = props => {
return (
<View style={styles.header}>
<Text style={styles.headerTitle}>MyStadium</Text>
</View>
)
};
const styles = StyleSheet.create({
header: {
width: '100%',
height:90,
backgroundColor: 'black',
paddingTop: 36,
alignItems: 'center',
justifyContent:'center',
},
headerTitle: {
color:'white',
fontSize: 18
}
});
export default Header;
And The AppNavigator is the base file in the tabs pre-config react-native project.
Thanks for your help
remove paddingTop: 36 from container styles
like below
const styles = StyleSheet.create({
header: {
width: '100%',
height: 90,
backgroundColor: 'black',
alignItems: 'center',
justifyContent:'center',
},
headerTitle: {
color:'white',
fontSize: 18
}
});
Ok I got this !
Just have to remove my Header.js and use de navigationOptions like this
HomeScreen.navigationOptions = {
title: 'MyStadium',
headerStyle: {
backgroundColor: 'black'
},
headerTintColor: 'white',
headerTitleStyle: {
fontWeight: 'bold'
}
};
In one of my components, I check the value of my props before deciding what component to return.
But the issue I am facing in testing is that the snapshot is not getting created properly for these components.
When I am testing a component on its own, the snapshot is created properly but not when my component checks prop value before returning a JSX.
This is my component:
import React, {Component} from 'react'
import {Button, Text, View, FlatList, StyleSheet, ActivityIndicator} from 'react-native'
import CategoryCell from '../Components/CategoryCell'
import { connect } from 'react-redux'
import { fetchQuotes } from '../actions/Quotes'
class QuotesCategories extends Component {
static navigationOptions = {
title: 'Categories',
}
render() {
return this.props.error ? (
<View style={styles.Container}>
<Text style={{color: 'red'}}>FAILED TO LOAD DATA</Text>
<Button
title='Reload'
onPress={this.props.fetchQuotes}
/>
</View>
) : this.props.loading ? (
<View style={styles.Container}>
<ActivityIndicator size="large"/>
</View>
) : (
<View style={styles.Container}>
<FlatList
style= {{flex:1, width: '100%'}}
data= {this.props.data}
renderItem = {({item,index}) => {
return (
<CategoryCell Category={item} navigation={this.props.navigation} id={index}/>
)
}}
keyExtractor = {(item, index) => item.category}
/>
<Text>Additions</Text>
</View>
)
}
}
const styles = StyleSheet.create({
Container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}
})
export const mapStateToProps = (state) => {
return {
loading: state.loading,
error: state.error,
data: state.data
}
}
export const mapDispatchToProps = (dispatch) => {
return {
fetchQuotes: () => {
dispatch(fetchQuotes())
}
}
}
export default connect(mapStateToProps,mapDispatchToProps)(QuotesCategories)
I am trying to test the three cases
When there is some error
When the data is loading
When the data has loaded
I am trying to test the three cases
error and loading is a boolean
data is an array of JSON objects
This is the test for the error case:
import React from 'react'
import {shallow} from 'enzyme'
import QuoteCategories from '../../Components/QuoteCategories'
import quotes from '../fixtures/quotes-fixture'
describe('Testing QuoteCategories component', () => {
it('should load error button when error loading', ( ) => {
const wrapper = shallow(
<QuoteCategories
loading = {false}
error = {true}
data = {undefined}
/>
)
expect(wrapper).toMatchSnapshot()
}
)
}
)
But in the QuoteCategories.test.js.snap file this is the snapshot I see:
exports[`Testing QuoteCategories component should load error button when error loading 1`] = `
<ContextConsumer>
<Component />
</ContextConsumer>
`;
Why am I seeing these tags <ContextConsumer>,<Component /> ?
In my other component test which directly returns a component, the snapshot is displaying properly:
My Component:
import React from 'react'
import { View, Text, TouchableHighlight, StyleSheet } from 'react-native'
const FavouriteQuoteCell = (props) => {
return (
<TouchableHighlight
onPress={() => props.navigation.navigate('Quotes',{id: props.item.parentId, category: props.item.category})}
style={styles.TableCell}
>
<View>
<Text style={styles.Quote}>{props.item.text}</Text>
<Text style={styles.Author}>-- {props.item.person}</Text>
<View style={styles.CategoryPill}>
<Text style={styles.Category}>
{props.item.category}
</Text>
</View>
</View>
</TouchableHighlight>
)
}
export default FavouriteQuoteCell
const styles = StyleSheet.create({
TableCell: {
backgroundColor: '#ff6347',
margin:5,
padding: 5,
justifyContent: 'space-around',
flexDirection: 'column',
flex: 1 ,
padding: 10,
margin: 5,
borderRadius: 15,
},
"Quote": {
fontWeight: 'bold',
color: 'white'
},
"Author": {
fontWeight:'200',
color:'white',
justifyContent: 'flex-end',
alignItems: 'flex-end',
height: 20
},
Category: {
color: '#ff6347',
fontWeight: 'bold',
fontSize: 12,
textTransform: 'capitalize',
margin: 'auto'
},
CategoryPill: {
marginTop: 10,
padding: 2,
height: 20,
borderRadius: 10,
backgroundColor: 'white',
width: 100,
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
})
The test:
import React from 'react'
import {shallow} from 'enzyme'
import FavouriteQuoteCell from '../../Components/FavouriteQuoteCell'
import {favouriteItem} from '../fixtures/favourites-fixture'
describe('testing FavouriteQuoteCell', () => {
let wrapper,navigation
beforeEach(() => {
navigation = {
navigate: jest.fn()
}
wrapper = shallow(<FavouriteQuoteCell navigation={navigation} item={favouriteItem}/>)
})
it('should render FavouriteQuoteCell correctly', () => {
expect(wrapper).toMatchSnapshot()
})
})
The snapshot:
exports[`testing FavouriteQuoteCell should render FavouriteQuoteCell correctly 1`] = `
<TouchableHighlight
activeOpacity={0.85}
delayPressOut={100}
onPress={[Function]}
style={
Object {
"backgroundColor": "#ff6347",
"borderRadius": 15,
"flex": 1,
"flexDirection": "column",
"justifyContent": "space-around",
"margin": 5,
"padding": 10,
}
}
underlayColor="black"
>
<View>
<Text
style={
Object {
"color": "white",
"fontWeight": "bold",
}
}
>
Believe you can and you"re halfway there
</Text>
<Text
style={
Object {
"alignItems": "flex-end",
"color": "white",
"fontWeight": "200",
"height": 20,
"justifyContent": "flex-end",
}
}
>
--
Theodore Roosevelt
</Text>
<View
style={
Object {
"alignItems": "center",
"backgroundColor": "white",
"borderRadius": 10,
"flex": 1,
"height": 20,
"justifyContent": "center",
"marginTop": 10,
"padding": 2,
"width": 100,
}
}
>
<Text
style={
Object {
"color": "#ff6347",
"fontSize": 12,
"fontWeight": "bold",
"margin": "auto",
"textTransform": "capitalize",
}
}
>
inspirational
</Text>
</View>
</View>
</TouchableHighlight>
`;
Your QuotesCategories component is connected to redux with:
export default connect(mapStateToProps,mapDispatchToProps)(QuotesCategories)
that is why when you are shallow rendering you see the redux wrapper component in the snapshot and not your QuotesCategories.
The usual why to fix this to also export your QuotesCategories and import it with its name in your tests:
So your component file should have two exports:
export class QuotesCategories extends Component {
...
}
export default connect(mapStateToProps,mapDispatchToProps)(QuotesCategories)
And in your test you should import QuotesCategories with:
import { QuoteCategories } from '../../Components/QuoteCategories'