invariant violation: App(...)Nothing was returned from render. this usually means a return statement is missing. react native - image

Good Day Every one! i just want to ask for your opinion why does my code display the error at the title. I've been working on it for 2 days but haven't resolve it yet. I hope somebody will help me. Thank you! Here is my actual Code. I'm using cloudinary where i save the image (which works perfectly) and firestore as my database. the error occurs when I apply Insert function on firestore for database.
import React, { useState, Component } from 'react'
import {
StyleSheet,
View,
Text,
Dimensions,
TouchableOpacity,
Image,
TextInput,
Platform,
ActivityIndicator, ScrollView, KeyboardAvoidingView, Picker
} from 'react-native';
import ImagePicker from 'react-native-image-picker';
import firebase from './firebase';
const App = (props) => {
const [photo, setPhoto] = useState('https://res.cloudinary.com/{my_Cloud_name}/image/upload/v1585540130/bg_3__1580384977_49.145.192.210_rnmved.jpg');
const selectPhotoTapped = () => {
const options = {
title: 'Select Photo',
storageOptions: {
skipBackup: true,
path: 'images',
},
};
ImagePicker.showImagePicker(options, (response) => {
console.log('Response = ', response);
if (response.didCancel) {
console.log('User cancelled image picker');
} else if (response.error) {
console.log('ImagePicker Error: ', response.error);
} else {
const source = {
uri: response.uri,
type: response.type,
name: response.fileName,
}
cloudinaryUpload(source)
console.log('Source: ', source);
console.log('cloudinary upload: ', photo);
return (photo);
}
});
}
const cloudinaryUpload = (photo) => {
const data = new FormData()
data.append('file', photo)
data.append('upload_preset', '{my_upload_present}/')
data.append("cloud_name", "{my_Cloud_name}/")
fetch("https://api.cloudinary.com/v1_1/{my_Cloud_name}//upload", {
method: "post",
body: data
}).then(res => res.json()).
then(data => {
setPhoto(data.secure_url)
}).catch(err => {
Alert.alert("An Error Occured While Uploading")
})
}
class AddPost extends Component {
state = {
image: '',
section: '',
unit: '',
price: '',
product: '',
status: '',
hasError: false,
errorText: '',
isLoading: false,
}
onChangeTitle = title => {
this.setState({ image })
this.setState({ section })
this.setState({ unit })
this.setState({ price })
this.setState({ product })
this.setState({ status })
}
onSubmit = async () => {
try {
const newDocumentData = this.ref.collection('products').doc().id;
this.setState({
loading: true,
});
const userId = firebase.auth().currentUser.uid;
this.ref.collection('products').doc(newDocumentData).set({
store_id: firebase.auth().currentUser.uid,
pr_name: this.state.product,
pr_id: newDocumentData,
pr_price: this.state.price,
pr_unit: this.state.unit,
pr_store_name: firebase.auth().currentUser.email, //change into name of store.
pr_section : this.state.section,
pr_image: this .state.image,
prod_status: 'active',
}).then((docRef) => {
this.setState({
image: '',
section: '',
unit: '',
price: '',
product: '',
status: '',
isloading: false,
});
Actions.gold();
})
} catch (e) {
console.error(e)
}
}
render() {
if (this.state.isLoading) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
return (
<View>
<View style={styles.imageContainer}>
<Text style={{fontSize: 20, textAlign: 'center', marginBottom: 7}}> Add Product </Text>
<ScrollView
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}>
<KeyboardAvoidingView enabled >
<Image source={{ uri: photo }} style={{
width: 200,
height: 200,
borderRadius: 100,
alignSelf: 'center',
}}/>
<TextInput
label="Enter Product"
placeholder="Enter Product"
label="Enter Product"
value={this.state.product}
onChangeText={product => this.onChangeTitle(product)}
underlineColorAndroid='transparent'
style={styles.TextInputStyleClass}
/>
<TextInput
placeholder="Enter Price"
keyboardType={'decimal-pad'}
value={this.state.price}
onChangeText={price => this.onChangeTitle(price)}
underlineColorAndroid='transparent'
style={styles.TextInputStyleClass}
/>
<Picker
style={{height: 50, width: 300}}
onValueChange={(TextInputValue, itemIndex) =>
this.setState({unit: TextInputValue})}>
<Picker.Item label = "Select Unit" />
<Picker.Item label = "Kilo" value = "Kilo" />
<Picker.Item label = "Each" value = "Each" />
<Picker.Item label = "Bottle" value = "Bottle" />
<Picker.Item label = "Pack" value = "Pack" />
<Picker.Item label = "Sack" value = "Sack" />
</Picker>
<Picker
style={{height: 50, width: 300}}
onValueChange={(TextInputValue, itemIndex) =>
this.setState({section: TextInputValue})}>
<Picker.Item label = "Select Section" />
<Picker.Item label = "Meat" value = "Meat" />
<Picker.Item label = "Vegetable" value = "Vegetable" />
<Picker.Item label = "Fruits" value = "Fruits" />
<Picker.Item label = "Biscuits" value = "Biscuits" />
<Picker.Item label = "Condiments" value = "Condiments" />
<Picker.Item label = "Canned Goods" value = "Canned Goods" />
<Picker.Item label = "Drinks" value = "Drinks" />
<Picker.Item label = "Diapers/Napkin" value = "Diapers/Napkin" />
<Picker.Item label = "Frozen Products" value = "Frozen Products" />
<Picker.Item label = "Junk Foods" value = "Junk Foods" />
<Picker.Item label = "Milk" value = "Milk" />
<Picker.Item label = "Soap/Shampoo" value = "Soap/Shampoo" />
<Picker.Item label = "Pesonal Items" value = "Pesonal Items" />
<Picker.Item label = "Pasta/Noodleslete" value = "Pasta/Noodles" />
</Picker>
<TextInput
placeholder="Enter Description"
multiline={true}
numberOfLines={4}
label="Enter Product"
value={this.state.Description}
onChangeText={Description => this.onChangeTitle(Description)}
underlineColorAndroid='transparent'
style={styles.TextInputStyleClass}
/>
<TextInput
placeholder="Enter image"
value={ photo }
onChangeText={image => this.onChangeTitle(image)}
underlineColorAndroid='transparent'
style={styles.TextInputStyleClass}
/>
<TouchableOpacity onPress={selectPhotoTapped} style={styles.uploadButton}>
<Text style={styles.uploadButtonText}>Upload</Text>
</TouchableOpacity>
<TouchableOpacity activeOpacity = { .4 } style={styles.uploadButton} >
<Text style={styles.TextStyle}> INSERT PRODUCT TO SERVER </Text>
</TouchableOpacity>
<TouchableOpacity activeOpacity = { .4 } style={styles.uploadButton} >
<Text style={styles.TextStyle}> SHOW ALL INSERTED PRODUCTS RECORDS</Text>
</TouchableOpacity>
<TouchableOpacity activeOpacity = { .4 } >
<Text style={styles.TextStyle}> </Text>
</TouchableOpacity>
</KeyboardAvoidingView>
</ScrollView>
</View>
</View >
);
};
}
}
export default App;
const styles = StyleSheet.create({
imageContainer: {
height: Dimensions.get('window').height
},
backgroundImage: {
flex: 1,
resizeMode: 'cover',
},
uploadContainer: {
backgroundColor: 'white',
borderTopLeftRadius: 45,
borderTopRightRadius: 45,
position: 'absolute',
bottom: 0,
width: Dimensions.get('window').width,
height: 200,
},
uploadContainerTitle: {
alignSelf: 'center',
fontSize: 25,
margin: 20,
fontFamily: 'Roboto'
},
uploadButton: {
borderRadius: 16,
alignSelf: 'center',
shadowColor: "#000",
shadowOffset: {
width: 7,
height: 5,
},
shadowOpacity: 1.58,
shadowRadius: 9,
elevation: 4,
margin: 10,
padding: 10,
backgroundColor: '#fe5b29',
width: Dimensions.get('window').width - 60,
alignItems: 'center'
},
uploadButtonText: {
color: '#f6f5f8',
fontSize: 20,
fontFamily: 'Roboto'
},
MainContainer :{
alignItems: 'center',
flex:1,
paddingTop: 30,
},
MainContainer_For_Show_StudentList_Activity :{
flex:1,
paddingTop: (Platform.OS == 'ios') ? 20 : 0,
marginLeft: 5,
marginRight: 5
},
TextInputStyleClass: {
textAlign: 'center',
width: '90%',
marginBottom: 7,
height: 40,
borderWidth: 1,
borderColor: '#FF5722',
borderRadius: 5 ,
},
TouchableOpacityStyle: {
paddingTop:10,
paddingBottom:10,
borderRadius:5,
marginBottom:7,
width: '90%',
backgroundColor: '#00BCD4'
},
TextStyle:{
color:'#fff',
textAlign:'center',
},
rowViewContainer: {
fontSize: 20,
paddingRight: 10,
paddingTop: 10,
paddingBottom: 10,
},
});

The return should have your JSX code in it to return Something.
Your current return has JSX code but it is all under your AddPost class while your App function has nothing to return.
You are using useState which is used for functional components and setState which is used for class components combined. I suggest you should use only one type of component at a time in a project to make your code simpler and better.
To make this work I suggest you should add your AddPost class under App's return.
eg
const App = (props) => {
//somewhere here
return(
//only if this is your initial file
//else import other file and write followingly
<AddPost/>
)
export default App;

Related

Getting undeifined value when passing data from one screen to another screen in react-native

When I am trying to pass data from Login screen to MyProfile screen then I got undefined value.
I am confused that why I am getting undefined value ?
Here is code of my main navigation files.
route.js
import 'react-native-gesture-handler';
import * as React from 'react';
import { NavigationContainer, getFocusedRouteNameFromRoute } from '#react-navigation/native';
import { createStackNavigator } from '#react-navigation/stack';
import { createBottomTabNavigator } from "#react-navigation/bottom-tabs";
import { createDrawerNavigator } from "#react-navigation/drawer";
import LoginScreen from "./../screens/Login/index.js";
import MyProfileScreen from "../screens/MyProfile/index.js";
import AboutUsScreen from "../screens/AboutUs/index.js";
import SettingScreen from "../screens/Setting/index.js";
import { Image, StyleSheet, View, TouchableOpacity } from "react-native";
import { heightPercentageToDP as hp, widthPercentageToDP as wp } from 'react-native-responsive-screen';
import { RFValue } from "react-native-responsive-fontsize"
const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();
const Drawer = createDrawerNavigator();
const NavigationDrawerStructure = (props) => {
const toggleDrawer = () => {
props.navigationProps.toggleDrawer();
}
return (
<View style={{ flexDirection: 'row' }}>
<TouchableOpacity onPress={() => toggleDrawer()}>
<Image
source={{ uri: 'https://raw.githubusercontent.com/AboutReact/sampleresource/master/drawerWhite.png' }}
style={{
width: 25,
height: 25,
marginLeft: 5
}}
/>
</TouchableOpacity>
</View>
)
}
const geHeaderTitle = (route) => {
const routeName = getFocusedRouteNameFromRoute(route) ?? 'MyProfileScreen';
switch (routeName) {
case 'MyProfileScreen':
return 'Profile';
case 'AboutUsScreen':
return 'AboutUs';
case 'SettingScreen':
return 'Setting';
}
}
const BottomTab = () => {
return (
<Tab.Navigator initialRouteName="MyProfileScreen"
tabBarOptions={{
activeTintColor: "red",
labelStyle: {
fontSize: RFValue('14'),
marginTop: 5
},
style: { height: hp('11') }
}}
>
<Tab.Screen
name="MayProfileScreen"
component={MyProfileScreen}
options={{
tabBarLabel: 'Profile',
tabBarIcon: ({ focused }) => (
focused ?
<Image source={require('./../../asstes/images/profile.png')} style={styles.activeImg} /> :
<Image source={require('./../../asstes/images/profile.png')} style={styles.deActiveImg} />
)
}}
/>
<Tab.Screen
name="AboutUsScreen"
component={AboutUsScreen}
options={{
tabBarLabel: 'AboutUs',
tabBarIcon: ({ focused }) => (
focused ?
<Image source={require('./../../asstes/images/aboutus.png')} style={styles.activeImg} /> :
<Image source={require('./../../asstes/images/aboutus.png')} style={styles.deActiveImg} />
)
}}
/>
<Tab.Screen
name="SettingScreen"
component={SettingScreen}
options={{
tabBarLabel: 'Setting',
tabBarIcon: ({ focused }) => (
focused ?
<Image source={require('./../../asstes/images/setting.png')} style={styles.activeImg} /> :
<Image source={require('./../../asstes/images/setting.png')} style={styles.deActiveImg} />
)
}}
/>
</Tab.Navigator>
)
}
const HomeStack = ({ navigation }) => {
return (
<Stack.Navigator initialRouteName="LoginScreen">
<Stack.Screen name="LoginScreen" component={LoginScreen} options={{ headerShown: false }} />
<Stack.Screen name="MyProfileScreen" component={BottomTab}
options={({ route }) => ({
headerTitle: geHeaderTitle(route),
headerLeft: () => (
<NavigationDrawerStructure navigationProps={navigation} />
),
title: 'Profile',
headerStyle: { backgroundColor: '#f4511e' },
headerTintColor: '#fff',
headerTitleStyle: { fontWeight: 'bold' }
})}
/>
<Stack.Screen name="AboutUsScreen" component={AboutUsScreen}
options={{
title: 'AboutUS',
headerStyle: { backgroundColor: '#f4511e' },
headerTintColor: '#fff',
headerTitleStyle: { fontWeight: 'bold' }
}} />
<Stack.Screen name="SettingScreen" component={SettingScreen}
options={{
title: 'Setting',
headerStyle: { backgroundColor: '#f4511e' },
headerTintColor: '#fff',
headerTitleStyle: { fontWeight: 'bold' }
}}
/>
</Stack.Navigator>
)
}
const AboutUsStack = ({ navigation }) => {
return (
<Stack.Navigator initialRouteName="AboutUsScreen"
screenOptions={{
headerLeft: () => (
<NavigationDrawerStructure navigationProps={navigation} />
),
headerStyle: { backgroundColor: '#f4511e' },
headerTintColor: '#fff',
headerTitleStyle: { fontWeight: 'bold' }
}}
>
<Stack.Screen name="AboutUsScreen" component={AboutUsScreen} options={{ title: 'AboutUs' }} />
</Stack.Navigator>
)
}
const SettingStack = ({ navigation }) => {
return (
<Stack.Navigator initialRouteName="SettingScreen"
screenOptions={{
headerLeft: () => (
<NavigationDrawerStructure navigationProps={navigation} />
),
headerStyle: { backgroundColor: '#f4511e' },
headerTintColor: '#fff',
headerTitleStyle: { fontWeight: 'bold' }
}}
>
<Stack.Screen name="SettingScreen" component={SettingScreen} options={{ title: 'Setting', }} />
</Stack.Navigator>
)
}
const Navigation = () => {
return (
<NavigationContainer>
<Drawer.Navigator
drawerContentOptions={{
activeTintColor: '#e91e63',
itemStyle: { marginVertical: 5 }
}}
>
<Drawer.Screen name="HomeStack" options={{ drawerLabel: 'Profile' }} component={HomeStack} />
<Drawer.Screen name="AboutUsStack" component={AboutUsStack} options={{ drawerLabel: 'AboutUs' }} />
<Drawer.Screen name="SettingStack" component={SettingStack} options={{ drawerLabel: 'Setting' }} />
</Drawer.Navigator>
</NavigationContainer>
)
}
const styles = StyleSheet.create({
activeImg: {
height: hp('4.8'), width: wp('8.5'), marginTop: 10, borderRadius: 12, tintColor: 'red'
},
deActiveImg: {
height: hp('4.8'), width: wp('8.5'), marginTop: 10, borderRadius: 12, tintColor: 'gray'
}
})
export default Navigation;
I am calling below function when user click on Login button.
Here is some lines of code that how I am trying to pass data from Login screen to MyProfile screen
Login screen
const resetTextInput = () => {
setName(null);
setPassword(null);
navigation.navigate('MyProfileScreen', { userName: name, userPwd: password,});
}
<TouchableOpacity style={styles.loginBtn} onPress={() => { resetTextInput() }}>
<Text style={styles.loginBtnTxt}>Login</Text>
</TouchableOpacity>
Here is some lines of code that how I am trying to get data from Login screen to MyProfile screen.
MyProfle screen
useEffect(() => {
console.log("username is-->",JSON.stringify(route?.params?.userName));
console.log("userpassword is-->",JSON.stringify(route?.params?.userPwd));
});
Perhaps pass route.params? as an argument inside your useEffect?
useEffect(() => {
...
},[route.params?]);
That way it'll apply the effect when you navigate into it.
Do you have a multiple stack navigator? If you are at different stack navigator and you want to pass params to a screen from different stack navigator, you have to specify the screen.
For example you are at the AuthStack and you want to navigate to MyProfileScreen from the ProfileStack:
navigation.navigate('ProfileStack', { screen: 'MyProfileScreen', params: { //data here } });
if not, I think you got a typo in your BottomTab component. Change the name of the first tab from "MayProfileScreen" to "MyProfileScreen".
then use this to navigate:
navigation.navigate('MyProfileScreen', { //params here });

Easy way to add a drawer item with custom onPress?

I'm using DrawerNavigator and, as long as I want to navigate to a new screen, all's fine.
Now I want to add a drawer item which does not navigate to a new screen but simply triggers an action (in general). Specifically, I want to use 'react-native' Share functionality.
I got this to work but I think the solution is not a very good one. Here's what I got so far:
const myContentComponent = props => (
<ScrollView alwaysBounceVertical={false}>
<SafeAreaView style={{ flex: 1 }} forceInset={{ top: 'always', horizontal: 'never' }}>
<DrawerItems {...props} />
<TouchableItem
key="share"
onPress={() => {
Share.share(
{
message: 'YO: this will be the text message',
url: 'http://tmp.com',
title: 'This will be the email title/subject',
},
{
// Android only:
dialogTitle: 'This will be the title in the dialog to choose means of sharing',
},
);
props.navigation.navigate('DrawerClose');
}}
delayPressIn={0}
>
<SafeAreaView forceInset={{ left: 'always', right: 'never', vertical: 'never' }}>
<View style={[{ flexDirection: 'row', alignItems: 'center' }, {}]}>
<View style={[{ marginHorizontal: 16, width: 24, alignItems: 'center' }, { opacity: 0.62 }, {}]}>
<Icon name="share" />
</View>
<Text style={[{ margin: 16, fontWeight: 'bold' }, { color: DrawerItems.defaultProps.inactiveTintColor }]}>Share</Text>
</View>
</SafeAreaView>
</TouchableItem>
</SafeAreaView>
</ScrollView>
);
const RootStack = DrawerNavigator(
{
Settings: {
screen: SettingsScreen,
},
},
{
contentComponent: myContentComponent,
},
);
Basically, I am creating a new contentComponent based off of the default:
https://github.com/react-navigation/react-navigation/blob/v1.5.8/src/navigators/DrawerNavigator.js#L16-L22
I am copying the styles and element structure of a normal drawer item (everything under TouchableItem) - all this so I can define my own onPress which does the share logic and closes the drawer.
There has to be a better way right? What happens if I want the "Share" drawer item somewhere amongst the drawer items rendered by DrawerItems (the ones that support navigation)? Right now I can only work around the items rendered by DrawerItems. Besides, copying so much code from react-navigation seems like really bad form.
I just want an item which does some custom logic instead of rendering a screen.
I am not sure will it be helpful?
I used onPress event for logout like this way. It's no need to render a new DrawerItems
const DrawerNavigator = createAppContainer(createDrawerNavigator({
Logout: {
screen: EmptyScreenForLogoutConfirmation,
navigationOptions: ({ navigation }) => ({
tabBarLabel: 'Logout',
drawerIcon: ({ tintColor }) => <Icon name={"ios-cog"} size={26} />,
}),
},
},
{
contentComponent:(props ) => (
<DrawerItems {...props}
onItemPress = {
( route ) =>
{
if (route.route.routeName !== "Logout") {
props.onItemPress(route);
return;
}
return Alert.alert( // Shows up the alert without redirecting anywhere
'Confirmation required'
,'Do you really want to logout?'
,[
{text: 'No'},
{text: 'Yes', onPress: () => { props.navigation.navigate('Logedout'); }},
]
)
}
}
/>
),
contentOptions: {
activeTintColor: '#e91e63',
}
},))

Why is react-native-material-dropdown so slow in the way I use it to edit and add majors?

My app drop-downs are pretty slow. It especially takes a long time if i go into the Settings Screen and try to change my major. Or anything dealing with adding or editing a major in this specific screen. I really need to make adding or editing a major much faster. I am not sure the reason why editing or adding a major is taking too long. And I am not sure about how to go at making adding and editing a major much faster. So I am posting the entire component and I am commenting parts that deal with adding or editing a major. I am using react-native-material-dropdown.
Here is the entire component code:
import React from 'react';
import PropTypes from 'prop-types';
import { View, ScrollView, Text, TouchableOpacity } from 'react-native';
import { connect } from 'react-redux';
import { compose, withStateHandlers } from 'recompose';
import { Dropdown } from 'react-native-material-dropdown';
import { Icon } from 'react-native-material-ui';
import R from 'ramda';
import { ConnectivityRenderer } from 'react-native-offline';
import NetworkConnectivity from '../error/NetworkConnectivity';
import { toArray } from '../selectors';
import { Container, Switch, SwitchOption } from './common';
import { editStudent } from '../actions';
const propTypes = {
toolbar: PropTypes.elem,
loading: PropTypes.bool,
university: PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,
}),
universities: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,
})
),
degrees: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string,
})
),
studentDegrees: PropTypes.arrayOf(
PropTypes.shape({
index: PropTypes.number,
track: PropTypes.string,
})
),
errors: PropTypes.shape({
university: PropTypes.string,
degree: PropTypes.string,
}),
year: PropTypes.string,
onUniversityChange: PropTypes.func,
onTermChange: PropTypes.func,
onDegreeChange: PropTypes.func,
onYearChange: PropTypes.func,
onTrackChange: PropTypes.func,
onAddDegree: PropTypes.func,
onDone: PropTypes.func,
};
const contextTypes = {
uiTheme: PropTypes.object.isRequired,
};
const validate = state => {
const result = {};
if (!state.university.id) {
result.university = 'You should select an university';
}
return result;
};
const enhance = compose(
connect(
({ user, universities, degrees }) => ({
studentId: user.id,
user,
universities: toArray(universities),
degrees: toArray(degrees),
}),
{ editStudent }
),
withStateHandlers(
props => {
return {
university: props.user.university || {},
year: props.user.academicClass || 'freshman',
studentDegrees: R.isEmpty(props.degrees)
? []
: R.isEmpty(props.user.studentDegrees)
? [{ degree_id: props.degrees[0].id, track: 'Major' }]
: R.values(props.user.studentDegrees),
errors: {},
};
},
{
onUniversityChange: () => (value, index, data) => ({
university: data[index],
}),
onYearChange: () => year => ({ year }),
onTrackChange: state => ({ idx, track }) => ({
studentDegrees: R.update(
idx,
R.assoc('track', track, state.studentDegrees[idx]),
state.studentDegrees
),
}),
// Fucntion dealing with degree change
onDegreeChange: (state, props) => ({ idx, index }) => ({
studentDegrees: R.update(
idx,
R.assoc(
'degree_id',
props.degrees[index].id,
state.studentDegrees[idx]
),
state.studentDegrees
),
}),
// Function dealing with degree adding
onAddDegree: (state, props) => () => ({
studentDegrees: R.append(
{
degree_id: props.degrees[0].id,
track: 'Major',
},
state.studentDegrees
),
}),
onRemoveDegree: state => idx => ({
studentDegrees: [
...state.studentDegrees.slice(0, idx),
...state.studentDegrees.slice(idx + 1),
],
}),
// When the user is done with settings.
// This function communicates with the back end to save things in the remote database
onDone: (state, { studentId, editStudent }) => () => {
const errors = validate(state);
if (Object.keys(errors).length !== 0) {
return { errors };
}
editStudent(
studentId,
state.year,
state.university.id,
state.studentDegrees
);
},
}
)
);
// The Settings Component
const FormUserSettings = (props, context) => {
const styles = getStyles(props, context);
return (
<ConnectivityRenderer>
{isConnected => (
isConnected ? (
<Container>
{React.cloneElement(props.toolbar, {
onRightElementPress: props.onDone,
})}
<ScrollView style={styles.container}>
<Text
style={[
styles.title,
props.errors.university ? styles.titleError : {},
]}
>
University
</Text>
// Selecting a university
<Dropdown
label="Select university..."
data={props.universities.map(u => ({ id: u.id, value: u.name }))}
onChangeText={props.onUniversityChange}
value={props.university.name}
/>
{props.errors.university &&
<Text style={styles.errorMessage}>
{props.errors.university}
</Text>}
<View style={{ height: 16 }} />
<Text style={styles.title}>Current Year</Text>
<View style={{ height: 8 }} />
<Switch
value={props.year}
onChange={props.onYearChange}
selectedColor={styles.switchSelectedColor}
unselectedColor={styles.switchUnselectedColor}
>
<SwitchOption text="Freshman" value="freshman" />
<SwitchOption text="Sophomore" value="sophomore" />
<SwitchOption text="Junior" value="junior" />
<SwitchOption text="Senior" value="senior" />
</Switch>
<View style={{ height: 16 }} />
<Text
style={[styles.title, props.errors.degree ? styles.titleError : {}]}
>
Major / Minors
</Text>
{!R.isEmpty(props.degrees) &&
props.studentDegrees.map((sd, idx) => {
const degree = R.find(R.propEq('id', sd.degree_id), props.degrees);
return (
<View
key={`sd-${idx}`}
style={{ flex: 1, height: 96, marginTop: 24 }}
>
<View
style={{
flex: 1,
flexDirection: 'row',
alignItems: 'flex-end',
}}
>
<View style={{ flex: 1 }}>
<Dropdown
style={{ flex: 1 }}
label="Select degree..."
data={props.degrees.map(d => ({
id: d.id,
value: d.name,
}))}
onChangeText={(value, index) =>
props.onDegreeChange({ idx, index })}
value={degree ? degree.name : ''}
/>
</View>
{props.studentDegrees.length !== 1 &&
<TouchableOpacity
style={{ marginBottom: 8, paddingLeft: 24 }}
onPress={() => props.onRemoveDegree(idx)}
>
<Icon name="delete" size={24} />
</TouchableOpacity>}
</View>
<Switch
value={sd.track}
onChange={track => props.onTrackChange({ idx, track })}
selectedColor={styles.switchSelectedColor}
unselectedColor={styles.switchUnselectedColor}
>
<SwitchOption text="Major" value="Major" />
<SwitchOption text="Minor" value="Minor" />
<SwitchOption text="Certificate" value="Cert" />
</Switch>
</View>
);
})}
<TouchableOpacity style={{ padding: 10 }} onPress={props.onAddDegree}>
<Text style={styles.addDegreeText}>+ Degree</Text>
</TouchableOpacity>
</ScrollView>
</Container>
) : (
<Container>
{React.cloneElement(props.toolbar, {
onRightElementPress: props.onDone,
})}
<NetworkConnectivity />
<ScrollView style={styles.container}>
<Text
style={[
styles.titleDisabled,
props.errors.university ? styles.titleError : {},
]}
>
University
</Text>
<Dropdown
label=""
data={props.universities.map(u => ({ id: u.id, value: u.name }))}
onChangeText={props.onUniversityChange}
value={props.university.name}
disabled={true}
editable={false}
/>
{props.errors.university &&
<Text style={styles.errorMessage}>
{props.errors.university}
</Text>}
<View style={{ height: 16 }} />
<Text style={styles.titleDisabled}>Current Year</Text>
<View style={{ height: 8 }} />
<Switch
value={props.year}
onChange={props.onYearChange}
selectedColor={styles.disabledSwitchSelectedColor}
unselectedColor={styles.switchUnselectedColor}
>
<SwitchOption text="Freshman" value="freshman" />
<SwitchOption text="Sophomore" value="sophomore" />
<SwitchOption text="Junior" value="junior" />
<SwitchOption text="Senior" value="senior" />
</Switch>
<View style={{ height: 16 }} />
<Text
style={[styles.titleDisabled, props.errors.degree ? styles.titleError : {}]}
>
Major / Minors
</Text>
// The problem of slowness starts here
// I feel like something here should be improved
{!R.isEmpty(props.degrees) &&
props.studentDegrees.map((sd, idx) => {
const degree = R.find(R.propEq('id', sd.degree_id), props.degrees);
return (
<View
key={`sd-${idx}`}
style={{ flex: 1, height: 96, marginTop: 24 }}
>
<View
style={{
flex: 1,
flexDirection: 'row',
alignItems: 'flex-end',
}}
>
<View style={{ flex: 1 }}>
<Dropdown
style={{ flex: 1 }}
label="Select degree..."
data={props.degrees.map(d => ({
id: d.id,
value: d.name,
}))}
disabled={true}
editable={false}
onChangeText={(value, index) =>
props.onDegreeChange({ idx, index })}
value={degree ? degree.name : ''}
/>
</View>
</View>
<Switch
value={sd.track}
onChange={track => props.onTrackChange({ idx, track })}
selectedColor={styles.disabledSwitchSelectedColor}
unselectedColor={styles.switchUnselectedColor}
>
<SwitchOption text="Major" value="Major" />
<SwitchOption text="Minor" value="Minor" />
<SwitchOption text="Certificate" value="Cert" />
</Switch>
</View>
);
})}
<TouchableOpacity disabled={true} style={{ padding: 10 }} onPress={props.onAddDegree} disabled={true}>
<Text style={styles.addDegreeTextDisabled}>+ Degree</Text>
</TouchableOpacity>
</ScrollView>
</Container>
)
)}
</ConnectivityRenderer>
);
};
FormUserSettings.contextTypes = contextTypes;
FormUserSettings.propTypes = propTypes;
export default enhance(FormUserSettings);

React Native sticky row and header scroll performance?

I have cobbled together a working version of a Microsoft Excel like "freeze pains" view. The column header scrolls with the content horizontally and the row headers scroll with the content vertically but each is "stuck" in position when the other is scrolled.
You can try the working version here.
It's not optimal as it stutters if you stop a flicked scroll or just swipe around a lot.
The approach uses a couple techniques but the one causing the issue is the synced scroll view.
As outlined here, I've tried setting useNativeDriver: true, which necessitates changing
ScrollView to Animated.ScrollView and
ref={ref => (this.instance = ref)} to ref={ref => (this.instance = ref._component)}
but then the synced goes completely haywire.
I'd love ideas on a more optimal approach. How can this be improved?
import React from 'react';
import { ScrollView, Animated, Text, View } from 'react-native';
export default class SyncScrollTest extends React.Component {
constructor() {
super();
this.scrollPosition = new Animated.Value(0);
this.scrollEvent = Animated.event(
[{ nativeEvent: { contentOffset: { y: this.scrollPosition } } }],
{ useNativeDriver: false },
);
}
render() {
return (
<View style={{ flex: 1 }}>
<View style={{ flexDirection: 'row' }}>
<ScrollViewVerticallySynced
style={{ width: 50, marginTop: 60 }}
name="C1"
color="#F2AFAD"
onScroll={this.scrollEvent}
scrollPosition={this.scrollPosition}
/>
<ScrollView horizontal bounces={false}>
<View style={{ width: 600 }}>
<View style={{ height: 60, justifyContent: 'center', backgroundColor: '#B8D2EC' }}>
<Text>
I am Column Header!! I am Column Header!! I am Column Header!! I am Column
Header!! I am Column Header!! I am Column Header!! I am Column Header!!
</Text>
</View>
<ScrollViewVerticallySynced
style={{ width: 600 }}
name="C2"
color="#D9E4AA"
onScroll={this.scrollEvent}
scrollPosition={this.scrollPosition}
/>
</View>
</ScrollView>
</View>
</View>
);
}
}
class ScrollViewVerticallySynced extends React.Component {
componentDidMount() {
this.listener = this.props.scrollPosition.addListener((position) => {
this.instance.scrollTo({
y: position.value,
animated: false,
});
});
}
render() {
const { name, color, style, onScroll } = this.props;
return (
<ScrollView
key={name}
ref={ref => (this.instance = ref)}
style={style}
scrollEventThrottle={1}
onScroll={onScroll}
bounces={false}
showsVerticalScrollIndicator={false}
>
{someRows(name, 25, color)}
</ScrollView>
);
}
}
const someRows = (name, rowCount, color) =>
Array.from(Array(rowCount).keys()).map(index =>
(<View
key={`${name}-${index}`}
style={{
height: 50,
backgroundColor: index % 2 === 0 ? color : 'white',
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}}
>
<Text>
{name} R{index + 1}
</Text>
</View>),
);
```
I've changed your example, instead of using listeners and Animated Event I use the scrollTo method from ScrollView to synchronize the scrolling. I think that listeners are the cause of lag between the rows when you are scrolling.
You can test the changes here.
import React from 'react';
import { ScrollView, Text, View } from 'react-native';
import { Constants } from 'expo'
export default class SyncScrollTest extends React.Component {
constructor() {
super();
this.c1IsScrolling = false;
this.c2IsScrolling = false;
}
render() {
return (
<View style={{ flex: 1, marginTop: Constants.statusBarHeight }}>
<View style={{ flexDirection: 'row' }}>
<ScrollViewVerticallySynced
style={{ width: 50, marginTop: 60 }}
refe= {ref => (this.c2View = ref)}
name="C1"
color="#F2AFAD"
onScroll={e => {
if (!this.c1IsScrolling) {
this.c2IsScrolling = true;
var scrollY = e.nativeEvent.contentOffset.y;
this.c1View.scrollTo({ y: scrollY });
}
this.c1IsScrolling = false;
}}
/>
<ScrollView horizontal bounces={false}>
<View style={{ width: 400 }}>
<View style={{ height: 60, justifyContent: 'center', backgroundColor: '#B8D2EC' }}>
<Text>
I am Column Header!! I am Column Header!! I am Column Header!! I am Column
Header!! I am Column Header!! I am Column Header!! I am Column Header!!
</Text>
</View>
<ScrollViewVerticallySynced
style={{ width: 400 }}
refe= {ref => (this.c1View = ref)}
name="C2"
color="#D9E4AA"
onScroll= {e => {
if (!this.c2IsScrolling) {
this.c1IsScrolling = true;
var scrollY = e.nativeEvent.contentOffset.y;
this.c2View.scrollTo({ y: scrollY });
}
this.c2IsScrolling = false;
}}
/>
</View>
</ScrollView>
</View>
</View>
);
}
}
class ScrollViewVerticallySynced extends React.Component {
render() {
const { name, color, style, onScroll, refe } = this.props;
return (
<ScrollView
key={name}
ref={refe}
style={style}
scrollEventThrottle={1}
onScroll={onScroll}
bounces={false}
showsVerticalScrollIndicator={false}
>
{someRows(name, 25, color)}
</ScrollView>
);
}
}
const someRows = (name, rowCount, color) =>
Array.from(Array(rowCount).keys()).map(index =>
(<View
key={`${name}-${index}`}
style={{
height: 50,
backgroundColor: index % 2 === 0 ? color : 'white',
flex: 1,
alignItems: 'center',
justifyContent: 'center',
}}
>
<Text>
{name} R{index + 1}
</Text>
</View>),
);
You can find another example here

Unable to store values in asyncStorage in React Native

The value set in asyncstorage is null and also the log for usertext is { [TypeError: undefined is not an object (evaluating 'this.state.usertext')] line: 30, column: 34 }
what might be the problem here?
'use strict'
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Navigator,
Text,
TextInput,
Button,
AsyncStorage,
View
} from 'react-native';
import { Actions } from 'react-native-router-flux';
import api from './utilities/api';
export default class LoginScreen extends Component{
constructor(props) {
super(props);
this.state = { usertext: 'placeholder' , passwordtext: 'placeholder' ,buttonvalue: '
Login'};
}
async login() {
const res = await api.getLoginResult();
const status= res.status;
console.log('log'+status);
if(status==='success'){
try {
console.log('usertext'+this.state.usertext);
await AsyncStorage.setItem('username', this.state.usertext);
var username = await AsyncStorage.getItem('username');
console.log('username'+username);
await AsyncStorage.setItem('password', this.state.passwordtext);
} catch (error) {
// Error saving data
console.log(error);
}
Actions.HomeScreen();
}
}
render() {
return (
<View style={styles.container}>
<View style={styles.containerLogin}>
<View style={styles.headerContainer}>
<Text style={styles.welcome}>PLEASE LOGIN</Text>
</View>
<View style={styles.inputlayouts}>
<TextInput
onChangeText={(usertext) => this.setState({usertext})}
value={this.state.usertext}
/>
<TextInput
onChangeText={(passwordtext) => this.setState({passwordtext})}
value={this.state.passwordtext}
/>
</View>
<View style={styles.buttonView}>
<Button
style={styles.buttonstyle}
onPress={this.login}
title={this.state.buttonvalue}
color="#FF7323">
</Button>
</View>
</View>
</View>
)
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'stretch',
backgroundColor: '#1AB591',
},
headerContainer:{
backgroundColor:'#FF7323'
},
buttonView:{
marginLeft:15,
marginRight:15,
marginBottom:15
},
inputlayouts:{
marginLeft:6,
marginRight:6,
marginBottom:10,
},
containerLogin: {
justifyContent: 'center',
alignItems: 'stretch',
backgroundColor: '#FFF',
marginLeft:20,
marginRight:20
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
color:'#FFF'
},
buttonstyle: {
width:20,
}
});
Change
<Button
style={styles.buttonstyle}
onPress={this.login}
title={this.state.buttonvalue}
color="#FF7323">
</Button>
to
<Button
style={styles.buttonstyle}
onPress={this.login.bind(this)}
title={this.state.buttonvalue}
color="#FF7323">
</Button>
When you do onPress={this.login}, the method reference to this will be different than your component. Binding this to method, allows you to reference your this in the function.

Resources