React-native scroll to top with pull to refresh iOS - react-native

I have a react-native app, that I want to scroll to top when the FlatList is refreshing in iOS. I can scroll to the top of the FlaList by using:
this.componentRef.FlatList.scrollToOffset({x: 0, y: 0, animated: true});
Now, the list has Pull-to-refresh on, so I would like to scroll above the refreshing indicator in iOS.
I tried:
this.componentRef.FlatList.scrollToOffset({x: 0, y: -20, animated: true});
and declaring the RefreshControl with a ref as refreshcontrol (using callback ref declaration):
this.componentRef.FlatList.refreshcontrol.scrollToOffset({x: 0, y: 0, animated: true});
and
this.componentRef.refreshcontrol.scrollToOffset({x: 0, y: 0, animated: true});
But none work. Is anyone aware of a way I can scroll above the refreshing indicator, if its on? This only happens in iOS as Android's refreshing indicator works differently.
UPDATE:
As scrollToOffset is not available for the RefrehControl component, it won;t work. Which brings me back to how can I scroll above a RefreshControl in a FlatList. My last attempt:
this.FlatList.scrollToOffset({x: 0, y: 0, animated: true});
Still scrolls to the beginning of the list, yet the "RefreshControl" is visually hidden.
I also tried adding an empty ScrollView above and scrolling to it, because it is empty, it did not work. Any ideas?
UPDATE 2
To clarify, this is how everything is called (simplified):
_scrollAndRefresh method in Main.js:
_scrollAndRefresh = () => {
this.setState({
loading: true
}, () => {
this.CustomFlatList._scrollToTop();
});
}
Rendering the component in Main.js:
<CustomFlatList ref={(ref) => {
this.CustomFlatList = ref}}
onRefresh={this._handleRefresh} loading={this.state.loading}/>
_handleRefresh method in Main.js:
_handleRefresh = () => {
this.setState({
loading: true
}, () => {
// REFRESH ACTION
})
};
_scrollToTop method CustomFlatList.js:
_scrollToTop = () => {
if (Platform.OS === 'ios' && this.props.loading) {
this.FlatList.scrollToOffset({x: 0, y: 0, animated: true});
}
else {
this.FlatList.scrollToOffset({x: 0, y: 0, animated: true});
}
}
And FlatList CustomFlatList.js:
<FlatList
ref={(ref) => { this.FlatList = ref; }}
refreshControl={<RefreshControl
refreshing={this.props.loading}
onRefresh={this.props.onRefresh}
/>}
/>

Since <RefreshControl /> detect refresh behavior from gesture, <FlatList /> scroll method has no effect on it; And you are just attempt to hack it.
Suggest to do it this way. You still scroll to top and shows refresh, and more directly:
constructor(props) {
super(props);
this.state = {
refreshing: false,
}
}
/// scroll to top, and show refresh at the same time
scrollToTopAndRefresh() {
this.componentRef.FlatList.scrollToOffset({x: 0, y: 0, animated: true});
this.setState({
refreshing: true,
}, () => {
this.refresh();
});
}
refresh() {
/// put your refresh logic here
}
componentRef = {};
render() {
return (
<FlatList ref={ (ref) => this.componentRef.FlatList = ref }
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={() => this.refresh()}
/>
}
/>
);
}
Update 2:
I made a simple workable code for your needs.
import React, { Component } from 'react';
import {
Image,
View,
FlatList,
Text,
StyleSheet,
Button,
RefreshControl,
} from 'react-native';
export class App extends Component {
constructor(props) {
super(props);
this.scrollToTopAndRefresh = this.scrollToTopAndRefresh.bind(this);
this.doRefresh = this.doRefresh.bind(this);
this.state = {
refreshing: false,
}
}
scrollToTopAndRefresh() {
this.flatlistref.scrollToOffset({y: 0, animated: true});
this.setState({refreshing: true}, this.doRefresh);
}
doRefresh() {
/// do refresh work here /////
//////////////////////////////
setTimeout( () => this.setState({refreshing: false}), 1000);
}
flatlistref = null;
render() {
return (
<View style={{flex: 1}}>
<FlatList
ref={(ref) => this.flatlistref = ref}
data={Array(30).fill(1)}
renderItem={() => <Text style={styles.line}>This is one line.</Text>}
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this.doRefresh}
/>
}
/>
<Button title='Scroll To Top' onPress={this.scrollToTopAndRefresh} />
</View>
)
}
}
const styles = StyleSheet.create({
line: {
height: 50,
paddingTop: 17,
textAlign: 'center',
backgroundColor: 'orange',
borderWidth: 1,
borderColor: 'purple',
}
});
Result:

I have something like this in my app:
<FlatList
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={() => this._onRefresh()}
/>}
data=...
/>
Maybe something like this would work:
this.componentRef.FlatList.scrollToOffset({x: 0, y: 0, animated: true});
this.setState({refreshing: true});
this._onRefresh();
this.setState({refreshing: false});

Related

Unexpected behaviour using seek in react-native-video

In react-native-video, whenever I click on the (custom) progress bar on a value less than 50% (half of it), the video jumps to start instead of seeking to the right time. When I click above 50%, it goes to 50%. It's not actually 50, more like 55-60 but whatever. This is really weird, was not able to find anything online!
import Video from 'react-native-video';
import ProgressBar from "react-native-progress/Bar";
class Welcome extends React.Component {
player;
constructor(props) {
super(props)
//this.player = React.createRef();
this.state = {
paused: false,
loaded: false,
progress: 0,
duration: 0,
pressed:false,
screenType: 'contain',
};
console.log("--- Screen --- Welcome")
}
componentDidMount = () => {
setTimeout(() => {
this.player.seek(8)
},8000)
}
handleMainButtonTouch = () => {
console.log("inside handleMainButtonTouch")
console.log(this.state.progress)
if (this.state.progress >= 1) {
this.player.seek(0);
}
this.setState(state => {
return {
paused: !state.paused,
};
});
};
handleProgressPress = e => {
const position = e.nativeEvent.locationX;
const progress = parseFloat(position / 250) * this.state.duration;
const isPlaying = !this.state.paused;
this.player.seek(progress);
};
handleProgress = progress => {
this.setState({
progress: parseFloat(progress.currentTime) / parseFloat(this.state.duration),
});
};
handleEnd = () => {
this.setState({
paused: true ,
progress: 0 ,
});
this.player.seek(0);
};
handleLoad = meta => {
this.setState({
loaded: true,
duration: meta.duration,
});
};
handleFullScreen = () => {
if (this.state.screenType == 'contain')
this.setState({ screenType: 'cover' });
else this.setState({ screenType: 'contain' });
};
render() {
return (
<View style={styles.container}>
<View style={this.handleOuterViewStyle()}>
<Video
paused={this.state.paused}
source={{uri: "https://res.cloudinary.com/dy6bbey4u/video/upload/v1565532579/fam/videos/sample.mp4"}}
resizeMode={this.state.screenType}
onLoad={this.handleLoad}
onProgress={this.handleProgress}
onEnd={this.handleEnd}
ref={ref => {
this.player = ref;
}}
/>
{ this.state.loaded &&
<View style={styles.controls}>
<TouchableWithoutFeedback onPress={this.handleMainButtonTouch}>
<Text>Play</Text>
</TouchableWithoutFeedback>
<TouchableWithoutFeedback onPress={this.handleProgressPress}>
<View>
<ProgressBar
animated={false}
progress={this.state.progress}
color="#FFF"
borderColor="#FFF"
width={250}
height={20}
/>
</View>
</TouchableWithoutFeedback>
<TouchableWithoutFeedback onPress={this.handleFullScreen}>
<Text style={styles.fullscreenButton}>Full</Text>
</TouchableWithoutFeedback>
</View>
}
</View>
</View>
)
}
}
export default Welcome
I was also facing the same problem. Whenever i backward the video, it goes forward. The problem is depending on the video format. I was using the Webm format. Now the mp4 format solved the problem.
P.S. Sorry for the late reply.
ffmpeg -i input.mp4 -force_key_frames "expr:gte(t,n_forced*1)" output.mp4
Solved by forcing adding keyframes to the video.
So the solution for me was changing line 640 from android/src/main/java/com/brentvatne/react/ReactVideoView.java
- super.seekTo(msec);
+ mMediaPlayer.seekTo(msec,3);
Original response: https://github.com/react-native-video/react-native-video/issues/2230#issuecomment-892982288

Can't scroll absolute positioned FlatList when there is nothing behind it

What I'm trying to do:
Make a search bar and show the results in a flat list under the bar.
What I have:
I have a SearchBar and a FlatList, the FlatList needs to but in absolute position so it covers the content on the bottom of the search bar
The Problem:
The FlatList is covering the search bar when it's active and I can't scroll the list or select an item. What I noticed is that if i try to select an item or scroll the list when clicking where the SearchBar should be appearing, I can select and scroll the list.
What I need:
The FlatList to show under the SearchBar and be able to scroll it.
I could use top: 50 to show the FlatList under the SearchBar but it doesn'r seems good
Observations: I'm not that good at styles
import React, { Component } from 'react'
import { Text, View, StyleSheet, TouchableHighlight, FlatList } from 'react-native'
import {
Slider,
SearchBar,
ListItem,
} from 'react-native-elements'
export default class SearchForm extends Component {
state = {
pages: 1,
displayList: false,
itemsPerPage: 5,
}
componentDidMount = async () => {
const {
data = [],
itemsPerPage = 5,
} = this.props
await fetch('https://servicodados.ibge.gov.br/api/v1/localidades/estados', {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
})
.then(res => res.json())
.then(data => this.setState({
data: data,
displayData: data.slice(0, itemsPerPage)
}))
console.log(this.state.data.length)
}
updateSearch = search => {
const { data, itemsPerPage } = this.state
let s = search ? search.toLowerCase() : ''
this.setState({
displayData: data.filter(res => res.nome.toLowerCase().includes(s)).slice(0, itemsPerPage),
displayList: true,
search: search,
pages: 1,
})
if(this.flatListRef){
this.flatListRef.scrollToOffset({ animated: true, offset: 0 })
}
}
loadMore = () => {
const { pages, displayData, data, search, itemsPerPage } = this.state
const start = pages * itemsPerPage
let end = (pages + 1) * itemsPerPage
let s = search ? search.toLowerCase() : ''
const newData = data.filter(res => res.nome.toLowerCase().includes(s)).slice(start, end)
this.setState({
displayData: [...displayData, ...newData],
pages: pages + 1,
})
console.log(this.state.displayData.length)
}
selectItem = (value) => {
this.setState({
search: value,
displayList: false,
})
}
renderItem = ({ item, index }) => {
return (
<ListItem
style={styles.flatListItem}
containerStyle={styles.flatListItemCointainer}
key={index}
title={item.nome}
onPress={() => this.selectItem(item.nome)}
/>
);
}
render() {
const {
search,
displayData = [],
displayList,
} = this.state
return (
<View style={styles.container}>
<SearchBar
ref={search => { this.search = search }}
placeholder="Type Here..."
leftIcon={false}
noIcon
onChangeText={this.updateSearch}
value={search}
/>
{displayList && <FlatList
style={styles.flatList}
ref={ref => this.flatListRef = ref}
data={displayData}
keyExtractor={(item, index) => index.toString()}
renderItem={this.renderItem}
onEndReached={this.loadMore}
onEndReachedThreshold={0.5}
/>}
<TextInput
style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
onChangeText={(text) => this.setState({ text })}
value={this.state.text}
/>
<TextInput
style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
onChangeText={(text) => this.setState({ text })}
value={this.state.text}
/>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
alignSelf: 'stretch',
backgroundColor: '#fff',
},
flatList: {
height: 200,
width: '100%',
position: 'absolute',
},
flatListItemCointainer: {
backgroundColor: 'rgba(0,0,0,1)'
}
})
Edit: I just change the code a little bit to show what I'm trying to do. Under the SearchBar will have other components (e.g. TextInput) and when the list is active, the list should go on top of that components.
With Shashin Bhayani answer, it's not going on top of things under it, only pushing it down.
This issue in android, to solve it :
import { FlatList } from 'react-native-gesture-handler';
Adding bottom: 0 solved my issue I am using zero because I want the Faltlist to end at the very bottom of the screen it can be any number.
Make sure there is no flex: 1 in flatlist style, contentContainerStyle or on the parent flatlist.
Try this and let me know this solved your issue or not.

why does FlatList keep loading forever?

I am using FlatList to write an infinite scroll, but it keeps sending request to my server forever. please see the code blow. I don't find any article clarify when the next page will load, what exactly does the onEndReached will be triggered.
import React, { Component } from 'react';
import { View, Text, FlatList, StyleSheet, ActivityIndicator, AsyncStorage } from 'react-native';
import { connect } from 'react-redux';
import { loadOrders } from '../redux/modules/Order';
import OrderListItem from './OrderListItem';
import { forOwn, isEmpty, reduce } from 'lodash';
class OrderList extends Component {
constructor(props) {
super(props);
this.state = {
page: 1,
error: null,
};
}
componentDidMount() {
this.loadOrders();
}
loadOrders = () => {
const { page } = this.state;
AsyncStorage.getItem("userToken")
.then((value) => {
return `Bearer ${value}`;
})
.then((userToken) => {
return this.props.loadOrders(page, { Authorization: userToken });
})
.then((response) => {
this.setState({
error: response.error || null,
});
})
.catch(error => {
this.setState({ error});
})
;
}
handleLoadMore = () => {
this.loadOrders();
};
onPressItem = (id: string) => {
};
keyExtractor = (item, index) => `order-item-${item.id}`;
renderItem = ({item}) => (
<OrderListItem
order={item}
onPressItem={this.onPressItem}
/>
);
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: "86%",
backgroundColor: "#CED0CE",
marginLeft: "14%"
}}
/>
);
};
renderFooter = () => {
if (!this.props.loading) return null;
return (
<View
style={{
paddingVertical: 20,
borderTopWidth: 1,
borderColor: "#CED0CE"
}}
>
<ActivityIndicator animating size="large" />
</View>
);
};
render() {
const { orders} = this.props;
if (orders.length> 0) {
return (
<View containerStyle={styles.container} >
<FlatList
data={orders}
keyExtractor={this.keyExtractor}
renderItem={this.renderItem}
ListFooterComponent={this.renderFooter}
ItemSeparatorComponent={this.renderSeparator}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={0.5}
/>
</View>
);
}
return <View>
<Text>empty</Text>
</View>
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
borderTopWidth: 0,
borderBottomWidth: 0
},
item: {
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#ccc'
}
});
const mapStateToProps = state => {
let order = state.get('order').toJS();
return {
orders: isEmpty(order.entities) ? [] : reduce(order.entities, (result, value) => {
result.push({ key: value.id, ...value });
return result;
}, []),
loading: order.loading
};
};
const mapDispatchToProps = {
loadOrders
};
export default connect(mapStateToProps, mapDispatchToProps)(OrderList);
the if part is false , but the onEndReached methods is still called, I must be insane.
the
Change this
onEndReachedThreshold={0.5}
to this:
onEndReachedThreshold={0}
Right now you're calling the end reached when you're halfway through. You can also try adding this to the FlatList:
legacyImplementation = {true}
If this still won't work I would recommend doing the 'pull' onRefresh. A nice example for you: https://www.youtube.com/watch?v=pHLFJs7jlI4
i met the problem too, in my case:
renderFooter somethings render null(height: 0) when loaded, but render ActivityIndicator when loading, and ActivityIndicator has its heigth bigger than 0(null's height)
when heigth change from 0 to ActivityIndicator's height, it will call onEndReached again
and you say the if part is false, i think its because it's not really false。
when code really run in FlatList, the if part is true, so it call onEndReached, and then the _scrollMetrics.contentLength or this._sentEndForContentLength has changed for some reason before your console in chrome. which makes the if part return false
above is all my thought for now, and i am still debugging for this problem, hope this answer will help you all

Does react-native view not able to scroll without ScrollView

This is the tutorial's link(https://medium.com/react-native-development/how-to-use-the-flatlist-component-react-native-basics-92c482816fe6) from where I'm learning react-native.
Everything is working fine except the view is not scrolling
import React, { Component } from 'react';
import { FlatList, Text, View } from 'react-native';
import { List, ListItem } from "react-native-elements";
type Props = {}
export default class FlatListDemo extends Component<Props> {
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
page: 1,
seed: 1,
error: null,
refreshing: false,
};
}
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const { page, seed } = this.state;
const url = `https://randomuser.me/api/?seed=${seed}&page=${page}&results=20`;
this.setState({
loading: true
});
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
data: page === 1 ? res.results : [...this.state.data, ...res.results],
error: res.error || null,
loading: false,
refreshing: false
})
})
.catch(error =>
this.setState({
error,
loading: false
})
)
};
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: "86%",
backgroundColor: "#CED0CE",
marginLeft: "14%"
}}
>
</View>
)
};
render() {
return (
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
roundAvatar
title={`${item.name.first} ${item.name.last}`}
subtitle={item.email}
avatar={{ uri: item.picture.thumbnail }}
containerStyle={{ borderBottomWidth: 0 }}
/>
)}
ItemSeparatorComponent={this.renderSeparator}
onEndReachedThreshold={50}
/> )
}}
Is it compulsory to use ScrollView to make able to scroll react-native view or we can do it
using any CSS or something else
When I tried to run this code in this given link: https://snack.expo.io/rkOsCaiD7 to find for a solution what I found was scroll view was working fine with the above code. Just try to reload and run it in react native by which I feel it might resolve your issue.

Performance issue of flatList in react native

I've tried flatlist but it has a bit of performance issues in android.
As I scroll down, it loads the list. But afterwards, it shows blank while scrolling upwards.
After reaching the end of the screen, it stops for a while and then loads the datas. Why is it not showing loader (activity indicator) at the bottom? Why is onEndReached and onEndReachedThreshold not working?
Plz have a look at the video here
https://youtu.be/5tkkEAUEAHM
My code:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
FlatList,
ActivityIndicator,
} from 'react-native';
import { List, ListItem, SearchBar } from "react-native-elements";
export default class FlatListExample extends Component
{
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
page: 1,
seed: 1,
error: null,
refreshing: false,
};
}
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const { page, seed } = this.state;
const url = `https://randomuser.me/api/?seed=${seed}&page=${page}&results=20`;
console.log('url', url);
this.setState({ loading: true });
setTimeout(()=>{
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
data: [...this.state.data, ...res.results],
error: res.error || null,
loading: false,
refreshing: false
});
})
.catch(error => {
this.setState({ error, loading: false });
});
},0);
};
renderFooter = () => {
if (!this.state.loading) return null;
return (
<View
style={{
paddingVertical: 20,
borderTopWidth: 1,
borderColor: "#CED0CE"
}}
>
<ActivityIndicator animating size="large" />
</View>
);
};
handleLoadMore = () =>{
this.setState({
page:this.state.page + 1,
},()=>{
this.makeRemoteRequest();
})
}
render() {
return (
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
roundAvatar
title={`${item.name.first} ${item.name.last}`}
subtitle={item.email}
avatar={{ uri: item.picture.thumbnail }}
/>
)}
keyExtractor={item => item.email}
ListFooterComponent={this.renderFooter}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={50}
/>
);
}
}
AppRegistry.registerComponent('FlatListExample', () => FlatListExample);
I've noticed that you're not setting initialNumToRender. From the docs:
initialNumToRender: number
How many items to render in the initial batch. This should be enough
to fill the screen but not much more. Note these items will never be
unmounted as part of the windowed rendering in order to improve
perceived performance of scroll-to-top actions.
So you'll want to estimate how many cells you expect to be visible at any given time and set it to that. I'd also recommend if you haven't already to update to the latest react-native which includes various improvements on the FlatList component.