React Native "onViewableItemsChanged" not working while scrolling on dynamic data - reactjs

I have a React Native FlatList.
base on Documentation I used onViewableItemsChanged for getting the current showing item on the screen. but while scrolling or when scrolling stops nothing happens and I can't get the current Id.
How Can I use it in correct way?
export default class TimeLine extends Component {
constructor(props) {
super(props);
this.renderTimeline = this.renderTimeline.bind(this);
this.state = {
timeline: [],
};
this.handleViewableItemsChanged = this.handleViewableItemsChanged.bind(this);
this.viewabilityConfig = {
itemVisiblePercentThreshold: 50,
};
}
...
renderTimelineList(timeline_data) {
return (
<Card key={timeline_data.unique_id}>
<CardItem style={styles.header}>
<Left>
<Icon type="MaterialIcons" name={this.timelineIcon(timeline_data.type)}/>
<Body>
<Text style={styles.header_title}>{timeline_data.title}</Text>
</Body>
</Left>
</CardItem>
<CardItem>
{this.renderTimeline(timeline_data)}
</CardItem>
<CardItem>
<Left>
<Icon small name="time" style={styles.small_font}/>
<Text note style={styles.small_font}>{timeline_data.release_date}</Text>
</Left>
<Right>
<Text note style={styles.small_font}>{timeline_data.duration}</Text>
</Right>
</CardItem>
</Card>
);
}
render() {
return (
<Container>
<Content>
<FlatList
key={this.state.key}
onViewableItemsChanged={this.handleViewableItemsChanged}
viewabilityConfig={this.viewabilityConfig}
data={this.state.timeline}
renderItem={({item}) => this.renderTimelineList(item)}
/>
</Content>
</Container>
);
};
}

Make sure that you implement keyExtractor correctly (or implement it if you haven't already).
In my case keyExtractor was returning null for many children, and onViewableItemsChanged would not dispatch for those.

I solved the problem myself.
The problem was because of I was trying to populate the data with Ajax.
To solve that I ran the request from the Parent component and passed it in props
and inside the constructor of the class if filled the state for my array.
Another thing that you should consider is that you should render the whole FlatList inside a View.
here is my final code:
export default class TimeLine extends Component {
constructor(props) {
super(props);
this.viewabilityConfig = {
minimumViewTime: 500,
viewAreaCoveragePercentThreshold: 60,
};
}
render() {
const {timeline} = this.state;
return (
<View style={styles.container}>
<FlatList
data={timeline}
renderItem={({item}) => this.renderTimelineList(item)}
viewabilityConfig={this.viewabilityConfig}
onViewableItemsChanged={({viewableItems}) => this.handleViewableItemsChanged(viewableItems)}
/>
</View>
);
};
}

Related

InfiniteScroll using ScrollView - React Native

I have a list using the ScrollView of react native itself. Basically, I build a list dynamically through an API return.
async fetchData(userSearch) {
const {route} = this.props;
const {params} = route;
const {type} = params;
this.setState({
loading: true,
});
const responseProcedures = await scheduleResource.getProcedures(userSearch);
this.setState({
procedures: responseProcedures.data,
loading: false,
});
}
<ScrollView
onScroll={(event) => this.shouldLoadMoreContent(event)}
>
{procedures.map(procedure => (
<ArrowBox key={procedure.id} onPress={() => RootNavigation.navigate('ProcedureDetails', {procedure})}>
<Text bold style={styles.ProcedureTitle}>
{procedure.name}
</Text>
{!!procedure.synonyms.length && (
<>
<Text bold style={styles.ProcedureSynonymTitle}>
SinĂ´nimos
</Text>
<View style={styles.ProcedureSynonymOptionsContainer}>
{procedure.synonyms.map(synonym => <Text style={styles.ProcedureSynonymOption} key={synonym}>{synonym}</Text>)}
</View>
</>
)}
</ArrowBox>
))}
</ScrollView>
The problem is that I load the entire return from the API and it slows down.
I would like to know how to dynamically load the content and make new calls in the api, when I reach the end of the page.
Api allows me to place offset and limit.
Could someone give me some example?
Thanks!!!!!
Basically the ScrollView is not designed to handle dynamic data, the correct component that is designed to perform this function is called Flatlist. It works almost exactly like ScrollView but it is faster and will only render components that are shown on the screen.
Please import Flatlist from React Native like this...
//At the top of your file, please import FlatList together with all the modules that you want
import { FlatList, Text, View } from "react-native";
Then replace the entire ScrollView in your code with a Flatlist like this:
<FlatList
keyExtractor={(procedure) => procedure.id}
data={this.state.procedures}
renderItem={(procedure) => {
return (
<ArrowBox
key={procedure.id}
onPress={() =>
RootNavigation.navigate("ProcedureDetails", {
procedure })}>
<Text bold style={styles.ProcedureTitle}>
{procedure.name}
</Text>
{!!procedure.synonyms.length && (
<>
<Text bold style={styles.ProcedureSynonymTitle}>
SinĂ´nimos
</Text>
<View
style={styles.ProcedureSynonymOptionsContainer}>
{procedure.synonyms.map((synonym) => (
<Text
style={styles.ProcedureSynonymOption}
key={synonym}>
{synonym}
</Text>
))}
</View>
</>
)}
</ArrowBox>
);
}}
></FlatList>;

React native re-render causes view to scroll to top - Do my keys change between renders?

I am using react native, redux and flatlist. I render 10 items and then fetch / render the next 10 items on click (load more). My problem is that when I use setState(to increase the page counter) (thats why I dont use setstate in my code) to fetch more items (i use pagination) or ask whether or not Im fetching (true when fetching -> shown loading sign, false when fetched -> showing items), that triggers
a re-render and my view gets scrolled to top.
Ive been reading for a while now and event.preventDefault (as sometimes suggested) doesnt work and I do believe that my problem is the key / parent tree of the corresponding element changes between renders. I do give each item the key of its ID which is always unique. Am I doing it wrong? Or what else could be causing this? Thanks!
class App extends React.Component {
state = {
page : 0
}
componentDidMount() {
this.props.fetchData(0)
this.state.page++;
}
load = () => {
this.props.fetchData(this.state.page);
this.state.page++;
}
renderRow = ({item}) => {
return (
<Item
key={item.id}
coin={item}
/>
)
}
renderButton() {
return <Button
title="Load more"
onPress={this.load}
/>
}
render() {
if (this.props.isFetching) {
return (
<View>
<Text>Loading</Text>
</View>
)
}
return (
<View style={styles.container}>
<FlatList
style={{width: '100%'}}
data={this.props.data}
renderItem={this.renderRow.bind(this)}
ListFooterComponent={this.renderButton.bind(this)}
/>
</View>
);
}
}
mapStateToProps = state => {
return {
isFetching: state.data.isFetching,
data: state.data.data
}
}
export default connect(mapStateToProps, {fetchData})(App)
Hey change your render method return like below :
return (
{ this.state.isFetching && <View> <Text>Loading</Text> </View>}
<View style={styles.container}>
<FlatList
style={{width: '100%'}}
data={this.props.data}
renderItem={this.renderRow.bind(this)}
ListFooterComponent={this.renderButton.bind(this)}
/>
</View>
);

FlatList calling twice

I have this code
class Home extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: []
}
this._handleRenderItem = this._handleRenderItem.bind(this);
this._keyExtractor = this._keyExtractor.bind(this);
}
componentDidMount() {
let success = (response) => {
this.setState({ dataSource: response.data });
};
let error = (err) => {
console.log(err.response);
};
listarProdutos(success, error);
}
_keyExtractor = (item, index) => item._id;
_handleRenderItem = (produto) => {
return (
<ItemAtualizado item={produto.item} />
);
}
render() {
return (
<Container style={styles.container}>
<Content>
<Card>
<CardItem style={{ flexDirection: 'column' }}>
<Text style={{ color: '#323232' }}>Produtos atualizados recentemente</Text>
<View style={{ width: '100%' }}>
<FlatList
showsVerticalScrollIndicator={false}
data={this.state.dataSource}
keyExtractor={this._keyExtractor}
renderItem={this._handleRenderItem}
/>
</View>
</CardItem>
</Card>
</Content>
</Container>
);
}
}
export default Home;
The function _handleRenderItem() is being called twice and I can't find the reason. The first time the values inside my <ItemAtualizado /> are empty, but the second was an object.
This is normal RN behavior. At first, when the component is created you have an empty DataSource ([]) so the FlatList is rendered with that.
After that, componentDidMount triggers and loads the updated data, which updates the DataSource.
Then, you update the state with the setState which triggers a re render to update the FlatList.
All normal here. If you want to try, load the datasource in the constructor and remove the loading in the componentDidMount. It should only trigger once.
If you want to control render actions, you can use shouldComponentUpdate method.
For example:
shouldComponentUpdate(nextProps, nextState){
if(this.state.friends.length === nextState.friends.lenght)
return false;
}
it will break render if friends count not change.
I recreated the issue in this snack. https://snack.expo.io/B1KoX-EUN - I confirmed you can use shouldComponentUpdate(nextProps, nextState) to diff this.state or this.props and return true/false - https://reactjs.org/docs/react-component.html#shouldcomponentupdate docs say this callback should be used only for optimization which is what we're doing here.

React-Native "expected a component class, got object Object"

I have a big code that does not run because of the error in the headline.
This is one file of my code, can anyone see what is wrong? I believe it is the line "{this.state.todos.map((todo,index) =>" (code line 62)
I have capitalised the name of the objects, so that is not the issue (I think).
npm -v 4.6.1
import React, { Component } from 'react';
//import $ from 'jquery';
import { Button, View, FormGroup, FormControl, ControlLabel } from 'react-native';
import { Text } from 'react-native-svg'
/* generating sample data to be shown, these data names are used to access the values*/
var todos = [];
//Will not work first time, since list do not exist in AsyncStorage.
//Get from AsyncStorage.
try{
todos = JSON.parse(AsyncStorage["todos"]);
}catch(ex){
console.log("Not working" + ex);
todos = [];
}
//Errormessage for errorhandeling.
var errorMessage = "";
/*--------------------*/
class Todos extends Component {
constructor(props) {
super(props);
this.state = {
todos
};
this.handleAddTodo = this.handleAddTodo.bind(this);
}
handleAddTodo(todo) {
/* creates todo in list that shows*/
this.setState({todos: [...this.state.todos, todo]});
/*this code saves in AsyncStorage*/
todos.push(todo);
//AsyncStorage...
AsyncStorage.setItem("todos", JSON.stringify(todos));
}
/* function that removes todos from the list*/
handleRemoveTodo(index) {
this.setState({
todos: this.state.todos.filter(function(e, i) {
return i !== index;
})
})
/* now working as expected*/
todos.splice(index, 1);
AsyncStorage.setItem("todos", JSON.stringify(todos));
}
render() {
return (
<View>
<TodosInput onAddTodo={this.handleAddTodo} />
<hr />
<Text>todo count: <span>{this.state.todos.length}</span></Text>
<View>
<View>{this.state.todos.map((todo,index) =>
<View key={index}>
<Text style={style.list-group-item-heading}>{todo.todoTitle}<small><span style={style.label} label-info></span></small> </Text>
<View>{todo.todoDesc}</View>
<Button bsStyle="danger" onClick={this.handleRemoveTodo.bind(this, index)}><span style={style.glyphicon} glyphicon-trash></span></Button>
</View>
)}</View>
</View>
</View>
);
}
It is more likely because of using html tags instead of native components.
Like hr span and small.
So you need to create your own components and also titlecase their names.
Here is possible example how to fix your problem:
const Small = ({ children }) => (
<Text
style={{
fontSize: 10
}}
>
{children}
</Text>
);
const HR = () => (
<View
style={{
height: 1,
borderBottomColor: "black",
borderBottomWidth: 1
}}
/>
);
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<View>
<Text>This is span</Text>
</View>
<View>
<Text>
Regular test <Small>With {"<small>"} text</Small> Inside
</Text>
</View>
<View>
<Text>This is {"<HR />"} bellow</Text>
<HR />
</View>
</View>
);
}
}
UPD: Actually there are a lot of other bugs in a code above
Quick list of possible issues:
as pointed dalready - replace all html tags with native components
style={style.list-group-item-heading} You cannot use dash separator in js except ni a strings - possible fix: style={style['list-group-item-heading']}
<View>{todo.todoDesc}</View> - you have to wrao text with Text compnent
<Button /> component have required prop title it should be a string, as well as it dosn't have OnClick handler but requires OnPress
To summarize - i would note that react native uses jsx but it is not html. It have its own API and you'll have to grasp it carefully.

props data is not working in componentDidMount using react-native

I have been struggling for the last 24 hrs about this one.
The problem is that, I am passing props in my parent class and calling the props data in my child component. When I check in render method the data shows correctly, but when calling it in componentDidMount it shows null.
Here is the sample of my code
class parent extends React.Component{
constructor(props){
super(props);
this.state = {
isOpen: false,
day:'monday',
data:[],
data1:[]
}
}
back () {
this.props.navigator.pop();
}
Mon(){
var record=[];
this.state.data.map((data) => {
if(data.frequency[0].day == 'Monday'){
record.push(data);
}
});
this.setState({data1:record});
}
componentWillMount() {
this.fetchData();
}
fetchData() {
//here i am getting the data from service and set the data to the state
}
render(){
return(
<View style={styles.Maincontainer}>
<View style={styles.navbar}>
<View style={styles.navbarContainer}>
<View style={styles.navbarIcon}>
<TouchableOpacity style={styles.button} onPress={() => this.back()}>
<Image
style={styles.menu2}
source={require('image!nav_arrow')} />
<Text style={styles.navbuttonText}>Back</Text>
</TouchableOpacity>
</View>
<View style={styles.navbarText}>
<Text style={styles.title}>Title</Text>
</View>
<View style={styles.navbarButton}>
</View>
</View>
</View>
<View style={styles.subMainContainer}>
<View style={styles.weekHeader}>
<View style={styles.weekRow}>
<Text style={styles.text} onPress={this.Mon.bind(this)}>Mon</Text>
</View>
</View>
<View style={styles.listBody}>
{this.state.data1.length == 0 ?(
<List data={this.state.data} />
) : (
<List data={this.state.data1}/>
)}
</View>
</View>
</View>
);
}
}
In my child component, I want to display the data in a ListView which is taken from the parent component
class List extends React.Component{
constructor(props){
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
}),
isLoading: true
};
}
componentDidMount(){ //**here props data shows empty**
alert("didMount:"+JSON.stringify(this.props.data));
var data=this.props.data
this.setState({
dataSource: this.state.dataSource.cloneWithRows(data),
isLoading: false
});
}
render() { //**In this alert it shows correct data**
alert("data:"+JSON.stringify(this.props.data));
if (this.state.isLoading) {
return this.renderLoadingView();
}
return(
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderSchedule.bind(this)}
automaticallyAdjustContentInsets={false}/>
);
}
renderLoadingView(){
return(
<View style={styles.loading}>
<ActivityIndicatorIOS
size='large'/>
<Text>
Loading Schedule...
</Text>
</View>
);
}
renderSchedule(data){
return(
<View style={styles.ListContainer}>
<View style={styles.subContainer}>
<Text style={styles.classText}>Class Name: {data.name}</Text>
{data.frequency[0].day == null ? (
<Text style={styles.Day}>Day: {data.frequency[0].date}</Text>
):
<Text style={styles.Day}>Day: {data.frequency[0].day}</Text>
}
</View>
<View style={styles.subContainer1}>
<Text style={styles.startTime}>Start: {data.frequency[0].from}</Text>
<Text style={styles.endTime}>End: {data.frequency[0].to}</Text>
</View>
</View>
)
}
}
Here what i want is that initially i need to display the total data,when click on the text parent class the data need to be changed.I want to show the data in ListView.I already explain above about my problem,So can any one give me suggestions that how to solve my problem, Any help much appreciated
My guess would be that the alert in componentDidMount() is empty because it only called once, at a time when data is still empty, while render() is called on each change of state or props.
I would expect the flow of alerts to look something like this:
Alert called from child's render: "data: (empty)"
Alert called from child's didMount: "didMount:(empty)"
User clicks something in parent, state change, re-render triggered
Alert called from child's render: "data: FOO-content"
React does NOT call componentDidMount() on the second pass. If you want your datasource to be populated on EACH update of the child, you will need to add a componentDidUpdate() method to your child, with copy of the code you have inside componentDidMount().
As a general note: I would advise you to read React's explanation of component lifecycle and methods. I have a feeling that the preparation you are doing inside componentDidMount() is probably better placed elsewhere:
creating a DataSource and initial fill with data from props: in getInitialState()
updating Datasource with data from new props: in componentWillReceiveProps()
I found in React generally that if I want to prepare the initial state of my component based on this.props then I should do it in componentDidUpdate instead of componentDidMount. Components aren't mounted with populated props from the parent component, only updated with them.
I noticed this when doing normal React and doing console.log(this.props) in the render method of my child component. It would log initally as empty, then log again populated with the props. If I called a this.setState in componentDidMount then the population of this.props occurs in the third render. This is because calling this.setState in componentDidMount triggered a pre-update render.
I hope this makes sense and applies to your situation - I arrived at your question looking for a solution to similar behaviour.

Resources