Pause video in flatlist when out of view - react-native

I have a flatlist showing videos. I want that when a video goes out of the view it should be paused. I am maintaining the pause state in each of the Posts component.
class Posts extends React.PureComponent {
constructor() {
super()
this.state = {
pause: true,
}
return(){
<Video
pause={this.state.pause}
//other props
/>
}
}
I am using react-native-video.
I have tried using onViewableItemsChanged prop of Flatlist but it doesn't change the state.
I tried this .
But it doesn't seem to work for me.
How should I proceed ?

Here is a possible solution using react-native-inviewport. This dependency is only a single index file that contains a component that has a callback when view is in the viewport. It could easily be modified to suit your needs.
I have constructed a very simple app that has a FlatList. The 11th item in the FlatList is a video. That should mean that the video is off the screen when the App renders so the viddeo won't be playing, once the video comes fully into the viewport it should then start playing.
App.js
import * as React from 'react';
import { Text, View, StyleSheet, FlatList } from 'react-native';
import Constants from 'expo-constants';
import VideoPlayer from './VideoPlayer';
export default class App extends React.Component {
state = {
data: [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
}
renderItem = ({item, index}) => {
if (index === 10) {
return <VideoPlayer />
} else {
return (
<View style={{height: 100, backgroundColor: '#336699', justifyContent: 'center', alignItems: 'center'}}>
<Text>{index}</Text>
</View>
)
}
}
keyExtractor = (item, index) => `${index}`;
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
}
});
VideoPlayer.js
This is a component that contains the Video component. The video is wrapped in the InViewPort component that has a callback function. The callback returns true when the component it surrounds is completely in the viewport and false when it is not fully in the viewport. The callback calls this.handlePlaying which in turn calls either this.playVideo or this.pauseVideo depending on the boolean value.
import React, {Component} from 'react';
import { View, StyleSheet } from 'react-native';
import { Video } from 'expo-av';
import InViewPort from './InViewPort';
//import InViewPort from 'react-native-inviewport; // this wouldn't work in the snack so I just copied the file and added it manually.
export default class VideoPlayer extends React.Component {
pauseVideo = () => {
if(this.video) {
this.video.pauseAsync();
}
}
playVideo = () => {
if(this.video) {
this.video.playAsync();
}
}
handlePlaying = (isVisible) => {
isVisible ? this.playVideo() : this.pauseVideo();
}
render() {
return (
<View style={styles.container}>
<InViewPort onChange={this.handlePlaying}>
<Video
ref={ref => {this.video = ref}}
source={{ uri: 'http://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4' }}
rate={1.0}
volume={1.0}
isMuted={false}
resizeMode="cover"
shouldPlay
style={{ width: 300, height: 300 }}
/>
</InViewPort>
</View>
)
}
}
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center'
}
});
Here is a snack showing it working https://snack.expo.io/#andypandy/video-only-playing-when-in-viewport
I should point out that if the video is not fully in the viewport then it will not play. I am sure some tweaking could be done to react-native-inviewport so that it would play the video if it was partially in the viewport if that is what you wanted, perhaps by passing the height of the video to the InViewPort component.

here is how i simply did the trick
inside my card component that have video
<Video ...
paused={currentIndex !== currentVisibleIndex}
/>
both currentIndex and currentVisibleIndex are passed the component from the FlatList parent
my FlatList pass the renderItem index as currentIndex
<FlatList
data={[...]}
renderItem={({ item, index }) => (
<GalleryCard
{...item}
currentIndex={index}
currentVisibleIndex={currentVisibleIndex}
/>
)}
onViewableItemsChanged={this.onViewableItemsChanged}
viewabilityConfig={{
viewAreaCoveragePercentThreshold: 90
}}
finally my this is how to calculate currentVisibleIndex
please make sure to read viewabilityConfig
onViewableItemsChanged = ({ viewableItems, changed }) => {
if (viewableItems && viewableItems.length > 0) {
this.setState({ currentVisibleIndex: viewableItems[0].index });
}
};
please let me know if this is helpful

I finally did this using redux. Not sure whether it is the right way.
Home.js
_renderItem = ({item, index}) => <Posts item={item} />
viewableItemsChanged = (props) => {
let {changed} = props;
let changedPostArray = [];
changed.map((v,i) => {
let {isViewable, item} = v;
if(!isViewable) {
let {post_id, type} = item;
if(type === 1)
changedPostArray.push(post_id);
}
});
if(changedPostArray.length != 0)
this.props.sendPostToPause(changedPostArray);
}
render() {
return(
<View style={{flex: 1}} >
<FlatList
ref={(ref) => this.homeList = ref}
refreshing={this.state.refreshing}
onRefresh={async () => {
await this.setState({refreshing: true, postsId: [0], radiusId: 0, followingId: 0});
this.getPosts();
}}
onEndReached={async () => {
if(!this.state.isEnd) {
await this.setState({isPaginate: true})
this.getPosts()
}
}}
onEndReachedThreshold={0.2}
removeClippedSubviews={true}
contentContainerStyle={{paddingBottom: 80}}
data={this.state.followersRes}
renderItem={this._renderItem}
viewabilityConfig={this.viewabilityConfig}
onViewableItemsChanged={this.viewableItemsChanged}
/>
</View>
<Footer callback={this.scrollToTopAndRefresh} />
</View>
)
}
export default connect(null, {sendPostToPause})(Home);
Posts.js
class Posts extends React.PureComponent {
constructor(){
super()
this.state: {
pause: false
}
}
componentDidUpdate(prevProps) {
if(prevProps != this.props) {
this.props.postIdArray.map((v) => {
if(v === prevProps.item.post_id) {
this.setState({pause: true})
}
})
}
}
render(){
return(
<Video
pause={this.state.pause}
//Other props
/>
)
}
}
const mapStateToProps = (state) => {
const {postId} = state.reducers;
return {
postIdArray: postId
}
}
export default connect(mapStateToProps, {sendPostToPause})(withNavigation(Posts));
Whenever the viewableItemsChanged is trigger I am adding the changed posts id in an array and calling the action with the array of post ids.
In the Posts component I am checking if the post ids match, if so I am setting the pause state to true.

Related

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

Expo SDK 29 FlatList onRefresh not calling

Using Expo SDK 29 for a react native application.
I would like to use a flat list component. This makes up the entirety of a SafeAreaView component. I make this point as there are lots of issues relating to a flat list inside of a scroll view which this is not.
The flat list shows a list of jobs.
I have added a jobLoading boolean to the redux state to manage when the list should show as refreshing and can confirm that this toggles as expected when firing the actions to fetch the data and the success.
When i add the props to the flat list for onRefresh and refreshing the component seems to work by showing the activity indicator in the UI but does not fire the onRefresh function. I have tried implementing the call in numerous ways but nothing happens. The result is that the activity indicator shows itself and never disappears.
As it's Expo SDK 29 the React Native version is 0.55.4
Anyone have any ideas of what to try. I've spent a couple of hours looking at this trying various things but suggestions are welcome.
Thanks in advance.
EDIT: Added the code for reference. Reducer for refreshing sets true when fetchJobs() is dispatched and false when a success or error is recieved. The console log for onRefresh never triggers.
import * as React from 'react'
import * as actions from '../../redux/actions'
import { ActivityIndicator, FlatList, KeyboardAvoidingView, Dimensions, SafeAreaView, StyleSheet, View } from 'react-native'
import { ApplicationState, JobState, Job } from '../../redux'
import { Button, Form, Input, Item, Text, Icon } from 'native-base'
import { JobListItem } from './jobListItem'
import { StateHandlerMap, compose, lifecycle, withPropsOnChange, withStateHandlers } from 'recompose'
import { connect } from 'react-redux'
interface ReduxStateProps {
jobs: JobState
refreshing: boolean
screenOrientation: string
}
interface ReduxDispatchProps {
fetchJobs: (param?: string) => any
}
export interface DataItem {
key: string
data: Job
}
interface ListProps {
jobList: DataItem[]
}
interface SearchStateProps {
timer: number | undefined
searchString: string
}
interface SearchHandlerProps extends StateHandlerMap<SearchStateProps> {
updateSearch: (searchString: string) => any
setTimer: (timer: number | undefined) => any
}
type OuterProps = {}
type InnerProps = OuterProps & ReduxStateProps & ReduxDispatchProps & ListProps & SearchStateProps & SearchHandlerProps
const enhance = compose<InnerProps, OuterProps>(
connect<ReduxStateProps, ReduxDispatchProps, OuterProps, ApplicationState>(
state => ({
jobs: state.job,
refreshing: state.jobLoading,
screenOrientation: state.screenOrientation
}),
dispatch => ({
fetchJobs: (param?: string) => dispatch(actions.jobs.request({ param }))
})
),
withPropsOnChange<ListProps, OuterProps & ReduxStateProps & ReduxDispatchProps>(
['jobs', 'screenOrientation'],
props => ({
jobList: props.jobs && Object.keys(props.jobs).map(job => ({ key: job, data: props.jobs[Number(job)] }))
})
),
withStateHandlers<SearchStateProps, SearchHandlerProps, OuterProps>(
{
timer: undefined,
searchString: ''
},
{
updateSearch: state => (searchString: string) => ({ searchString }),
setTimer: state => (timer: number | undefined) => ({ timer })
}
),
lifecycle<InnerProps, {}>({
componentDidMount() {
this.props.fetchJobs()
}
})
)
export const JobList = enhance(({ fetchJobs, jobList, refreshing, screenOrientation, searchString, setTimer, timer, updateSearch }) => {
const onSearchChange = (search: string) => {
clearTimeout(timer)
updateSearch(search)
const timing = setTimeout(() => {
fetchJobs(search)
}, 500)
setTimer(timing)
}
const onRefresh = () => {
console.log('requesting refresh')
fetchJobs()
}
return (
<SafeAreaView style={{ flex: 1}}>
<KeyboardAvoidingView style={{ flexDirection: 'row', justifyContent: 'space-evenly', paddingTop: 3, paddingRight: 3 }}>
<Form style={{ flex: 1, paddingLeft: 10, paddingRight: 10 }}>
<Item>
<Input
value={searchString}
onChangeText={(text: string) => onSearchChange(text)}
placeholder='Search'
/>
</Item>
</Form>
<Button onPress={() => {fetchJobs(); updateSearch('')}}>
<Icon name='refresh' />
</Button>
</KeyboardAvoidingView>
{refreshing &&
<View style={styles.refreshContainer}>
<Text style={{ paddingBottom: 10 }}>Fetching Data</Text>
<ActivityIndicator />
</View>
}
<FlatList
keyExtractor={item => item.key}
data={jobList}
renderItem={({ item }) =>
<JobListItem
screenOrientation={screenOrientation}
item={item}
/>
}
onRefresh={onRefresh}
refreshing={refreshing}
/>
</SafeAreaView>
)
})
const styles = StyleSheet.create({
refreshContainer: {
height: 60,
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
}
})
I'm having the exact same issue and I'm using expo SDK 30. But my case is a little bit different. The onRefresh function is called everytime I pull, however if I scroll down my list, and scroll back up fast, the loading indicator shows up, but my onRefresh function is not called.
My refreshing prop is set on my reducer, and my onRefresh function dispatches an action that fetches data and set refreshing true and false.
Here is my code:
class NoticiasScreen extends Component {
static navigationOptions = {
header: <Header
title='Notícias Alego'
leftComponent={<Image source={require('../../../assets/images/play_grande.png')} style={imageStyle} resizeMode='contain'/>}
/>
}
constructor(props) {
super(props);
this.renderItem = this.renderItem.bind(this);
this.keyExtractor = this.keyExtractor.bind(this);
this.renderContent = this.renderContent.bind(this);
this.navigateToNoticias = this.navigateToNoticias.bind(this);
this.carregarMaisNoticias = this.carregarMaisNoticias.bind(this);
this.onRefresh = this.onRefresh.bind(this);
}
componentDidMount() {
this.props.carregarNoticias(this.props.pagina);
}
renderItem({item}) {
return (
<NoticiaListItem noticia={item} abrirNoticia={this.navigateToNoticias} />
);
}
keyExtractor(item) {
return item.id.toString();
}
navigateToNoticias(noticia) {
this.props.navigation.navigate('NoticiasExibir', { id: noticia.id });
}
onRefresh() {
console.log('onRfresh');
this.props.carregarNoticias(1, true);
}
carregarMaisNoticias() {
const { carregarNoticias, pagina } = this.props;
carregarNoticias(pagina + 1);
}
renderContent() {
const { noticias, carregandoNoticias, erroNoticias } = this.props;
if(noticias.length === 0 && carregandoNoticias) {
return (
<View style={styles.containerCenter}>
<ActivityIndicator size="large" color={colors.verde}/>
</View>
);
}
if(erroNoticias) {
return (
<View style={styles.containerCenter}>
<Text style={styles.message}>{erroNoticias}</Text>
<TouchableOpacity hitSlop={hitSlop15}>
<Text>Recarregar</Text>
</TouchableOpacity>
</View>
)
}
return (
[<TextInput
style={styles.textInput}
placeholder='Pesquise'
key='pesquisa'
underlineColorAndroid='transparent'
/>,
<FlatList
data={noticias}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
style={styles.list}
key='lista'
onRefresh={this.onRefresh}
refreshing={carregandoNoticias}
onEndReached={this.carregarMaisNoticias}
onEndReachedThreshold={0.1}
/>]
)
}
render() {
return (
<SafeAreaView style={styles.safeArea}>
<View style={styles.container}>
{this.renderContent()}
</View>
</SafeAreaView>
);
}
}
function mapStateToProps(state) {
return {
noticias: state.intranet.noticias,
pagina: state.intranet.pagina,
erroNoticias: state.intranet.erroNoticias,
carregandoNoticias: state.intranet.carregandoNoticias
}
}
function mapDispatchToProps(dispatch) {
return {
carregarNoticias: (pagina, recarregar) => dispatch(ActionCreator.carregarNoticias(pagina, recarregar))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(NoticiasScreen);
No idea what's going on. Any help is appreciated.
EDIT:
I fixed it somehow. I added the onMomentScrollBegin prop to prevent my flatList from rendering twice on Render, and that fixed this issue.
here is what I added:
constructor(props) {
super(props);
...
this.onRefresh = this.onRefresh.bind(this);
this.onMomentumScrollBegin = this.onMomentumScrollBegin.bind(this);
this.onEndReachedCalledDuringMomentum = true; //PUT THIS HERE
}
onRefresh() {
this.props.carregarNoticias(1, true);
}
carregarMaisNoticias() {
if(!this.onEndReachedCalledDuringMomentum){
const { carregarNoticias, pagina } = this.props;
carregarNoticias(pagina + 1);
this.onEndReachedCalledDuringMomentum = true;
}
}
onMomentumScrollBegin() {
this.onEndReachedCalledDuringMomentum = false;
}
render() {
<OptimizedFlatList
data={noticias}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
style={styles.list}
key='lista'
onRefresh={this.onRefresh}
refreshing={carregandoNoticias}
onMomentumScrollBegin={this.onMomentumScrollBegin} //PUT THIS HERE
onEndReached={this.carregarMaisNoticias}
onEndReachedThreshold={0.1}
/>
}

How to avoid data duplicate when navigate screen?

My situation is i have two FlatList component (A and B).
A is my first screen. I use react-navigation to navigate A to B , B will show back arrow on headerLeft. When i click the arrow it will back to A . But the FlatList data is still show B even it is really in A...
My data is from fetch API by react-redux, i think the problem is come from react-redux. Because i test a simple test without react-redux. The problem is gone.
I want to use react-redux create my project. I try to use shouldComponentUpdate like
shouldComponentUpdate = (nextProps, nextState) => {
if (nextProps.movieList === this.props.movieList) {
return false;
}
return true;
};
It is still can't fix my problem when goBack() to another component
I console.log it try to find what is going on with my props data.
When i navigate to B from A. My console.log will show like this, i find A component will be rendered...
Then i click the back arrow on headerLeft to A. The screen is A but the data is still B add my console.log is empty at the same time.
I can't figure it out. Any help would be appreciated. Thanks in advance.
Here is my A component file (B is similar with A):
import React, { Component } from 'react';
import {
View, FlatList, Dimensions,
TouchableOpacity, Image,
ActivityIndicator, Alert, Platform
} from 'react-native';
import { Icon } from 'react-native-elements';
import { connect } from 'react-redux';
import { fetchMainMovieList } from '../actions';
const { width, height } = Dimensions.get('window');
const equalWidth = (width / 2);
class MainActivity extends Component {
static navigationOptions = ({ navigation }) => ({
title: 'MainActivity',
headerLeft:
<TouchableOpacity style={{ marginLeft: 10 }} onPress={() => navigation.navigate('DrawerOpen')} >
<Icon name='menu' />
</TouchableOpacity>
});
componentWillMount() {
this.props.fetchMainMovieList();
}
renderItem({ item }) {
return (
<View>
<Image
source={{ uri: item.photoHref }}
style={{ height: 220, width: equalWidth }}
resizeMode="cover"
/>
</View>
);
}
render() {
const movieData = this.props.movieList.movie;
console.log('A component this.props=>');
console.log(this.props);
if (movieData === []) {
return (
<View style={styles.loadingStyle}>
<ActivityIndicator />
</View>
);
}
return (
<View style={{ flex: 1 }}>
<FlatList
data={movieData}
renderItem={this.renderItem}
numColumns={2}
horizontal={false}
keyExtractor={(item, index) => index}
/>
</View>
);
}
}
const styles = {
loadingStyle: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
}
};
const mapStateToProps = (state) => {
const movieList = state.movieList;
return { movieList };
};
export default connect(mapStateToProps, { fetchMainMovieList })(MainActivity);
Here is my B component file:
import React, { Component } from 'react';
import {
View, FlatList, Dimensions,
Image, ActivityIndicator, Text
} from 'react-native';
import { connect } from 'react-redux';
import { fetchThisWeek } from '../actions';
const { width, height } = Dimensions.get('window');
const equalWidth = (width / 2);
class ThisWeek extends Component {
static navigationOptions = ({ navigation }) => ({
title: 'ThisWeek',
});
componentWillMount() {
this.props.fetchThisWeek();
}
renderItem({ item }) {
return (
<View>
<Image
source={{ uri: item.photoHref }}
style={{ height: 500, width: '100%' }}
resizeMode="cover"
/>
</View>
);
}
render() {
const movieData = this.props.movieList.movie;
console.log('B component this.props=>');
console.log(this.props);
if (movieData === []) {
return (
<View style={styles.loadingStyle}>
<ActivityIndicator />
</View>
);
}
return (
<View style={{ flex: 1 }}>
<FlatList
data={movieData}
renderItem={this.renderItem}
numColumns={1}
horizontal={false}
keyExtractor={(item, index) => index}
/>
</View>
);
}
}
const styles = {
loadingStyle: {
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
}
};
const mapStateToProps = (state) => {
const movieList = state.movieList;
return { movieList };
};
export default connect(mapStateToProps, { fetchThisWeek })(ThisWeek);
Here is my MyListReducer.js:
import {
MOVIELIST_MAINACTIVITY,
MOVIELIST_THISWEEK,
MOVIELIST_THEATER
} from '../actions/types';
const INITIAL_STATE = {};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case MOVIELIST_MAINACTIVITY:
return action.payload;
case MOVIELIST_THISWEEK:
return action.payload;
case MOVIELIST_THEATER:
console.log(action.payload);
return action.payload;
default:
return state;
}
};
In your reducer you have added the fetched data into the main object in store, instead, you should have to maintain two different variables to save data of those different components separately. Try by changing the reducer as,
import {
MOVIELIST_MAINACTIVITY,
MOVIELIST_THISWEEK,
MOVIELIST_THEATER
} from '../actions/types';
const INITIAL_STATE = {
weeklyMovies:[],
allMovies:[]
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case MOVIELIST_MAINACTIVITY:
return {
...state,
allMovies:action.payload
};
case MOVIELIST_THISWEEK:
return {
...state,
weeklyMovies:action.payload
};
case MOVIELIST_THEATER:
console.log(action.payload);
return action.payload;
default:
return {...state};
}
};
And in your component A and B you should change your mapStateToProps to read data from corresponding objects in store.
For MainActivity component
const mapStateToProps = (state) => {
const movieList = state.allMovies;
return { movieList };
};
and for ThisWeek component
const mapStateToProps = (state) => {
const movieList = state.weeklyMovies;
return { movieList };
};

State does not change until the second button press? Adding a call back throws an error "invariant violation: invalid argument passed"

I'm trying to render a Flatlist that contains a store name. Upon clicking the store name, more information about the store should be displayed.
I attempted to change state and then use
{this.renderMoreDetails(item) && this.state.moreDetailsShown}
to get the additional information to appear. However, it was clear via console.log that state was only changed after a second button press.
From what I read in this article 1 and this article 2 it seemed as though I needed a callback function as an additional parameter to my setState().
I tried adding a callback function(probably incorrect, but I'm extremely lost at this point) and only got more errors.
I know that I have access to all of the data from renderItem inside FlatList. How do I make it appear upon click?
import React, { Component } from 'react';
import {
View,
FlatList,
Text,
TouchableWithoutFeedback,
ListView,
ScrollView
} from 'react-native'
import { NavigationActions } from 'react-navigation';
import { Header, Card, CardSection } from '../common';
import Config from '../Config';
export default class Costco extends Component<Props> {
constructor(props) {
super(props);
this.state = {
stores: [],
selectedItem: {id: null},
};
}
componentWillMount() {
const obj = Config.costcoThree;
const costcoArr = Object.values(obj);
this.setState({
stores: costcoArr,
})
}
renderListOfStores() {
return <FlatList
data={this.state.stores}
renderItem={ ({item}) => this.singleStore(item)}
keyExtractor={(item) => item.id}
extraData={this.state.selectedItem} />
}
singleStore(item) {
console.log(this.state.selectedItem.id)
return item.id === this.state.selectedItem.id ?
<TouchableWithoutFeedback
onPress={() => this.selectStore(item)}
>
<View>
<Text>{item.branchName}</Text>
<Text>Opening Time {item.openingTime}</Text>
<Text>Closing Time {item.closingTime}</Text>
<Text>Address {item.dongAddKor}</Text>
</View>
</TouchableWithoutFeedback>
:
<TouchableWithoutFeedback>
<View>
<Text>{item.branchName}</Text>
<Text>Only showing second part</Text>
</View>
</TouchableWithoutFeedback>
}
selectStore(item) {
this.setState({selectedItem: item});
console.log('repssed');
}
render() {
return(
<ScrollView>
<Header headerText="Costco"/>
<Text>hello</Text>
{this.renderListOfStores()}
</ScrollView>
)
}
}
as a workaround you can have a state that maintain your selected item to expand(or something like another view) and then you can use some conditions to work for render your flat list data.
Consider the below code and let me know if you need some more clarification.
import React, { Component } from 'react';
import {
Platform,
StyleSheet,
Text,FlatList,TouchableNativeFeedback,
View
} from 'react-native';
export default class App extends Component<Props> {
constructor(props) {
super(props);
this.state = {
response: [],
selectedItem: {id: null},
};
}
userListLayout() {
const {response} = this.state;
return <FlatList data={response} renderItem={ ({item}) => this.singleUserLayout(item)} keyExtractor={(item) => item.email} extraData={this.state.selectedItem} />
}
singleUserLayout(item) {
return item.id == this.state.selectedItem.id ?
<TouchableNativeFeedback onPress={() => this.userSelected(item)}>
<View style={{flex:1, borderColor: 'black', borderWidth: 1}}>
<Text style={{padding: 10, fontSize: 25}}>{item.email}</Text>
<Text style={{padding: 10,fontSize: 20}}>{item.name}</Text>
</View>
</TouchableNativeFeedback>
: <TouchableNativeFeedback onPress={() => this.userSelected(item)}><Text style={{padding: 10,fontSize: 20}}>{item.email}</Text></TouchableNativeFeedback>
}
userSelected(item) {
console.log(item)
this.setState({selectedItem: item})
}
componentDidMount() {
fetch("https://jsonplaceholder.typicode.com/users")
.then(data => data.json())
.then(response => this.setState({response}))
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>HI THERE</Text>
{this.userListLayout()}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});

Implement Bidirectional Infinite ScrellView in react-native

I want to implement infinite scrollview in both the direction. Also the data should be loaded dynamically.
I am using SectionList component for list. I have implemented forward infinite scrolling. That means if user scroll down, the data will append to list automatically.
For that I have used onMomentumScrollEnd event. When user stops the scrolling, if the scroll is in Up direction, data will be appended at the End and if the scroll is in Down direction, data will be appended at the Top.
Now the problem is when I append the data at Top of list, It shift all the current list data to backward. I don't want to shift the current list even if the data is updated. Is there any way to do it.
This is my code:
import React, {Component} from 'react';
import {
Text,
View,
StyleSheet,
SectionList,
} from 'react-native';
import CardComponent from './CardComponent'
export default class Schedule extends Component {
constructor(props) {
super(props);
this.state = {
sectionData: [],
loading: false,
}
this.contentOffsetY = 0;
this._onScroll = this._onScroll.bind(this)
}
componentDidMount() {
this.setState({ sectionData: this.props.data })
}
renderItem = ({item}) => (
<CardComponent
data={item}
key={item}
/>
);
renderDateSeparator(text) {
return (
<Text style={{
paddingVertical: 15,
fontSize: 14,
flex: 1,
textAlign: 'center',
textAlignVertical: 'center',
}}>
{text}
<Text>
)
}
_onScroll(e){
let contentOffset = e.nativeEvent.contentOffset.y;
this.contentOffsetY < contentOffset ? this.loadMoreOnBottom() : this.loadMoreOnTop();
this.contentOffsetY = contentOffset;
}
loadMoreOnTop() {
this.setState({ lodaing: true });
// code to append data on top of list
this.setState({ lodaing: false });
}
loadMoreOnBottom() {
// code to append data at bottom of list
}
render() {
const sectionData = this.state.sectionData;
return(
<View style={{flex: 1}}>
<SectionList
onMomentumScrollEnd={this._onScroll}
automaticallyAdjustContentInsets={false}
itemShouldUpdate={false}
renderItem={this.renderItem}
renderSectionHeader={({section}) => this.renderDateSeparator(section.date)}
sections={sectionData}
stickySectionHeadersEnabled={false}
refreshing={this.state.loading}
onRefresh={() => this.loadMoreOnTop()}
onEndReachedThreshold={0.3}
onEndReached={() => this.loadMoreOnBottom()}
keyExtractor={(item) => item.key}
/>
</View>
)
}
}
Thanks in advance.
After so much of research, I have finally implemented the bidirectional infinite scroll view in react-native.
For the implementation, I have replaced my SectionList with FlatList, Because I want to use scrollToOffset method which is not properly working in SectionList.
I have used setInterval function of javaScript. It regularly checks weather the list need to be append from top or bottom.
This is my code:
import React, {Component} from 'react';
import {
Text,
View,
StyleSheet,
FlatList,
Dimensions,
} from 'react-native';
import CardComponent from './CardComponent'
let {height, width} = Dimensions.get('window');
export default class Schedule extends Component {
constructor(props) {
super(props);
this.state = {
listData: [],
}
this.contentOffsetY = 0;
this.pageOffsetY = 0;
this.contentHeight = 0;
this._onScroll = this._onScroll.bind(this);
this.loadMoreOnTop = this.loadMoreOnTop.bind(this);
this.loadMoreOnBottom = this.loadMoreOnBottom.bind(this);
}
componentDidMount() {
this.setState({ listData: this.props.data });
this._interval = setInterval(() => {
this.setState({ load: true });
}, 2000);
}
componentWillUnmount() {
clearInterval(this._interval);
}
renderItem = ({item}) => (
<CardComponent
data={item}
key={item}
/>
);
_onScroll(e){
let contentOffset = e.nativeEvent.contentOffset.y;
this.contentOffsetY < contentOffset ? this.loadMoreOnBottom() : this.loadMoreOnTop();
this.contentOffsetY = contentOffset;
}
scrollToOffset = (offset) => {
this.flatListRef ? this.flatListRef.scrollToOffset({animated: false, offset}) : null;
};
loadMoreOnTop() {
let newOffset;
// code to append data on top of list
// calculate newOffset:
newOffset = this.pageOffsetY + space required for new data.
this.contentOffsetY = newOffset;
this.scrollToOffset(newOffset);
}
loadMoreOnBottom() {
// code to append data at bottom of list
}
render() {
const listData = this.state.listData;
if(this.pageOffsetY < 600) {
this.loadMoreOnTop();
} else if((this.contentHeight - this.pageOffsetY) < (height * 1.5)){
this.loadMoreOnBottom();
}
return(
<View style={{flex: 1}}>
<FlatList
onScroll={(e) => {
this.pageOffsetY = e.nativeEvent.contentOffset.y;
this.contentHeight = e.nativeEvent.contentSize.height;
return null;
}}
onMomentumScrollEnd={this._onScroll}
automaticallyAdjustContentInsets={false}
itemShouldUpdate={false}
renderItem={this.renderItem}
data={listData}
refreshing={false}
onRefresh={() => this.loadMoreOnTop()}
onEndReachedThreshold={0.3}
onEndReached={() => this.loadMoreOnBottom()}
keyExtractor={(item) => item.key}
ref={(ref) => { this.flatListRef = ref; }}
animated={false}
/>
</View>
)
}
}