Force BottomSheetFlatList open from first position every time useCallback called in react-native-bottom-sheet - react-native

I am using react-native-bottom-sheet with BottomSheetFlatList.
I want to open BottomSheetFlatList at initialPosition every time it opened but I am not able to achieve that. When I open it it's always open at last scrolling position.
<BottomSheet
ref={bottomSheetRef}
index={0}
snapPoints={snapPoints}
enableDismissOnClose={true}
handleIndicatorStyle={{display: 'none'}}
handleStyle={{padding: 0}}>
<View style={{backgroundColor: '#b2b2b2', alignItems: 'center'}}>
{!isOpen && (
<Pressable onPress={() => handleSnapPress(1)}>
<Image
source={IconCollapse}
style={{width: 30, height: 20, backgroundColor: '#b2b2b2'}}
/>
</Pressable>
)}
{isOpen && (
<Pressable onPress={() => handleClosePress()}>
<Image
source={IconExpand}
style={{width: 30, height: 20, backgroundColor: '#b2b2b2'}}
/>
</Pressable>
)}
</View>
<View style={{backgroundColor: '#b2b2b2'}}>
<Text
style={{
color: 'white',
fontSize: 20,
textTransform: 'uppercase',
paddingStart: 10,
}}>
Select Zone
</Text>
<View
style={{
borderBottomColor: '#fff',
borderBottomWidth: 1,
marginEnd: 10,
marginStart: 10,
}}
/>
</View>
<BottomSheetFlatList
data={zoneList}
keyExtractor={(item, index) => `${index}`}
renderItem={renderItem}
contentContainerStyle={styles.contentContainer}
/>
</BottomSheet>

you can try
this.flatListRef.scrollToOffset({ animated: true, offset: 0 });
hope this is work

use Prop enableDismissOnClose
as mentioned here

As Mohammad Momin said you can try scrollToIndex({index: 0, offset: 0}).
In order to do that you have to declare a ref for your FlatList and also specify the getItemLayout prop. Then you can call scrollToIndex.
So every time that you open the bottomSheet you have to call scrollToIndex.
The full working example I created is available in this github repositor which is something like this:
export const CustomBottomSheet = forwardRef(({
zoneList = [],
selectedZone,
onZoneSelected
}, ref) => {
const bottomSheetRef = useRef(null);
const flatListRef = useRef(null);
const scrollToItem = () => {
if (!selectedZone) return;
if (zoneList.length < 1) return;
// find item index
const index = zoneList.findIndex(value => value.id === selectedZone?.id);
// scroll to destination index
// it's better to set animated to true (experimental)
flatListRef.current.scrollToIndex({
animated: true,
index: index, // 0
})
console.log('scrollToItem called by index:' + index)
}
const handleSnapPress = (index = 0) => {
bottomSheetRef.current.snapToIndex(index);
// call this method after each time user opens the bottom sheet
scrollToItem();
}
const handleClosePress = () => {
bottomSheetRef.current.snapToIndex(index);
}
const renderItem = ({ item }) => {
return (
<ListItem
{...item}
onPress={() => onZoneSelected(item)}
isSelected={item.id === selectedZone?.id}
/>
)
}
const getItemLayout = (_, index) => (
{
length: ListItem.HEIGHT,
offset: (ListItem.HEIGHT + ListItem.VERTICAL_SPACING) * index,
index: index,
}
)
// forwarding methods via ref
useImperativeHandle(ref, () => ({
open: handleSnapPress,
close: handleClosePress,
snapToIndex: handleSnapPress,
}));
return (
<BottomSheet
ref={bottomSheetRef}
index={-1}
snapPoints={SNAP_POINTS}
enableDismissOnClose={true}
// onChange={handleOnChange}
enablePanDownToClose={true}
>
<View style={{ backgroundColor: '#b2b2b2', alignItems: 'center', marginTop: 16 }}>
<Text
style={{
color: 'white',
fontSize: 20,
textTransform: 'uppercase',
paddingStart: 10,
}}>
{'Select Zone'}
</Text>
</View>
<BottomSheetFlatList
// add ref and getItemLayout in order to use scrollToIndex method
ref={flatListRef}
getItemLayout={getItemLayout}
data={zoneList}
keyExtractor={(item) => item.id}
renderItem={renderItem}
contentContainerStyle={styles.contentContainer}
/>
</BottomSheet>
)
})

Related

Styling conditional FlatList?

I want to show FlatList only if there are results from an axios call. Unfortunately when there are some results, they are showing beneath the components below it, like TextArea, etc. I tried a lot of styling combinations but nothing works. Any help is appriciated!
const CreateScreen = () => {
const [searchKeyword, setSearchKeyword] = useState("");
const [searchResults, setSearchResults] = useState("");
const [isShowingResults, setIsShowingResults] = useState(false);
searchLocation = async (text) => {
setSearchKeyword(text);
axios
.request({
method: "post",
url:
"https://maps.googleapis.com/maps/api/place/autocomplete/json?key=" +
apiKey +
"&input=" +
searchKeyword +
"&types=(cities)&components=country:bg&language=bg",
})
.then((response) => {
console.log(response.data);
setSearchResults(response.data.predictions);
setIsShowingResults(true);
})
.catch((e) => {
console.log(e.response);
});
};
return (
<ScrollView>
<Text style={{ marginBottom: 3 }}>Address</Text>
<View style={styles.autocompleteContainer}>
<TextInput
returnKeyType="search"
placeholderTextColor="#000"
onChangeText={(text) => searchLocation(text)}
value={searchKeyword}
/>
{isShowingResults && (
<FlatList
data={searchResults}
renderItem={({ item, index }) => {
return (
<TouchableOpacity
style={styles.resultItem}
onPress={() => {
setSearchKeyword(item.description);
setIsShowingResults(false);
}}
>
<Text>{item.description}</Text>
</TouchableOpacity>
);
}}
keyExtractor={(item) => item.id}
style={styles.searchResultsContainer}
/>
)}
</View>
<Text style={{ marginBottom: 3 }}>Title</Text>
<TextInput />
</ScrollView>
);
}
const styles = StyleSheet.create({
autocompleteContainer: {
zIndex: 1,
},
searchResultsContainer: {
width: "100%",
backgroundColor: "#fff",
position: "absolute",
top: 50,
},
resultItem: {
justifyContent: "center",
height: 40,
borderBottomColor: "#ccc",
borderBottomWidth: 1,
paddingLeft: 15,
},
});
{isShowingResults? (
<FlatList
data={searchResults}
renderItem={({ item, index }) => {
return (
<TouchableOpacity
style={styles.resultItem}
onPress={() => {
setSearchKeyword(item.description);
setIsShowingResults(false);
}}
>
<Text>{item.description}</Text>
</TouchableOpacity>
);
}}
keyExtractor={(item) => item.id}
style={styles.searchResultsContainer}
/>
):null}
You can try this, or you can just pass ListEmptyComponent to null, nothing will display when you have empty array in data of flatlist

flatlist slow performance on large data react native

I'm new to react native and I'm having some performance problem with flatlist in React Native Expo. On my screen I'm displaying a list of contacts and I want them to be selected or deselected and I have managed to achieve that part but the problem is that it takes a lot of visibility time to check the contacts or to uncheck them. Making the screen not very user friendly. I have tried a lot of solutions that I've found on internet regarding this problem but none of them had worked. Hope someone can help me. Below I'll attach the code!
function ContactList() {
const [itemChecked, setItemChecked] = useState([]);
const [checked, setChecked] = useState(false);
const [contacts, setContacts] = useState([]);
const [filter, setFilter] = useState([]);
const [search, setSearch] = useState('');
const [checkedBox, setCheckedBox] = useState(false);
useEffect(() => {
(async () => {
const { status } = await Contacts.requestPermissionsAsync();
if (status === 'granted') {
const { data } = await Contacts.getContactsAsync({
fields: [Contacts.Fields.PhoneNumbers],
// fields: [Contacts.Fields.Name],
});
if (data.length > 0) {
setContacts(data);
setFilter(data);
// console.log('contact', contacts[1]);
// console.log('filter', filter);
}
}
})();
}, []);
const searchFilter = (text) => {
if (text) {
const newData = contacts.filter((item) => {
const itemData = item.name ? item.name.toUpperCase() : ''.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setFilter(newData);
setSearch(text);
} else {
setFilter(contacts);
setSearch(text);
}
};
const onChangeValue = (item, index) => {
if (itemChecked.includes(item.phoneNumbers[0].digits)) {
itemChecked.splice(itemChecked.indexOf(item.phoneNumbers[0].digits), 1);
} else {
itemChecked.push(item.phoneNumbers[0].digits);
setCheckedBox(true);
}
setItemChecked(itemChecked);
console.log(itemChecked);
// console.log(item);
};
const renderItem = ({ item, index }) => {
return (
<SafeAreaView>
<ScrollView>
<TouchableOpacity style={{ flexDirection: 'row', flex: 1 }}>
<View style={{ flex: 1, borderTopWidth: 0.5, borderTopColor: 'grey', marginBottom: 15 }}>
<Text onPress={() => setChecked(true)} style={{ fontSize: 20, marginHorizontal: 10 }}>
{item.name + ' '}
</Text>
<Text style={{ fontSize: 17, marginHorizontal: 10, marginTop: 5, color: 'grey' }}>
{item.phoneNumbers && item.phoneNumbers[0] && item.phoneNumbers[0].number}
</Text>
</View>
<View style={{ flex: 1, borderTopWidth: 0.5, borderTopColor: 'grey' }}>
{itemChecked.includes(item.phoneNumbers[0].digits) === false ? (
<CheckBox
style={{ width: 15, height: 15 }}
right={true}
checked={false}
onPress={() => {
onChangeValue(item, index);
}}
/>
) : (
<CheckBox
style={{ width: 15, height: 15, paddingTop: 8 }}
right={true}
checked={true}
onPress={() => {
onChangeValue(item, index);
}}
/>
)}
</View>
{/* <Post postJson={item} isGroupAdmin={isGroupAdmin} user={user} /> */}
</TouchableOpacity>
</ScrollView>
</SafeAreaView>
);
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.container}>
<View
style={{
height: 40,
justifyContent: 'center',
backgroundColor: '#eeeeee',
width: '90%',
marginHorizontal: 20,
marginTop: 15,
borderRadius: 10,
}}
>
<Feather name="search" size={20} color="grey" style={{ position: 'absolute', left: 32 }} />
<TextInput
placeholder="Search"
placeholderTextColor="#949494"
style={{
left: 20,
paddingHorizontal: 35,
fontSize: 20,
}}
value={search}
onChangeText={(text) => {
searchFilter(text);
setSearch(text);
}}
/>
</View>
<FlatList
style={{ marginTop: 15 }}
data={contacts && filter}
keyExtractor={(item) => `key-${item.id.toString()}`}
renderItem={renderItem}
refreshing={true}
initialNumToRender={10}
ListEmptyComponent={<Text message="No contacts found." />}
/>
</View>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});
export default ContactList;

react native modal takes 2 second to open

In my app you can join a group. There are storys and messages. If there is no story or messages modal is open fast. But If I have 30 text messages to render and want to click to open my modal then it takes 1-2 seconds or later to open. The same issue if I click a button. The opacity and function works until 1-2 seconds but if there is no much messages to render then it goes fast. I need help.
message render function
<FlatList
data={globalMessages}
style={{width: '100%'}}
contentContainerStyle={{marginLeft: 12}}
renderItem={({ item }) => (
<TouchableOpacity onLongPress={() => deleteMessage(item.id)} style={{width: 230, flexDirection: 'column', backgroundColor: '#eee', padding: 8, borderRadius: 12, margin: 12, marginLeft: 0, marginTop: 0}}>
<Text style={{fontFamily: 'poppins-light', fontSize: 16, color: '#333'}}>{item.message}</Text>
<Text style={{fontFamily: 'poppins-medium', fontSize: 12, color: '#333'}}>{item.date}</Text>
</TouchableOpacity>
)}
keyExtractor={item => item.id.toString()}
// ListHeaderComponent={() => this.renderHeader()}
horizontal={false}
nestedScrollEnabled
showsHorizontalScrollIndicator={false}
// Performance settings
removeClippedSubviews={true} // Unmount components when outside of window
initialNumToRender={2} // Reduce initial render amount
maxToRenderPerBatch={1} // Reduce number in each render batch
updateCellsBatchingPeriod={100} // Increase time between renders
windowSize={4} // Reduce the window size
/>
fullCode:
import { StatusBar } from 'expo-status-bar';
import React, { useState, useEffect, useRef } from 'react';
import { StyleSheet, Text, TextInput, View, TouchableOpacity, Image, Dimensions, ImageBackground, ActivityIndicator, ScrollView, FlatList, TouchableWithoutFeedback } from 'react-native';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
import { AntDesign, Ionicons } from '#expo/vector-icons';
import Modal from 'react-native-modal';
import * as ImagePicker from 'expo-image-picker';
import uploadStoryAPI from '../api/uploadStory';
import createMessageAPI from '../api/createMessage';
import io from 'socket.io-client/dist/socket.io';
const width = Dimensions.get('window').width;
const height = Dimensions.get('window').height;
const Room = ({ route }) => {
const socket = io('http://192.168.0.249:3000', {
transports: ['websocket']
});
const [connected, setConnected] = useState(false);
const [areaHeight, setAreaHeight] = useState(0);
const [chatMessage, setChatMessage] = useState('');
const [globalMessages, setGlobalMessages] = useState(route.params.messages || []);
const [loadSendChatMessage, setLoadSendChatMessage] = useState(false);
const [alignItem, setAlignItem] = useState('center');
const [addStory, setAddStory] = useState(false);
const [storys, setStorys] = useState(route.params.storys || []);
const [modalImagePreview, setModalImagePreview] = useState(false);
const [modalDeleteMessage, setModalDeleteMessage] = useState(false);
const [imagePreview, setImagePreview] = useState('');
const [loadUpload, setLoadUpload] = useState(false);
const [sourceStory, setSourceStory] = useState({
uri: null,
type: null,
name: null
});
useEffect(() => {
socket.on('connect', () => {
setConnected(true);
console.log('user connected');
});
socket.on('getmessage', message => {
globalMessages.length > 0 ?
setGlobalMessages(prevState => {
return [...prevState, message]
}) : setGlobalMessages([message]);
handleAddMessage();
});
});
const handleAlign = () => {
areaHeight > 100 ? setAlignItem('flex-end') : setAlignItem('center');
}
const handleAddStory = async () => {
try {
let result = await ImagePicker.launchImageLibraryAsync({
allowsEditing: true,
aspect: [1, 1]
});
setAddStory(false);
if(result.cancelled) {
setAddStory(false);
return;
}
let localUri = result.uri;
let filename = localUri.split('/').pop();
// Infer the type of the image
let match = /\.(\w+)$/.exec(filename);
let type = match ? `image/${match[1]}` : `image`;
setImagePreview(localUri);
setSourceStory({
uri: localUri,
type,
filename
});
setModalImagePreview(true);
} catch(e) {
return;
}
};
const handleUploadToServer = async () => {
try {
setLoadUpload(true);
let res = await uploadStoryAPI(route.params.roomID, sourceStory);
res.upload && storys.length > 0
? setStorys(prevState => {
return [{
source: res.source
}, ...prevState]
}) : setStorys([{ source: res.source}]);
console.log(`D ${storys}`)
res.upload ? (setLoadUpload(false),setModalImagePreview(false)) : setLoadUpload(false);
} catch(e) {
return e;
}
};
const handleAddMessage = async () => {
try {
setLoadSendChatMessage(true);
const res = await createMessageAPI(route.params.roomID, chatMessage);
console.log(res);
//scrollViewRef.current.scrollToEnd({animated: true});
setLoadSendChatMessage(false);
} catch(e) {
console.log(e);
return e;
}
};
const deleteMessage = async id => {
try {
console.log(id);
} catch(e) {
return e;
}
};
return (
<KeyboardAwareScrollView keyboardShouldPersistTaps="handled" style={{margin: 0, padding: 0}}>
<View style={{height: height - 155, backgroundColor: '#fff'}}>
<View>
<ScrollView>
<Text style={styles.title}>Storys</Text>
{
storys.length > 0
?
<FlatList
data={storys}
contentContainerStyle={{marginLeft: 8}}
renderItem={({ item }) => (
<TouchableOpacity key={`index-${item.source}`} style={{padding: 0, marginRight: 16, borderWidth: 2, justifyContent: 'center', alignItems: 'center', borderRadius: 100, borderColor: '#1ab7ff', height: 68, width: 68}}>
<Image resizeMode="contain" source={{uri: `http://192.168.0.249:3000/build/${item.source}`}} style={{height: 55, width: 55, borderRadius: 500}} />
</TouchableOpacity>
)}
keyExtractor={item => item.source.toString()}
// ListHeaderComponent={() => this.renderHeader()}
nestedScrollEnabled
horizontal
showsHorizontalScrollIndicator={false}
// Performance settings
removeClippedSubviews={true} // Unmount components when outside of window
initialNumToRender={2} // Reduce initial render amount
maxToRenderPerBatch={1} // Reduce number in each render batch
updateCellsBatchingPeriod={100} // Increase time between renders
windowSize={7} // Reduce the window size
/>
:
<Text>Es sind zurzeit keine Storys vorhanden.</Text>
}
<Text style={styles.title}>Nachrichten</Text>
{
globalMessages.length > 0
?
<FlatList
data={globalMessages}
style={{width: '100%'}}
contentContainerStyle={{marginLeft: 12}}
renderItem={({ item }) => (
<TouchableOpacity onLongPress={() => deleteMessage(item.id)} style={{width: 230, flexDirection: 'column', backgroundColor: '#eee', padding: 8, borderRadius: 12, margin: 12, marginLeft: 0, marginTop: 0}}>
<Text style={{fontFamily: 'poppins-light', fontSize: 16, color: '#333'}}>{item.message}</Text>
<Text style={{fontFamily: 'poppins-medium', fontSize: 12, color: '#333'}}>{item.date}</Text>
</TouchableOpacity>
)}
keyExtractor={item => item.id.toString()}
// ListHeaderComponent={() => this.renderHeader()}
horizontal={false}
nestedScrollEnabled
showsHorizontalScrollIndicator={false}
// Performance settings
removeClippedSubviews={true} // Unmount components when outside of window
initialNumToRender={2} // Reduce initial render amount
maxToRenderPerBatch={1} // Reduce number in each render batch
updateCellsBatchingPeriod={100} // Increase time between renders
windowSize={4} // Reduce the window size
/>
: null
}
</ScrollView>
</View>
</View>
<View style={{flex: 1, paddingLeft: 16, paddingTop: 8, paddingRight: 16, paddingBottom: 12, backgroundColor: '#fff', alignItems: alignItem, justifyContent:'space-between', flexDirection: 'row'}}>
<TouchableOpacity onPress={() => setAddStory(true)}>
<AntDesign name="pluscircleo" size={32} color="#444" />
</TouchableOpacity>
<TextInput
onChangeText={e => setChatMessage(e)}
// onFocus={() => setAlignItem('flex-end')}
onEndEditing={() => handleAlign()}
value={chatMessage}
multiline={true}
style={[styles.input, {height: Math.max(50, areaHeight)}]}
onContentSizeChange={e => {
setAreaHeight(e.nativeEvent.contentSize.height);
}}
placeholder="Gebe eine Nachricht ein..." />
<TouchableOpacity onPress={() => chatMessage.length > 0 ? socket.emit('getmessage', {
chatMessage,
date: Date.now()
}) : null} style={{padding: 8, paddingRight: 10, borderRadius: 8, backgroundColor: '#1ab7ff'}}>
{ !loadSendChatMessage ? <Ionicons name="paper-plane-outline" size={28} color="#fff" /> : <ActivityIndicator style={{height: 20, width: 20}} color="#fff" />}
</TouchableOpacity>
</View>
{ /* Ask user if he wants to upload */}
<Modal
isVisible={modalImagePreview}
swipeDirection="down"
onSwipeComplete={() => setModalImagePreview(false)}
onBackButtonPress={() => setModalImagePreview(false)}
onBackdropPress={() => setModalImagePreview(false)}
backdropTransitionOutTiming={0}
// style={{margin: 0, padding: 0}}
>
<View style={{ backgroundColor: '#fff', borderRadius: 12, padding: 12}}>
<Text style={{fontFamily: 'poppins-light', color: '#333', fontSize: 18, textAlign: 'center', marginBottom: 10}}>Möchtest du diese Story hochladen?</Text>
{ <Image resizeMode="contain" style={{height: 250, borderRadius: 12}} source={{uri: imagePreview}} /> }
<View style={{flexDirection: 'row', justifyContent: 'space-around', alignItems: 'center', marginTop: 20, marginBottom: 10}}>
<TouchableOpacity onPress={() => setModalImagePreview(false)}>
<Text style={{fontFamily: 'poppins-light', color: '#333', borderWidth: 1, borderColor: '#eee', padding: 10, paddingTop: 13, borderRadius: 8}}>Abbrechen</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => handleUploadToServer()} style={{backgroundColor: '#1ab7ff', padding: 10, paddingTop: 13, borderRadius: 8}}>
{ !loadUpload ? <Text style={{fontFamily: 'poppins-light', color: '#fff'}}>Story hinzufügen</Text> : <ActivityIndicator style={{height: 20, width: 20}} color="#fff" /> }
</TouchableOpacity>
</View>
</View>
</Modal>
{ /* Modal open Story */ }
<Modal
isVisible={addStory}
swipeDirection="down"
onSwipeComplete={() => setAddStory(false)}
onBackButtonPress={() => setAddStory(false)}
onBackdropPress={() => setAddStory(false)}
backdropTransitionOutTiming={0}
style={{margin: 0, padding: 0}}
>
<View style={{position: 'absolute', bottom: 0, backgroundColor: '#fff', width: '100%', justifyContent: 'center', alignItems: 'center', borderTopLeftRadius: 20, borderTopRightRadius: 20}}>
<TouchableOpacity onPress={() => handleAddStory()} style={{padding: 20, backgroundColor: '#1ab7ff', width: width, justifyContent: 'center', alignItems: 'center',borderTopLeftRadius: 20, borderTopRightRadius: 20}}>
<Text style={{color: '#fff', fontFamily: 'poppins-light'}}>Story hinzufügen</Text>
</TouchableOpacity>
<TouchableOpacity onPress={() => setAddStory(false)} style={{padding: 20}}>
<Text>Abbrechen</Text>
</TouchableOpacity>
</View>
</Modal>
</KeyboardAwareScrollView>
)
};

Card drop-down on react native

Is it possible to achieve a component drop on react native?
Something like this. Sorry for the paint skills. Thank you in advance .
You can create a custom component like below, You can change the render label to support the view you want.
This sample needs the data to be like the below object but you can create your own object.
const dataArray = [{value: 'Car', id: 1,},{value: 'Van',id: 2},{ value: 'Bus',id: 3}];
//Usage
<CustomDropDown data={dataArray} />
const CustomDropDown = (props) => {
const [open, setOpen] = React.useState(false);
const [selected, setSelected] = React.useState(-1);
const renderLabel = (item) => {
return (
<View
style={{
width: 200,
height: 60,
borderWidth: 1,
borderRadius: 10,
padding: 5,
marginVertical: 3,
}}>
<Text style={{ fontSize: 20 }}>{item.value}</Text>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Text>{item.value}</Text>
<Text style={{ fontSize: 15 }}>{item.value}</Text>
</View>
</View>
);
};
const renderItem = (item) => {
return (
<TouchableOpacity
onPress={() => {
setSelected(item.id);
setOpen(!open);
//Call back to external state
}}>
{renderLabel(item)}
</TouchableOpacity>
);
};
return (
<View>
<TouchableOpacity style={{}} onPress={() => setOpen(!open)}>
{selected === -1 && <Text>Place holder text</Text>}
{selected !== -1 &&
renderLabel(props.data.find((x) => x.id === selected))}
</TouchableOpacity>
{open && (
<View
style={{
position: 'absolute',
top: 70,
}}>
{props.data.map((item) => renderItem(item))}
</View>
)}
</View>
);
};

TypeError: Cannot read property of 'data' of undefined

I am using <FlatList/> to render an item for each object of json. This json is stored in this.state.json. Here is my code:
export class MyAppsName extends React.Component {
static navigationOptions = {
title: "App name",
headerLeft: null,
};
constructor() {
super();
this.state = {
index: 0,
isAuthed: false,
data: [
{
name: "Class 1",
grade: 83,
letterGrade: "A+"
},
{
name: "Class 2",
grade: 90,
letterGrade: "B+"
}
],
loading: false
};
}
refresh() {
this.setState({ isAuthed: true });
}
componentWillMount() {
}
render() {
return (
<Root>
<ScrollableTabView initialPage={0} tabBarPosition="top">
<HomeRoute tabLabel="Grades" />
<View
style={{ flex: 1, justifyContent: "center" }}
tabLabel="Settings"
>
</View>
</ScrollableTabView>
</Root>
);
}
}
makeRemoteRequest = () => {
//do request stuff
};
handleRefresh = () => {
this.makeRemoteRequest();
};
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: "86%",
backgroundColor: "black",
}}
/>
);
};
renderHeader = () => {
return <View />;
};
classSelected = className => {
//do stuff
};
renderFooter = () => {
if (!this.state.loading) return null;
return (
<View>
<ActivityIndicator animating size="large" />
</View>
);
};
const HomeRoute = () => (
<List containerStyle={{ borderTopWidth: 0, borderBottomWidth: 0 }}>
<FlatList
style={{ width: width }}
data={this.state.data}
renderItem={({ item }) => (
<View style={{ left: "9%", padding: 6 }}>
<TouchableOpacity onPress={() => this.classSelected(item.name)}>
<Text
style={{
fontSize: 16
}}
>
{item.name}
</Text>
<Text
style={{
fontSize: 12
}}
>
{item.grade}
</Text>
<Text
style={{
fontSize: 12
}}
>
{item.letterGrade}
</Text>
</TouchableOpacity>
</View>
)}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
keyExtractor={item => item.name}
/>
</List>
);
The error I get is 'TypeError: Cannot read property 'data' of undefined' within HomeRoute. I think the error is in this line: data={this.state.data}.
I am defining data correctly? Am I even correctly setting up the FlatList? Or does it have to do with how I am passing down application state?
If you need any other code to be appended to the question, you can just ask in the comments.
Thanks in advance
You've declared the state in is parent component, causing this.state is undefined in its child component.
When you use the HomeRoute component, you need to pass the state data from its parent component.
render() {
return (
<Root>
<ScrollableTabView initialPage={0} tabBarPosition="top">
<HomeRoute tabLabel="Grades" data={this.state.data} />
<View
style={{ flex: 1, justifyContent: "center" }}
tabLabel="Settings"
>
</View>
</ScrollableTabView>
</Root>
);
}
and in the HomeRoute itself, you need to extract data from its props.
const HomeRoute = ({data}) => (
<List containerStyle={{ borderTopWidth: 0, borderBottomWidth: 0 }}>
<FlatList
style={{ width: width }}
data={data}
renderItem={({ item }) => (
<View style={{ left: "9%", padding: 6 }}>
<TouchableOpacity onPress={() => this.classSelected(item.name)}>
<Text
style={{
fontSize: 16
}}
>
{item.name}
</Text>
<Text
style={{
fontSize: 12
}}
>
{item.grade}
</Text>
<Text
style={{
fontSize: 12
}}
>
{item.letterGrade}
</Text>
</TouchableOpacity>
</View>
)}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
keyExtractor={item => item.name}
/>
</List>
);
Several functions, including HomeRoute, are not defined within the MyAppsName class. Therefore, this in these functions doesn't point to the correct this. I suggest to put them inside the class and bind all the functions that use this.
You get a syntax error because they are not defined properly. Follow the pattern of the definition of render, for example:
makeRemoteRequest() {
Instead of
makeRemoteRequest = () => {