How to make two ScrollViews synchronously scroll simultaneously on React Native? - reactjs

I have two ScrollViews. When I scroll one the other should also scroll.
I tried using onScroll event, but it has a delay, the second ScrollView scrolls after a while. I really need it to scroll exactly at the same time.
Is there any other way to do it?
let scroll_ref_1 = null;
let scroll_ref_2 = null;
const Page = () => {
return (
<SafeAreaView>
<ScrollView
ref={ref => scroll_ref_1 = ref}
horizontal
onScroll={e => {
if(scroll_ref_2 !== null) {
scroll_ref_2.scrollTo({
x: e.nativeEvent.contentOffset.x,
animated: false,
});
}
}}
>
...
</ScrollView>
<ScrollView
horizontal
ref={ref => scroll_ref_2 = ref}
>
...
</ScrollView>
</SafeAreaView>
);
};

You can get a similar effect if you use flatlist with 2 columns.

Related

How to use useEffect inside a View in React Native

I want to use an useEffect inside a return (inside a Text that is inside multiple View to be exact) and from what I know, I must use {...} in order to say that what I write is some code. Howether I got a blank screen without errors and I don't know where is the issue with my code.
Here is the code:
const [pass, setPass] = useState(0);
...
return (
<View>
<FlatList
data={letter.description}
numColumns={2}
keyExtractor={(_, index) => index.toString()}
renderItem={({ item }) => {
if (pass >= letter.description?.length) {
useEffect(() => {
setPass((prev) => 0);
});
}
return (
<View>
<Text>
{letter.data[pass]}
{"\n"}
</Text>
<Text>
{letter.description[pass]}
{useEffect(() => {
setPass((prev) => prev + 1);
})}
{"\n"}
</Text>
</View>
);
}}
/>
</View>
letter is my data, but you can ignore it. I just keep it here to explain why I need the pass
Why use useEffect to setState?
just set the state :
if (pass >= letter.description?.length) { setPass((prev) => 0); }
You can use UseEffect to render your component when you want to render it.
More of this here : https://reactjs.org/docs/hooks-effect.html

Scroll down to a specific View in React Native ScrollView

I want to be able to scroll a after pressing button so that it visible on screen. How can I tell a react-native ScrollView move to a certain?
Hello you can use the property scrollTo like below
import {useRef} from "react"
import {
ScrollView,
Button,
} from 'react-native';
const YouComp = () => {
const refScrollView = useRef(null);
const moveTo = () => {
refScrollView.current.scrollTo({x: value1, y: value2});
// or just refScrollView.current.scrollTo({x: value1}); if you want to scroll horizontally
// or just refScrollView.current.scrollTo({y: value2}); if you want to scroll vertically
}
return (<>
<Button onPress={moveTo} title="title" />
<ScrollView ref={refScrollView}>
...
</ScrollView>
</>);
}
You can set whether x or y value or both
Check the full doc here
First you need to create reference to element
this.scrollViewRefName = React.createRef()
Then pass it to ref attribute
<ScrollView ref={this.scrollViewRefName}>
Then you trigger the function from your button with scrollToTheEnd or wherever you want to scroll within the element
<View style={styles.ButtonContainer}>
<Button onPress={() => { this.scrollViewRef.current.scrollToTheEnd }} />
</View>
Note that you may need extra callback function in onPress depending on from which context you have the components
using ref and scrollTo is just bullshit and dose not always work.
Here is how i did it.
const [scrollYPosition, setScrollYPosition] = useState(0);
const [data, setData] = useState([]);
const goToItem = () => {
// lets go to item 200
// the 200 is the item position and the 150 is the item height.
setScrollYPosition(200 * 150);
}
<ScrollView contentOffset = {
{
y: scrollYPosition,
x: 0
}
}>
// papulate your data and lets say that each item has 150 in height
</ScrollView>

Flatlist onEndReached endless loop

I am using state to store the following data.
state = {
refresh: true,
isFetching: true,
showLoadingBottom: false,
data: []
};
on componentDidMount I manually call a function _fetchData which loads data into this.state.data.
When the flatlist is scrolled to the end it fires _fetchData twice which ends up returning the same data twice ( which is another problem, why does it fire twice? ).
Once the flatlist reaches the end ie, no more data is returned from the server, it goes into an endless loop since the onEndReached continuously fires over and over again even though no new data is returned from the server and this.state.data remains the same.
This is my render code
render() {
return (
<View
style={{
flex: 1
}}>
<FlatList
refreshControl={
<RefreshControl
refreshing={this.state.refresh}
onRefresh={() => {
this.setState({
refresh: true
}, this._fetchData);
}}
title={"Pull To Refresh"}
tintColor={darkGrey}
titleColor={darkGrey}/>
}
onEndReachedThreshold={0.5}
onEndReached={() => {
this.setState({
showLoadingBottom: true
}, () => {
this._fetchData();
});
}}
showsVerticalScrollIndicator={false}
data={this.state.data}
ListFooterComponent={() => {
return (
this.state.showLoadingBottom &&
<View style={{padding: 10}}>
<ActivityIndicator size="small" color={colorAccent}/>
</View>
);
}}
renderItem={this._renderItem}
keyExtractor={(item) => item.id.toString()}
removeClippedSubviews={true}
/>
</View>
);
}
Here is my solution that can maybe be changed to suit other peoples needs:
Basically the important parts are onEndReached={this.state.moreData && this.retrieveMore}. So you can test inside your onEndReached function weather there is anymore data (In my case if we only return 1 object i know it's finished) then set state this.state.moreData to false.
<SafeAreaView style={styles.container}>
<FlatList
data={Object.values(this.state.documentData)}
// Render Items
renderItem={({ item }) => (
<ItemSelector
item={item}
onPress={() => {this.selectItem(item)}}
/>
)}
// On End Reached (Takes in a function)
onEndReached={this.state.moreData && this.retrieveMore}
// How Close To The End Of List Until Next Data Request Is Made
onEndReachedThreshold={1}
ListEmptyComponent={
<Text>No jobs to show</Text>
}
/>
</SafeAreaView>
retrieveMore = async () => {
try {
// Set State: Refreshing
this._isMounted && this.setState({ refreshing: true });
fbDb.ref('job')
.orderByKey()
.startAt(this.state.lastVisible) //Start at the last item we found
.limitToFirst(this.state.limit) //Limit queries returned per page
.once('value', snapshot => {
//check if we got a result
if(snapshot.numChildren() > 1){
.....
this._isMounted && this.setState({
documentData: newstate,
lastVisible: lastVisible,
refreshing: false, //Hide loading icon
});
} else {
this._isMounted && this.setState({
refreshing: false, //Hide loading icon
moreData: false
});
}
});
}
catch (error) {
console.log(error);
}
};
I have a similar problem. In my case it is because the ListFooterComponent.
If you render the ListFooterComponent with this pattern or equivalent
onEndReachedThreshold={x} // for any x >= 0
ListFooterComponent={() => {
if (isDataFetching) {
return <SomeComponentWithSomeHeight />
} else {
return undefined;
}
}}
It will trigger onEndReached infinitely when the user scrolls down the end of the list (or if your content is not longer than the list's visibility area).
And it is because the presence and absence of the <SomeComponentWithSomeHeight /> affects the height of the content and thus triggers the endReached re-calculation.
And following are the possible solution I can think of.
Use negative onEndReachedThreshold that is always "higher" than the height of the ListFooterComponent. But I don't like this solution because it is difficult to know the "higher" (it is relative to the FlatList's visibility area). And the negative onEndReachedThreshold may cause some issue on Android.
Implement your own loading overlay outside of the FlatList so that the loading component does not affect the content height.
Set opacity = 0 to hide the ListFooterComponent instead of returning undefined, so that it is always there and the content height does not change when it becomes visible.
ListFooterComponent={() => {
return (
<View style={{ opacity: isDataFetching ? 1 : 0 }}>
<SomeComponentWithSomeHeight />
</View>
);
}}
You are using a component that is being rendered when you are loading data, right? So your flatlist needs to rerender. You meed to make sure you only call you fetch method once even thou your end is reached multiple times.

Flatlist scroll references with getItemLayoutProp

I've got a little problem with flatlist and scroll methods.
I have flatlist with comments and if new one is added I want to scroll list to the bottom to see added comment.
Using scrollToIndex doesn't work properly, native keep showing errors due to lack of getItemLayout, and here is another problem with setting this function - every item can have different size.
scrollToEnd has some issues, sometimes it scrolls to almost bottom of the page, sometimes to headerComponent elements which are set in flatlist props.
Do you have any ideas how to make it scroll to the bottom?
To use scrollToIndex you need to use getItemLayout. There is no point in using it if you have no intention to use getItemLayout. Here is an example taken from the react-native docs:
class ScrollToExample extends Component {
getItemLayout = (data, index) => (
{ length: 50, offset: 50 * index, index }
)
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 (
<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}
/>
);
}
}
https://gist.github.com/joshyhargreaves/b8eb67d24ce58a6d8bffb469f7eeaf39
Hope this helps!

React native infinity view pager

I need to create infinity view pager to display calendar days, and add an ability to user for swapping left/right and changing date.
As I see in the documentation, the view pager will work only with preset number of views, and also research some opensource packages - cant find anything about that.
So my question - how can I implement infinity swiping for calendar (or is it possible at all)?
I have an infinite viewpager made with VirtualizedList. It works on iOS an Android.
import React, { Component } from 'react'
import { View, Text, Dimensions, VirtualizedList } from 'react-native'
const { width, height } = Dimensions.get('window')
const startAtIndex = 5000
const stupidList = new Array(startAtIndex * 2)
class InfiniteViewPager extends Component {
//only use if you want current page
_onScrollEnd(e) {
// const contentOffset = e.nativeEvent.contentOffset
// const viewSize = e.nativeEvent.layoutMeasurement
// // Divide the horizontal offset by the width of the view to see which page is visible
// const pageNum = Math.floor(contentOffset.x / viewSize.width)
}
_renderPage(info) {
const { index } = info
return (
<View style={{ width, height }}>
<Text>
{' '}{`index:${index}`}{' '}
</Text>
</View>
)
}
render() {
return (
<VirtualizedList
horizontal
pagingEnabled
initialNumToRender={3}
getItemCount={data => data.length}
data={stupidList}
initialScrollIndex={startAtIndex}
keyExtractor={(item, index) => index}
getItemLayout={(data, index) => ({
length: width,
offset: width * index,
index,
})}
maxToRenderPerBatch={1}
windowSize={1}
getItem={(data, index) => ({ index })}
renderItem={this._renderPage}
onMomentumScrollEnd={this._onScrollEnd}
/>
)
}
}

Resources