Force Rerender on FlatList when Screen Dimensions Change - react-native

I want to make a responsive App in React Native. I subscribe to Dimension changes in the container using this:
const RCTDeviceEventEmitter = require("RCTDeviceEventEmitter");
export interface Props {
data: Array<any>;
}
export interface State {}
class MyContainer extends React.Component<Props, State> {
_updateIfSelected() {
if (...some more logic...) {
this.forceUpdate();
}
}
constructor(props) {
super(props);
this.state = {
listener: null,
updateIfSelected: this._updateIfSelected.bind(this),
};
}
componentWillMount() {
this.setState({
listener: RCTDeviceEventEmitter.addListener("didUpdateDimensions", this.state.updateIfSelected),
});
}
componentWillUnmount() {
this.state.listener.remove("didUpdateDimensions",this.state.updateIfSelected);
}
renderItem() {
console.log("Rerendering Item")
return <Text style={{ width: Dimensions.get("window").width }} >Some Text</Text>
}
render() {
return (
<FlatList
data={this.props.data}
keyExtractor={(_, i) => (i.toString())}
renderItem={({item}) => this.renderItem(item)}
/>
}
}
I was wondering how to force a FlatList to rerender its items, because the appearance needs to change when the screen is tilted. However, because the data doesn't change, the list won't be rerendered on screen tilts.
The Documentation provides a parameter called extraData:
By passing extraData={this.state} to FlatList we make
sure FlatList itself will re-render when the state.selected
changes.
Without setting this prop, FlatList would not know it
needs to re-render any items because it is also a
PureComponent and the prop comparison will not show any changes
But I don't really understand what this is trying to say. Any Ideas how I can make the FlatList Rerender on Dimension changes?

You can pass a function to onLayout and check the dimensions when onLayout is called. I would recommend passing the function to a parent view. You could then setState in the function
class MyComponent extends Component {
_onLayout() { // I haven't tried this but I think this or something similar will work
const { width, height } = Dimensions.get('window');
if(width > height) {
this.setState(state => ({ ...state, orientation: 'landscape' }));
} else {
this.setState(state => ({ ...state, orientation: 'portrait' }));
}
}
render() {
return (
<View onLayout={this._onLayout}>
<FlatList
data={this.props.data}
keyExtractor={(_, i) => (i.toString())}
renderItem={({item}) => this.renderItem(item)}
extraData={this.state.orientation}
/>
</View>
)
}
}

Related

Back button to correct position of flatlist

I have a flatlist with many product . E.g. I click lot 20, it will proceed to screen with product detail. In product detail screen have a search function to other product. If search for lot 1 it will show product detail for lot 1. But when click back button it show the flatlist screen at the position of product 20, I want it to show the flatlist screen at the position of product 1. I am using react native class component. Can someone help?
flatlist.js
class FlatlistScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount(){
}
render(){
return(
<SafeAreaView style={{flex: 1}}>
<AnimatedFlatList
style={{flex: 1}}
ref={(ref) => this.flatListRef = ref}
data={this.props.APIE}
renderItem={this.renderItem}
contentContainerStyle={{paddingTop: Platform.OS !== 'ios' ? HEADER_MAX_HEIGHT : 0,}}
keyExtractor={item => item._id}
refreshing={this.state.refreshing}
removeClippedSubviews={Platform.OS == "android" ? this.state.sticky.length > 0 : true}
scrollEventThrottle={1}
refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={
this.onRefresh.bind(this)
}
progressViewOffset={HEADER_MAX_HEIGHT}
/>
}
contentInset={{
top: HEADER_MAX_HEIGHT,
}}
contentOffset={{
y: -HEADER_MAX_HEIGHT,
}}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }],
{ useNativeDriver: true },
)}
/>
</SafeAreaView>
)
}
}
productDetail.js
class ProductDetailScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
componentDidMount(){
BackHandler.addEventListener('hardwareBackPress', this.handleBackButtonClick);
}
handleBackButtonClick() {
return true;
}
render(){
return(
<Text>Product Detail</Text>
)
}
}
On this.renderItem you probably use ProductDetailScreen to render this.props.APIE elements. Well, you could add a prop to ProductDetailScreen like:
<ProductDetailScreen onGoBack={this.scrollToTop} /*other props*/ />
this.scrollToTop looks like:
scrollToTop() {
this.flatListRef.scrollToOffset({ animated: true, offset: 0 });
}
So this is the function that scoll the Flatlist to top.
Ok, now on ProductDetailScreen component, when we go back (I mean, on handleBackButtonClick function) we can call props.onGoBack():
class ProductDetailScreen extends React.Component {
...
handleBackButtonClick() {
props.onGoBack();
return true;
}
...
}
Thats it. Now when you go back from product details, FlatList will scroll to top.

PanResponder & Animated Problem in React Native

im using PanResponder in React Native but i have a simple problem that i can't solve!!!
this is my code
class test extends React.Component {
constructor(props) {
super(props);
const position = new Animated.ValueXY();
const panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: (evt, gs) => {
console.log(gs.dx);
position.setValue({ x: gs.dx , y: gs.dy })
},
onPanResponderRelease: (evt, gs) => {
},
});
this.state = { panResponder, position }
}
getCardStyle() {
const { position } = this.state;
return {
...position.getLayout(),
};
}
render() {
return (
<View>
{
this.props.cart.map((item, index) => {
return (
<Animated.View style={[this.getCardStyle() ,styles.CardContainer]} key={item.id}
{...this.state.panResponder.panHandlers}
>
<View><Image style={styles.image} source={{ uri: item.src}} /></View>
<Text style={styles.title}>{item.location}</Text>
</Animated.View>
)
})
}
</View>
);
}
}
i want to move every item finger
Separately but when i touch every item in list whole items start moving.
can anyone help me please??
SOLVE IT
I made another component named ListItem and in that component render FlatList items and it worked!

How to mak FlatList automatic scroll?

Here is what i try i use setInterval function to set a variable content will be changed every second and i find onMomentumScrollEnd can get the position y when scroll the FlatList
And then i am stuck , i thougt event.nativeEvent.contentOffset.y = this.state.content; can let my FlatList automatic scroll. Obviously it is not.
Any one can give me some suggestion ? Thanks in advance.
My data is from an API
Here is my App.js:
import React from 'react';
import { View, Image, FlatList, Dimensions } from 'react-native';
const { width, height } = Dimensions.get('window');
const equalWidth = (width / 2 );
export default class App extends React.Component {
constructor(props) {
super(props);
this.renderRow = this.renderRow.bind(this);
this.state = { movies: [], content: 0 };
}
componentWillMount() {
fetch('https://obscure-reaches-65656.herokuapp.com/api?city=Taipei&theater=Centuryasia')
.then(response => response.json())
.then(responseData => {
console.log(responseData);
this.setState({ movies: responseData[0].movie });
})
.catch((error) => console.log(error));
this.timer = setInterval(() => {
this.setState({content: this.state.content+1 })
}, 1000);
}
// get the jsonData key is item and set the value name is movie
renderRow({ item: movie }) {
console.log('renderRow => ');
return (
<View>
<Image source={{ uri: movie.photoHref}} style={{ height: 220, width: equalWidth }} resizeMode="cover"/>
</View>
);
}
render() {
const movies = this.state.movies;
// it well be rendered every second from setInterval function setState
console.log('render');
return (
<View style={{ flex: 1 }}>
<FlatList
data={movies}
renderItem={this.renderRow}
horizontal={false}
keyExtractor={(item, index) => index}
numColumns={2}
onMomentumScrollEnd={(event) => {
console.log(event.nativeEvent.contentOffset.y);
event.nativeEvent.contentOffset.y = this.state.content;
}}
/>
</View>
);
}
}
You need to tell your FlatList that you want it to scroll to a new position using scrollToOffset().
Store a reference to your FlatList in your class by adding the prop
ref={flatList => { this.flatList = flatList }} to it.
Then, call this.flatList.scrollToOffset({ offset: yourNewOffset }) to scroll to the desired offset.
Docs on this method are here.

react-native componentWillUnmount not working while navigating

I am doing this simple steps but unmount was not calling I don't know why. Please I need a solution for this I need unmount to be get called while navigating to another screen...
class Homemain extends Component {
constructor(props) {
super(props);
}
componentWillMount(){
alert('willMount')
}
componentDidMount(){
alert('didMount')
}
componentWillUnmount(){
alert('unMount')
}
Details = () => {
this.props.navigation.navigate('routedetailsheader')
}
render() {
return(
<View style={styles.container}>
<TouchableOpacity onPress={() => this.Details()} style={{ flex: .45, justifyContent: 'center', alignItems: 'center', marginTop: '10%', marginRight: '10%' }}>
<Image
source={require('./../Asset/Images/child-notification.png')}
style={{ flex: 1, height: height / 100 * 20, width: width / 100 * 20, resizeMode: 'contain' }} />
<Text
style={{ flex: 0.5, justifyContent: 'center', fontSize: width / 100 * 4, fontStyle: 'italic', fontWeight: '400', color: '#000', paddingTop: 10 }}>Details</Text>
</TouchableOpacity>
</View>
);
}
}
export default (Homemain);
This is my RouteConfiguration in this way I am navigating to the next screen. Can someone please help me for this error so that i can proceed to the next steps
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { addNavigationHelpers, NavigationActions } from 'react-navigation';
import { connect } from 'react-redux';
import { BackHandler } from 'react-native';
import { Stack } from './navigationConfiguration';
const getCurrentScreen = (navigationState) => {
if (!navigationState) {
return null
}
const route = navigationState.routes[navigationState.index]
if (route.routes) {
return getCurrentScreen(route)
}
return route.routeName
}
class StackNavigation extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
navigation: PropTypes.shape().isRequired,
};
constructor(props) {
super(props);
BackHandler.addEventListener('hardwareBackPress', this.backAction);
}
//backAction = () => this.navigator.props.navigation.goBack();
backAction = () => {
const { dispatch, navigation } = this.props;
const currentScreen = getCurrentScreen(navigation)
if (currentScreen === 'Homemain') {
return false
}
else
if (currentScreen === 'Login') {
return false
}
dispatch(NavigationActions.back());
return true;
};
render() {
const { dispatch, navigation } = this.props;
return (
<Stack
ref={(ref) => { this.navigator = ref; }}
navigation={
addNavigationHelpers({
dispatch,
state: navigation,
})
}
/>
);
}
}
export default connect(state => ({ navigation: state.stack }))(StackNavigation);
Routing to a new screen does not unmount the current screen.
For you usecase you instead of writing the code in componentWillUnmount you can continue by writing it after calling navigate in Details itself.
If you are looking for a callback when you press back from the new screen to come back to the current screen. Use goBack as shown in https://github.com/react-navigation/react-navigation/issues/733
If you are using a stack navigator, then routing to a new view loads the new view above the old one. The old view is still there for when you navigate back.
As I understand from your question and code you are using redux with navigation and want to unmount a screen. So what I did I just added a screen component inside another component to make my screen component as child.
e.g. below is the snippet that I am using to unmount the PushScreen from PushedData component.
I render PushScreen and inside it there is component PushedData that originally making the view. On PushedData `componentWillMount I am just doing some conditional functionality and on success I am just unmounting PushData from PushScreen.
class PushScreen extends Component{
state ={ controllerLaunched: false };
updateControllerLauncher = () => {
this.setState({ controllerLaunched: true });
}
render (){
if(this.state.controllerLaunched){
return null;
} else {
return <PushedData handleControllerLauncher={this.updateControllerLauncher} />;
}
}
}
class PushedData extends Component{
componentWillMount(){
this.unmountPushData();//calling this method after some conditions.
}
unmountPushData = () => {
this.props.handleControllerLauncher();
}
render(){
return (
<View><Text>Component mounted</Text></View>
);
}
}
Let me know if you need more information.
When you use Stack Navigator then routing to a new view loads the new view above the old one as Rob Walker said. There is a workaround. You can bind blur event listener on componentDidMount using navigation prop:
componentDidMount() {
this.props.navigation.addListener('blur', () => {
alert('screen changed');
})
}
So when your screen goes out of focus the event listener is called.
You can find more about events right here.
If you want to go next Screen use (.replace instead .navigate) where you want to call componentWillUnmount. and if you want to go back to one of previous screens use .pop or .popToTop.
you can set condition for costume function that u write for hardware button , for example when ( for example for React Native Router Flux ) Actions.currentScene === 'Home' do something or other conditions u want .

React native infinite scroll with flatlist

I followed this tutorial https://www.youtube.com/watch?v=rY0braBBlgw
When I scroll down it sends the request then it gets stuck in a loop and just requests and requests. I think this is a problem with the scrollview in the listview.
I am not sure if you were able to resolve this but I was having the same problem and I am adding what worked well for me.
onEndReachedThreshold=>onEndThreshold
<FlatList
data={this.state.data}
renderItem={({ item }) => (
<ListItem
roundAvatar
title={
<Text style={{textAlign: 'left'}}> {item.name.first} {item.name.last}</Text>
}
subtitle={
<Text style={{textAlign: 'left'}}>{item.email}</Text>
}
avatar={{ uri: item.picture.thumbnail }}
containerStyle={{ borderBottomWidth: 0 }}
/>
)}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
keyExtractor={item => item.email}
refreshing={this.state.refreshing}
onRefresh={this.handleRefresh}
onEndReached={this.handleLoadMore}
onEndThreshold={0}
/>
I hope this helps someone.
This works for me:
<FlatList
data={this.state.storesList}
renderItem={({ item, index }) => renderItem(item, index)}
keyExtractor={(item, index) => item.id.toString()}
onEndReached={this.fetchMore}
onEndReachedThreshold={0.1}
ListFooterComponent={this.renderFooter}
refreshing={this.state.refreshing}
/>
renderFooter = () => {
if (this.state.refreshing) {
return <ActivityIndicator size="large" />;
} else {
return null;
}
};
fetchMore = () => {
if (this.state.refreshing){
return null;
}
this.setState(
(prevState) => {
return { refreshing: true, pageNum: prevState.pageNum + 1 };
},
() => {
this.sendAPIRequest(null , true);
}
);
};
The reason I used the following in the fetchMore function:
if (this.state.refreshing){
return null;
}
Is because when you setState to the pageNum it calls the render() function and then the fetchMore called again. This is written to prevent it.
In addition, I set:
refreshing: false
after the sendAPIRequest is done.
Pay attention about onEndReachedThreshold in FlatList:
How far from the end (in units of visible length of the list) the
bottom edge of the list must be from the end of the content to trigger
the onEndReached callback.
Meaning in my example (0.1) means: when you reach 10% of items from the bottom, the fetchMore callback is called. In my example, I have 10 items in the list, so when the last item is visible, fetchMore is called.
I'm not sure if this is exactly what you're looking for, but the code I've left below allows you to continue scrolling through a fixed set of data props. When you reach the last index, it basically wraps around to the beginning. I've achieved this by appending a copy of the first element of the supplied data to the end of the FlatList; when the user scrolls this into view, we can safely reset the scroll offset.
import React, { Component } from 'react';
import { FlatList } from 'react-native';
export default class InfiniteFlatList extends Component {
constructor(props) {
super(props);
this.state = {
};
this._flatList = null;
}
getWrappableData = (data) => {
return [...data, data[0]];
}
render = () => (
<FlatList
{ ...this.props }
ref={ (el) => this._flatList = el }
onLayout={ ({nativeEvent}) => {
const {width, height} = nativeEvent.layout;
this.setState({
width, height
});
} }
onScroll={ ({ nativeEvent }) => {
const { x } = nativeEvent.contentOffset;
if(x === (this.props.data.length * this.state.width)) {
this._flatList.scrollToOffset({x: 0, animated: false});
}
} }
data={ this.getWrappableData(this.props.data) }
pagingEnabled={true}
/>
)
};
InfiniteFlatList.defaultProps = { };
InfiniteFlatList.propTypes = { };
This assumes you want to scroll horizontally.
It probably isn't perfect; there is likely a better technique out there which uses FlatList's onEndReached callback, however this only seemed to fire once througohout. By polling the scroll offset of the FlatList, we can fire off our own equivalent as many times as needed. If you specify a getItemLayout prop, you'll be able to use scrollToIndex({index, animated?}) instead.
Aug. 5, 2019 update
On React native 0.60, one should use scrollToOffset as:
this._flatList.scrollToOffset({offset: 0, animated: false});