React Native Scroll View not working with - react-native

Having an issue with my ScrollView. I use it in a couple different places in my application, and most of them are working exactly as expected.
However, in one component it is working very strangely - if I swipe quickly, it will sometimes work, but usually not, and if I swipe gently or only a small amount, it doesn't work at all. I render a couple different things inside the ScrollView, but can't work out why any of them might be causing a problem, and can't spot anything obvious that's different between the one that doesn't work and the others, so I'm really at my wits end!
I am testing it on Android.
Here's what I think are the relevant bits of code for the page, but I've also put the full code below - please let me know if there's any other detail that would be useful:
const wait = (timeout) => {
return new Promise((resolve) => setTimeout(resolve, timeout));
};
export default function PotluckStandalone(props) {
const potlucks = useSelector((state) => state.potlucks);
const potluck = potlucks.find(
({ idCode }) => idCode === props.route.params.idCode
);
const [refreshing, setRefreshing] = React.useState(false);
const onRefresh = React.useCallback(() => {
setRefreshing(true);
wait(2000).then(() => setRefreshing(false));
}, []);
const dispatch = useDispatch();
const Reply = () => {
return (
<View>
<FlatList
keyExtractor={(item, index) => index}
data={potluck.replies}
renderItem={({ item }) => (
<View>
<Card
containerStyle={{
borderRadius: 12,
borderWidth: 1,
elevation: 0,
backgroundColor: "rgba(255,255,255,0.6)",
overflow: "hidden",
}}
style={{ borderColor: "rgba(255,255,255,0.1)" }}
>
<Card.Title>{item.bringer} is bringing...</Card.Title>
<Card.Divider />
{item.bringing.map((bringItem, index) => {
return (
<Text key={index}>
{bringItem}
{index < item.bringing.length - 2 ? ", " : ""}
{index === item.bringing.length - 2 ? " and " : ""}
</Text>
);
})}
</Card>
</View>
)}
/>
</View>
);
};
if (!potluck) {
return <Text>Loading...</Text>;
} else {
return (
<ImageBackground
source={require("../images/background.png")}
style={{ width: "100%", height: "100%", alignItems: "center" }}
>
<ScrollView
style={styles.page}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
<Card
containerStyle={{
borderRadius: 12,
borderWidth: 1,
elevation: 0,
backgroundColor: "rgba(255,255,255,0.6)",
overflow: "hidden",
}}
style={{ borderColor: "rgba(255,255,255,0.1)" }}
>
<Card.Title>
<Text>{potluck.potluckTitle}</Text>
</Card.Title>
<Card.Divider />
<Text>Host: {potluck.potluckHost}</Text>
<Text>Theme: {potluck.potluckTheme}</Text>
<Text>
Essentials:
{potluck.essentials.map((essential, index) => {
return (
<Text key={index}>
{" "}
{essential}
{index < potluck.essentials.length - 2 ? ", " : ""}
{index === potluck.essentials.length - 2 ? " and " : ""}
</Text>
);
})}
</Text>
<Card.Divider />
<Reply />
</Card>
<Bringing
potluck={potluck}
setReplySnack={() => setReplySnack(true)}
/>
</ScrollView>
</ImageBackground>
);
}
}
const styles = StyleSheet.create({
page: {
width: "90%",
paddingTop: 50,
paddingBottom: 250,
},
});
Full code here:
import { StatusBar } from "expo-status-bar";
import React, { useState, useEffect } from "react";
import { useSelector } from "react-redux";
import {
ScrollView,
View,
Text,
FlatList,
RefreshControl,
SafeAreaView,
Button,
Share,
ImageBackground,
} from "react-native";
import { useDispatch } from "react-redux";
import { Card } from "react-native-elements";
import Bringing from "./Bringing";
import { updatePotluck } from "../actions/potlucks";
import { render } from "react-dom";
import { StyleSheet } from "react-native";
import Snackbar from "react-native-snackbar-component";
const wait = (timeout) => {
return new Promise((resolve) => setTimeout(resolve, timeout));
};
export default function PotluckStandalone(props) {
const potlucks = useSelector((state) => state.potlucks);
const potluck = potlucks.find(
({ idCode }) => idCode === props.route.params.idCode
);
const [refreshing, setRefreshing] = React.useState(false);
const onRefresh = React.useCallback(() => {
setRefreshing(true);
wait(2000).then(() => setRefreshing(false));
}, []);
const dispatch = useDispatch();
const [potluckSnackIsVisible, setPotluckSnackIsVisible] = useState(false);
const [replySnackVisible, setReplySnackVisible] = useState(false);
React.useEffect(() => {
props.route.params.success
? setPotluckSnackIsVisible(true)
: setPotluckSnackIsVisible(false);
}, []);
const onShare = async () => {
try {
const result = await Share.share({
message: `Join me for a potluck | whatLuck https://whatluck.netlify.app/potlucks/${potluck.idCode}`,
});
if (result.action === Share.sharedAction) {
if (result.activityType) {
// shared with activity type of result.activityType
} else {
// shared
}
} else if (result.action === Share.dismissedAction) {
// dismissed
}
} catch (error) {
alert(error.message);
}
};
const setReplySnack = () => setReplySnackVisible(true);
const Reply = () => {
return (
<View>
<FlatList
keyExtractor={(item, index) => index}
data={potluck.replies}
//style={styles.flatlist}
renderItem={({ item }) => (
<View>
<Card
containerStyle={{
borderRadius: 12,
borderWidth: 1,
elevation: 0,
backgroundColor: "rgba(255,255,255,0.6)",
overflow: "hidden",
}}
style={{ borderColor: "rgba(255,255,255,0.1)" }}
>
<Card.Title>{item.bringer} is bringing...</Card.Title>
<Card.Divider />
{item.bringing.map((bringItem, index) => {
return (
<Text key={index}>
{bringItem}
{index < item.bringing.length - 2 ? ", " : ""}
{index === item.bringing.length - 2 ? " and " : ""}
</Text>
);
})}
</Card>
</View>
)}
/>
</View>
);
};
if (!potluck) {
return <Text>Loading...</Text>;
} else {
return (
<ImageBackground
source={require("../images/background.png")}
style={{ width: "100%", height: "100%", alignItems: "center" }}
>
<ScrollView
style={styles.page}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
}
>
<Card
containerStyle={{
borderRadius: 12,
borderWidth: 1,
elevation: 0,
backgroundColor: "rgba(255,255,255,0.6)",
overflow: "hidden",
}}
style={{ borderColor: "rgba(255,255,255,0.1)" }}
>
<Card.Title>
<Text>{potluck.potluckTitle}</Text>
</Card.Title>
<Card.Divider />
<Button onPress={onShare} title="Invite your friends" />
<Text>Host: {potluck.potluckHost}</Text>
<Text>Theme: {potluck.potluckTheme}</Text>
<Text>
Essentials:
{potluck.essentials.map((essential, index) => {
return (
<Text key={index}>
{" "}
{essential}
{index < potluck.essentials.length - 2 ? ", " : ""}
{index === potluck.essentials.length - 2 ? " and " : ""}
</Text>
);
})}
</Text>
<Card.Divider />
<Reply />
</Card>
<Bringing
potluck={potluck}
setReplySnack={() => setReplySnack(true)}
/>
<Snackbar
visible={potluckSnackIsVisible}
textMessage="Potluck created successfully!"
autoHidingTime={3000}
/>
<Snackbar
visible={replySnackVisible}
textMessage="Reply posted successfully!"
autoHidingTime={3000}
/>
</ScrollView>
</ImageBackground>
);
}
}
const styles = StyleSheet.create({
page: {
width: "90%",
paddingTop: 50,
paddingBottom: 250,
},
});

Related

How to put the bottomsheet to the front of the screen?

In ReactNative, the bottomsheet is displayed overlaid on the fragment.
Is there a way to make the bottomsheet rise to the top of the screenenter image description here
The bottom sheet looks opaque as in the picture, so the bottom sheet cannot be touched Please help
The code below is a shortened version
enter image description here
enter image description here
import React, { FC , Component, useState, useEffect, Fragment,useCallback, useMemo, useRef } from "react"
import { FlatList, ViewStyle, StyleSheet, View, Platform, TextInput, TouchableOpacity} from "react-native"
import {
BottomSheetModal,
BottomSheetModalProvider,
BottomSheetBackdrop,
} from '#gorhom/bottom-sheet';
const ROOT: ViewStyle = {
backgroundColor: DefaultTheme.colors.background,
flex: 1,
}
export const ChecklookupScreen: FC<StackScreenProps<NavigatorParamList, "checklookup">> = observer(function ChecklookupScreen() {
const bottomSheetModalRef = useRef<BottomSheetModal>(null);
// variables
const snapPoints = useMemo(() => ['25%', '50%'], []);
// callbacks
const handlePresentModalPress = useCallback((index: string) => {
LOG.info('handlePresentModalPress', index);
bottomSheetModalRef.current?.present();
}, []);
const handleSheetChanges = useCallback((index: number) => {
LOG.info
console.log('handleSheetChanges', index);
}, []);
const renderItem = ({ item, index }) => (
<TouchableOpacity
key={index + item.inspNo + item.spvsNo}
//style={listContainer}
onPress={throttle(() => {
onClickItem(item.inspNo,item.spvsNo);
})}
>
<View>
<Fragment>
</View>
<Button icon="magnify-expand"
mode="elevated"
style={styles.detailButton}
onPress={throttle(() => {
onClickItem(item.inspNo,item.spvsNo);
})}
// onPress={() => navigation.navigate("checkdetail")}
>
</Button>
</View>
</Fragment>
</View>
</TouchableOpacity>
);
const fetchChecklookups = async (offset: number) => {
LOG.debug('fetchChecklookups:' + offset);
setRefreshing(true);
await checklookupStore.getChecklookups(offset)
setRefreshing(false);
};
const onEndReached = () => {
if (checklookupStore?.checklookupsTotalRecord <= checklookups?.length) {
LOG.debug('onEndReached555555555');
} else {
setPage(page + 1)
fetchChecklookups(page + 1);
}
};
const [searchQuery, setSearchQuery] = React.useState('');
const onChangeSearch = query => setSearchQuery(query);
return (
<Screen preset="fixed" style={{ backgroundColor: colors.background, flex: 1, padding: 10,}}>
<View style={{ flex: 1,}}>
<View style={{ flex: 1, }}>
<Searchbar
placeholder="조회조건을 입력해주세요"
onChangeText={onChangeSearch}
value={searchQuery}
onPressIn={() => handlePresentModalPress('touch on')}
/>
<BottomSheetModalProvider>
<BottomSheetModal
backgroundStyle={{ backgroundColor: "gray" }}
style={styles.bottomSheet}
ref={bottomSheetModalRef}
index={1}
snapPoints={snapPoints}
onChange={handleSheetChanges}
>
<View style={{ marginTop: 10, marginLeft: 50, marginRight: 50, flexDirection: "row"}}>
<View style={{ flex: 1, }}>
<Button
mode="outlined"
>소속을 입력하세요
</Button>
</View>
</View>
</BottomSheetModal>
</BottomSheetModalProvider>
</Screen>
)
})
You can try with portal, wrap you bottom sheet to from another package.

Understanding UI State : Dynamic TextInput loses focus after each key press

Screens and navigating is fine, but getting data from users has been a struggle. Not only are the UI elements different, the means to capture input and store in state effectively across various input types is troubling me. Here is an example of what I think is a simple UX yet I cannot get the text inputs to focus correctly.
In the below example, the desire is to have a list of items within a horizontal scroll view and when i click on the arrow of an item, the screen scrolls and a form to edit the item appears with one or more text boxes. Future enhancements were to have this second panel have more fields based on the type of field from the list, but i cant even get a simple text box to work properly
I've got some code to boot, copy and paste as app.js in an expo init project and it should run
main question: how to retain focus on inputs on the detail panel
import React from "react";
import {
Dimensions,
FlatList,
SafeAreaView,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
const init_items = [
{ name: "start", value: 12500, type: "num" },
{ name: "end", value: 12700, type: "num" },
{ name: "time", value: 123.45, type: "time" },
];
const main_color = "#dddddd";
const _base = 3;
const _width = Dimensions.get("window").width;
export default function App() {
const [index, setIndex] = React.useState(0);
const [items, setItems] = React.useState(init_items);
const [curItem, setCurItem] = React.useState("");
const ref = React.useRef(null);
const textRef = React.useRef(null);
React.useEffect(() => {
console.log("index chsnaged?", index);
//if (!index) return;
ref.current?.scrollToIndex({ index, animated: true });
}, [index]);
React.useEffect(() => {
setIndex(curItem === "" ? 0 : 1);
}, [curItem]);
const useCurItem = () => {
if (curItem == "") return;
return items.find((item) => item.name == curItem);
};
const setCurItemValue = (value) => {
console.log("update " + curItem + " to " + value);
const new_items = items.map((item) => {
if (item.name == curItem) return { ...item, value: value };
return item;
});
console.log("new_items: ", new_items);
setItems(new_items);
};
const Button = ({ type, press }) => {
return (
<TouchableOpacity onPress={() => press(type)}>
<Text style={{ fontSize: 20, fontWeight: "900", margin: _base }}>
{type == "arrow" ? ">" : "X"}
</Text>
</TouchableOpacity>
);
};
const ListPanel = () => {
return (
<View>
{items.map((item) => {
return (
<View
key={item.name}
style={{
margin: _base,
}}
>
<Text style={{ fontWeight: "600", margin: _base }}>
{item.name}
</Text>
<View
style={{
alignItems: "center",
backgroundColor: "white",
borderRadius: _base,
flexDirection: "row",
justifyContent: "space-between",
margin: _base,
padding: _base,
}}
>
<Text>{item.value}</Text>
<Button type="arrow" press={() => setCurItem(item.name)} />
{/* <EmojiButton
name={"fat_arrow"}
onPress={() => setCurItem(item.name)}
size={20}
/> */}
</View>
</View>
);
})}
</View>
);
};
const DetailPanel = () => {
let thisItem = useCurItem();
if (!thisItem) return null;
return (
<View style={{ width: "100%" }}>
{/* <EmojiButton name="arrow_left" onPress={() => setCurItem("")} /> */}
<Button type="cancel" press={() => setCurItem("")} />
<Text>{curItem}</Text>
<Text>{thisItem?.value}</Text>
<Text>{thisItem.type}</Text>
{thisItem.type == "num" && (
<TextInput
ref={textRef}
onChangeText={(text) => setCurItemValue(text)}
// onSubmitEditing={() => textRef.current.focus()}
style={{ backgroundColor: "white", margin: 2 }}
value={thisItem.value.toString()}
/>
)}
</View>
);
};
const screens = [
{ name: "listing", panel: <ListPanel /> },
{ name: "detail", panel: <DetailPanel /> },
];
return (
<View style={{ marginTop: 30 }}>
<Text>Sample sliding inputs</Text>
<FlatList
bounces={false}
horizontal
keyExtractor={(item) => item.name}
ref={ref}
showsHorizontalScrollIndicator={false}
data={screens}
renderItem={({ item, index: fIndex }) => {
console.log("rendering " + item);
return (
<View
style={{
backgroundColor: main_color,
height: 300,
width: _width,
padding: _base,
}}
>
<Text> {item.name}</Text>
{item.panel}
</View>
);
}}
/>
<Text>index: {index}</Text>
<Text>curItem: {curItem}</Text>
<TouchableOpacity onPress={() => setCurItem("")}>
<View>
<Text>reset</Text>
</View>
</TouchableOpacity>
</View>
);
}

Error in uploading image with 'react-native-image-picker' library

I am creating an android application and I need to use the react-native-image-picker library but it is showing some error, the error is:
[Unhandled promise rejection: TypeError: null is not an object (evaluating '_reactNative.NativeModules.ImagePickerManager.launchImageLibrary')]
I have tried everything like changing to different versions as well, also I have used expo-image-picker which does not show an error while fetching an image from android but gives an error when uploading it to firebase.
Please help, I have been frustrated with this error.
import {
View,
Text,
Image,
StyleSheet,
KeyboardAvoidingView,
TouchableOpacity,
ActivityIndicator
} from "react-native";
import React, { useState } from "react";
import { TextInput, Button } from "react-native-paper";
import ImagePicker, { launchImageLibrary } from "react-native-image-picker";
import storage from "#react-native-firebase/storage";
import auth from '#react-native-firebase/auth';
import firestore, {getStorage, ref, uploadBytes} from '#react-native-firebase/firestore';
export default function SignUp({ navigation }) {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [image, setImage] = useState(null);
const [shownext, setShownext] = useState(false);
const [loading, setLoading] = useState(false);
if (loading) {
return <ActivityIndicator size='large' />
}
const userSignUp = async () => {
setLoading(true)
if (!email || !password || !image || !name) {
alert('fill all details correctly')
return false;
}
try {
const result = await auth().createUserWithEmailAndPassword(email, password);
firestore().collection('users').doc(result.user.uid).set({
name: name,
email: result.user.email,
uid: result.user.uid,
pic:image
})
setLoading(false)
}catch (err) {
alert('something went wrong from your side')
}
}
const pickImageAndUpload = () => {
console.log(1);
launchImageLibrary({ puality: 0.5 }, async (fileobj) => {
console.log(2);
console.log(fileobj);
const uploadTask = storage().ref().child(`/userprofilepic/${Date.now()}`).putFile(fileobj.uri);
uploadTask.on(
"state_changed",
(snapshot) => {
const progress =
(snapshot.bytesTransferred / snapshot.totalBytes) * 100;
if (progress == 100) alert("image uploaded");
},
(error) => {
alert("error uploading image");
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
setImage(downloadURL);
});
}
);});
};
return (
<KeyboardAvoidingView behavior="position" style={{ alignItems: "center" }}>
<View style={styles.box1}>
<Text style={styles.text}>Welcome to chatapplication</Text>
<Image style={styles.img} source={require("../assets/wa-logo.png")} />
</View>
{!shownext && (
<>
<TextInput
style={{ width: 330, marginTop: 50, marginBottom: 30 }}
label="Email"
value={email}
mode="outlined"
onChangeText={(text) => setEmail(text)}
/>
<TextInput
style={{ width: 330, marginBottom: 30 }}
label="Password"
value={password}
mode="outlined"
onChangeText={(text) => setPassword(text)}
secureTextEntry
/>
</>
)}
{shownext ? (
<>
<TextInput
style={{ width: 330, marginTop: 50, marginBottom: 30 }}
label="Name"
value={name}
mode="outlined"
onChangeText={(text) => setName(text)}
/>
<Button
style={{ marginBottom: 30 }}
mode="contained"
onPress={() => { pickImageAndUpload() }}
>
Upload Profile Pic
</Button>
<Button
disabled={image?false:true}
mode="contained"
onPress={() => { userSignUp() }}>
SignUp
</Button>
</>
) : (
<Button
// disabled={email&&password?false:true}
mode="contained"
onPress={() => {
setShownext(true);
}}
>
Next
</Button>
)}
<TouchableOpacity onPress={() => navigation.goBack()}>
<Text style={{ margin: 10, textAlign: "center", fontSize: 18 }}>
Already have an account?
</Text>
</TouchableOpacity>
</KeyboardAvoidingView>
);
}
const styles = StyleSheet.create({
text: {
fontSize: 22,
color: "green",
margin: 20,
},
img: {
width: 200,
height: 200,
},
box1: {
alignItems: "center",
},
});

Why is AsyncStorage not retrieving data once I refresh my App?

I am building a todo app and I am trying to store and retrieve data but it's not retrieving any data that is being stored. Once I refresh the data doesn't seem to persist. If there is another way of storing or writing my code please assist. I tried using other methods of storage like MMKV but it was just similar to AsyncStorage so I decided to stick with AsyncStorage. Here is my code:
import AsyncStorage from "#react-native-async-storage/async-storage";
export default function todaytodo() {
const [modalOpen, setModalOpen] = useState(false);
const [todos, setTodos] = useState("");
const storedata = async () => {
try {
await AsyncStorage.setItem("Todos", JSON.stringify(todos));
} catch (err) {
console.log(err);
}
};
const loadData = async () => {
try {
const value = await AsyncStorage.getItem("Todos");
if (value !== null) {
console.log(value);
return value;
}
} catch (error) {
console.log(error);
}
};
useEffect(() => {
storedata();
loadData();
});
const toggleComplete = (index) =>
setTodos(
todos.map((Todo, k) =>
k === index ? { ...Todo, complete: !Todo.complete } : Todo
)
);
const pressHandler = (key) => {
setTodos((prevTodos) => {
return prevTodos.filter((todo) => todo.key != key);
});
};
const submitHandler = (Todo) => {
Todo.key = Math.random().toString();
setTodos((currentTodo) => {
return [Todo, ...currentTodo];
});
setModalOpen(false);
};
return (
<View style={styles.container}>
<View>
<View>
<Ionicons
style={{
position: "absolute",
marginTop: 650,
alignSelf: "flex-end",
zIndex: 10,
marginRight: 5,
}}
name="md-add-circle-outline"
size={73}
color="black"
onPress={() => setModalOpen(true)}
/>
</View>
<FlatList
data={todos}
renderItem={({ item, index, complete }) => (
<TouchableOpacity onPress={() => toggleComplete(index)}>
<ScrollView>
<View style={styles.everything}>
<View style={styles.itemlist}>
<Checkbox
label="delete"
checked={true}
onPress={() => pressHandler(item.key)}
/>
<Text
style={{
marginLeft: 8,
marginTop: 5,
fontSize: 15,
textDecorationLine: item.complete
? "line-through"
: "none",
color: item.complete ? "#a9a9a9" : "black",
}}
>
{item.Todo}
</Text>
</View>
<Text
style={{
fontSize: 12,
marginLeft: 50,
marginTop: -15,
color: "#008b8b",
textDecorationLine: item.complete
? "line-through"
: "none",
color: item.complete ? "#a9a9a9" : "#008b8b",
}}
>
{item.Comment}
</Text>
</View>
</ScrollView>
</TouchableOpacity>
)}
/>
</View>
<View style={styles.modalcont}>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<RNModal visible={modalOpen} animationType="slide">
<View style={styles.modalContent}>
<Ionicons
name="md-close-circle-outline"
style={{ alignSelf: "center" }}
size={60}
color="black"
onPress={() => setModalOpen(false)}
/>
<AddForm submitHandler={submitHandler} />
</View>
</RNModal>
</TouchableWithoutFeedback>
</View>
</View>
);
}
Use of useEffect is suspicious here, If you want to do it once on load of component
then need to update code for useEffect.
useEffect(() => {
storedata();
loadData();
}, []);

How to use ScrollToIndex in React Native?

I use a FlatList to display search results.
<View>
<FlatList
data={this.state.films}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) => <FilmItem film={item}/>}
/>
</View>
However, when the user starts a second search, this list does not restart to index 0. So I would like to add ScrollToIndex or viewPosition. I tried this but it doesn't work :
<View>
<FlatList
data={this.state.films}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) => <FilmItem film={item}/>}
scrollToItem={(index) => {0}}
/>
</View>
Could you please explain me why this is wrong and what would be the best solution ?
Thanks a lot,
Try this... I made a bunch of changes and have put comments:
// Components/Search.js
import React from "react";
import {StyleSheet, View, TextInput, Button, Text, FlatList, ActivityIndicator} from "react-native";
import FilmItem from "./filmItem";
import {getFilmsFromApiWithSearchedText} from "../API/TMDBApi";
class Search extends React.Component {
flatListRef = null; // declaring this here to make it explicit that we have this available
constructor(props) {
super(props);
this.searchedText = "";
this.state = {
films: [],
isLoading: false,
};
}
_loadFilms = () => {
// needs to be an arrow function so `this` is bound to this component
this.scrollToIndex(); // assumed you meant to actually call this?
if (this.searchedText.length > 0) {
this.setState({isLoading: true});
getFilmsFromApiWithSearchedText(this.searchedText).then(data => {
this.setState({
films: data.results,
isLoading: false,
});
});
}
};
// needs arrow to use `this`
_searchTextInputChanged = text => {
this.searchedText = text;
};
// needs arrow to use `this`
_displayLoading = () => {
// better to return null if not loading, otherwise return loading indicator
if (!this.state.isLoading) return null;
return (
<View style={styles.loading_container}>
<ActivityIndicator size='large' />
</View>
);
};
scrollToIndex = () => {
// you previously had this inside another method
this.flatListRef && this.flatListRef.scrollToIndex({index: 1}); // checking it exists first
};
render() {
return (
<View style={styles.main_container}>
<TextInput
style={styles.textinput}
placeholder='Titre du film'
onChangeText={text => this._searchTextInputChanged(text)}
onSubmitEditing={this._loadFilms} // no need to create a new anonymous function here
/>
<Button title='Rechercher' onPress={this._loadFilms} />
<View>
<FlatList
data={this.state.films}
ref={ref => (this.flatListRef = ref)}
keyExtractor={item => item.id.toString()}
onEndReachedThreshold={1} // corrected typo
onEndReached={() => {
console.log("TOC");
}}
renderItem={({item}) => <FilmItem film={item} />}
/>
</View>
{this._displayLoading()}
</View>
);
}
}
const styles = StyleSheet.create({
main_container: {
marginTop: 60,
padding: 15,
flex: 1,
},
loading_container: {
position: "absolute",
left: 0,
right: 0,
top: 100,
bottom: 0,
alignItems: "center",
paddingTop: 50,
backgroundColor: "white",
},
textinput: {
height: 50,
borderColor: "#999999",
borderWidth: 1,
paddingLeft: 5,
},
});
export default Search;
Using this code as example:
import React, { Component } from 'react';
import { Text, View, FlatList, Dimensions, Button, StyleSheet } from 'react-native';
const { width } = Dimensions.get('window');
const style = {
justifyContent: 'center',
alignItems: 'center',
width: width,
height: 50,
flex: 1,
borderWidth: 1,
};
const COLORS = ['deepskyblue','fuchsia', 'lightblue '];
function getData(number) {
let data = [];
for(var i=0; i<number; ++i)
{
data.push("" + i);
}
return data;
}
class ScrollToExample extends Component {
getItemLayout = (data, index) => (
{ length: 50, offset: 50 * index, index }
)
getColor(index) {
const mod = index%3;
return COLORS[mod];
}
scrollToIndex = () => {
let randomIndex = Math.floor(Math.random(Date.now()) * this.props.data.length);
this.flatListRef.scrollToIndex({animated: true, index: randomIndex});
}
scrollToItem = () => {
let randomIndex = Math.floor(Math.random(Date.now()) * this.props.data.length);
this.flatListRef.scrollToIndex({animated: true, index: "" + randomIndex});
}
render() {
return (
<View style={styles.container}>
<View style={styles.header}>
<Button
onPress={this.scrollToIndex}
title="Tap to scrollToIndex"
color="darkblue"
/>
<Button
onPress={this.scrollToItem}
title="Tap to scrollToItem"
color="purple"
/>
</View>
<FlatList
style={{ flex: 1 }}
ref={(ref) => { this.flatListRef = ref; }}
keyExtractor={item => item}
getItemLayout={this.getItemLayout}
initialScrollIndex={50}
initialNumToRender={2}
renderItem={({ item, index}) => (
<View style={{...style, backgroundColor: this.getColor(index)}}>
<Text>{item}</Text>
</View>
)}
{...this.props}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
header: {
paddingTop: 20,
backgroundColor: 'darkturquoise',
alignItems: 'center',
justifyContent: 'center'
}
});
export default class app extends Component {
render() {
return <ScrollToExample
data={getData(100)}
/>
}
}
I tried to add a ref but it doesn't work :
// Components/Search.js
import React from 'react'
import { StyleSheet, View, TextInput, Button, Text, FlatList, ActivityIndicator } from 'react-native'
import FilmItem from './filmItem'
import { getFilmsFromApiWithSearchedText } from '../API/TMDBApi'
class Search extends React.Component {
constructor(props) {
super(props)
this.searchedText = ""
this.state = {
films: [],
isLoading: false,
}
}
_loadFilms() {
{this.scrollToIndex}
if (this.searchedText.length > 0) {
this.setState({isLoading: true})
getFilmsFromApiWithSearchedText(this.searchedText).then(data => {
this.setState({
films: data.results,
isLoading: false
})
})
}
}
_searchTextInputChanged(text) {
this.searchedText = text
}
_displayLoading() {
if (this.state.isLoading) {
return (
<View style={styles.loading_container}>
<ActivityIndicator size='large'/>
</View>
)
}
scrollToIndex = () => {
this.flatListRef.scrollToIndex({index: 1});
}
}
render() {
return (
<View style={styles.main_container}>
<TextInput
style={styles.textinput}
placeholder='Titre du film'
onChangeText={(text) => this._searchTextInputChanged(text)}
onSubmitEditing={() => this._loadFilms()}
/>
<Button title='Rechercher' onPress={() => this._loadFilms()} />
<View>
<FlatList
data={this.state.films}
ref={(ref) => { this.flatListRef = ref; }}
keyExtractor={(item) => item.id.toString()}
onEndReachedThreashold={1}
onEndReached={() => {console.log("TOC")}}
renderItem={({item}) => <FilmItem film={item}/>}
/>
</View>
{this._displayLoading()}
</View>
)
}
}
const styles = StyleSheet.create({
main_container: {
marginTop: 60,
padding: 15,
flex: 1,
},
loading_container: {
position: 'absolute',
left: 0,
right: 0,
top: 100,
bottom: 0,
alignItems: 'center',
paddingTop: 50,
backgroundColor: 'white',
},
textinput: {
height: 50,
borderColor: '#999999',
borderWidth: 1,
paddingLeft: 5,
}
})
export default Search
I'm sur I am doing something wrong, but don't find the error. Thanks a lot for your help.