React Native BigList stopping and not calling 'onEndReached' - react-native

I'm very new to React Native and I'm having an issue using the BigList.
After four or five scrolls to the bottom of the list it just stops retrieving any more data, even though there is plenty more data in the DB. I can still scroll the list up and down but it's like the 'onEndReached' is no longer being fired for some reason.
I've done a Google search but nobody else seems to be having this problem so I assume the issue is with my code.
Here's my code:
import React, { useEffect, useState } from 'react';
import { View, Text, Image, Dimensions } from 'react-native';
import BigList from 'react-native-big-list';
const HomeScreen = ({ navigation }) => {
const BaseURL = "http://localhost:12646/"
const windowWidth = Dimensions.get('window').width;
const [homeImages, setHomeImages] = useState([]);
const [pageNumber, setOffset] = useState(1);
const [loading, setLoading] = useState(false);
useEffect(() => {
getData();
}, []);
const getData = () => {
if (!loading) {
setLoading(true);
var url = BaseURL + 'api/images/GetHomepageImages?PageNumber=' + pageNumber + '&PageSize=3';
fetch(url)
.then((response) => response.json())
.then((responseJson) => {
setOffset(pageNumber + 1);
setHomeImages([...homeImages, ...responseJson]);
setLoading(false);
})
.catch((error) => {
console.error(error);
});
}
}
const renderItem = ({ item, index }) => {
return (
<View style={{ height: windowWidth }}>
<Image
style={{ width: windowWidth, height: windowWidth, }}
source={{ uri: BaseURL + "images/" + item.basePath + "/" + item.imageSquare }}
/>
</View>
);
}
return (
<View style={{flex: 1}}>
{
(homeImages != null) ?
<BigList
data={homeImages}
onEndReached={getData}
itemHeight={windowWidth}
renderItem={renderItem}
/>
: <Text>Loading</Text>
}
</View>
)
};
export default HomeScreen;

If anyone else is stuck on this I think I've fond the solution. Adding the following line as one of the options in the BigList appears to have fixed it: -
onEndReachedThreshold={1}
Since adding this it thankfully hasn't happened again

Related

Need some help handling the response I'm getting from json server

I'm developing an app in React Native in which I'm trying to display some data provided by a fake API I set up using json server. I'm using the useContext hook to handle the general state of the app and since I'm fairly new to React Native and React in general I need some help handling the response I'm manipulating through the context API.
This is the State file I set up in the context folder
import React, { useReducer } from 'react'
import MenusReducer from './MenusReducer'
import MenusContext from './MenusContext'
import { baseUrl } from '../../shared/baseURL'
const MenusState = (props) => {
const initialState = {
menus: [],
selectedMenu: null
}
const [state, dispatch] = useReducer(MenusReducer, initialState)
const getMenus = async () => {
const response = await fetch(baseUrl + 'RESTAURANTES')
const data = await response.json()
console.log('This is the reducer working'); // This is a test log to see if it works
dispatch({
type: 'GET_MENUS',
payload: data
})
}
const getDetails = async (id) => {
const response = await fetch(`${baseUrl}RESTAURANTES/${id}`)
const data = await response.json()
dispatch({
type: 'GET_DETAILS',
payload: data
})
}
return (
<MenusContext.Provider value={{
menus: state.menus,
selectedMenu: state.selectedMenu,
getMenus,
getDetails
}}>
{props.children}
</MenusContext.Provider>
)
}
export default MenusState;
So here I set up a getMenus() function by which I get all the items I'd like to display in my components. As you can see, I put a test log inside the function to see if it works, which it does.
The problem comes when I try to get those items inside my app components. Here's one of the instances in which I try to get the items to display.
const Home = ({ navigation }) => {
const { menus, getMenus } = useContext(MenusContext)
const [search, setSearch] = useState('')
const [response, setResponse] = useState([])
const [categories, setCategories] = useState(allCategories)
const [loading, setLoading] = useState(true)
useEffect(() => {
const data = async () => await getMenus();
console.log('This is the app executing');
setLoading(false);
setResponse(data)
console.log(response);
}, [])
// ... some code later
return (
<ScrollView style={styles.yScroll}>
<View>
<Text style={styles.sectionTitle}>Destacados</Text>
</View>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
<View style={styles.sectionContainer}>
<Text>{response[0]}</Text> // Here's where I'm trying to print something about the response but it's not working
</View>
</ScrollView>
<View>
<Text style={styles.sectionTitle}>Categorias</Text>
</View>
<View style={styles.sectionContainer}>
{categories.map((item, index) => {
return (
<View key={index} style={styles.category}>
<Text>{item}</Text>
</View>
)
})}
</View>
</ScrollView>
)
}
So inside one of the ScrollViews I'm setting up a test to see if the response can be displayed, which it is not. However, inside the useEffect, I'm setting up a test log with the message 'This is the app executing' which is working, BUT, the response being logged is an empty array.
I'm sure the problem I'm facing has something to do with the asynchronous response between app and server, but I have no clear idea as to how I can address this.
Can someone please point me in the right direction? Thanks in advance!!
Based on your code, I think you can do this
const Home = ({ navigation }) => {
const { menus, getMenus } = useContext(MenusContext)
const [search, setSearch] = useState('')
const [categories, setCategories] = useState(allCategories)
const [loading, setLoading] = useState(true)
useEffect(() => {
const data = async () => await getMenus();
console.log('This is the app executing');
data();
setLoading(false);
}, [])
// ... some code later
return (
<ScrollView style={styles.yScroll}>
<View>
<Text style={styles.sectionTitle}>Destacados</Text>
</View>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
<View style={styles.sectionContainer}>
<Text>{menus[0]}</Text> // Here's where I'm trying to print something about the response but it's not working
</View>
</ScrollView>
<View>
<Text style={styles.sectionTitle}>Categorias</Text>
</View>
<View style={styles.sectionContainer}>
{categories.map((item, index) => {
return (
<View key={index} style={styles.category}>
<Text>{item}</Text>
</View>
)
})}
</View>
</ScrollView>
)
}

How to convert Fetch to Axios and Class component to Functional component?

How to convert Fetch to Axios and Class component to Functional component?
I want to learn to implement Infinite-Scrolling using functional component and axios in React native, but it is difficult to apply because the reference document is composed of class component and fetch.
import React from 'react';
import {
View,
Image,
Text,
FlatList, // here
} from 'react-native';
export default class App extends React.Component {
state = {
data: [],
page: 1 // here
}
_renderItem = ({item}) => (
<View style={{borderBottomWidth:1, marginTop: 20}}>
<Image source={{ uri: item.url }} style={{ height: 200}} />
<Text>{item.title}</Text>
<Text>{item.id}</Text>
</View>
);
_getData = () => {
const url = 'https://jsonplaceholder.typicode.com/photos?_limit=10&_page=' + this.state.page;
fetch(url)
.then(r => r.json())
.then(data => {
this.setState({
data: this.state.data.concat(data),
page: this.state.page + 1
})
});
}
componentDidMount() {
this._getData();
}
// here
_handleLoadMore = () => {
this._getData();
}
render() {
return (
<FlatList
data={this.state.data}
renderItem={this._renderItem}
keyExtractor={(item, index) => item.id}
onEndReached={this._handleLoadMore}
onEndReachedThreshold={1}
/>
);
}
}
When converting from a class to a function component, there are a few steps which are relevant here:
replace lifecycle events like componentDidMount with useEffect.
replace component state with one or many useState hooks.
convert class methods to plain functions.
remove all references to this.
delete render() and just return the JSX directly.
The methods _renderItem, _getData, and _handleLoadMore are basically unchanged. They just become const variables instead of class properties.
Here's the straight conversion from class to function component:
import React, { useEffect, useState } from 'react';
import {
View,
Image,
Text,
FlatList,
} from 'react-native';
export default function App() {
const [page, setPage] = useState(1);
const [data, setData] = useState([]);
const _renderItem = ({ item }) => (
<View style={{ borderBottomWidth: 1, marginTop: 20 }}>
<Image source={{ uri: item.url }} style={{ height: 200 }} />
<Text>{item.title}</Text>
<Text>{item.id}</Text>
</View>
);
const _getData = () => {
const url =
'https://jsonplaceholder.typicode.com/photos?_limit=10&_page=' + page;
fetch(url)
.then((r) => r.json())
.then((data) => {
setData(data.concat(data));
setPage(page + 1);
});
};
const _handleLoadMore = () => {
_getData();
};
// useEffect with an empty dependency array replaces componentDidMount()
useEffect(() => _getData(), []);
return (
<FlatList
data={data}
renderItem={_renderItem}
keyExtractor={(item, index) => item.id}
onEndReached={_handleLoadMore}
onEndReachedThreshold={1}
/>
);
}
Here it is with axios and with a few other improvements. I noticed that the end reached function was being called upon reaching the end of the initial zero-length list causing the first page to be fetched twice. So actually the componentDidMount is not needed. I changed from .then() to async/await, but that doesn't matter.
import React, { useEffect, useState } from 'react';
import { View, Image, Text, FlatList } from 'react-native';
import axios from 'axios';
export default function App() {
const [page, setPage] = useState(1);
const [data, setData] = useState([]);
const _renderItem = ({ item }) => (
<View style={{ borderBottomWidth: 1, marginTop: 20 }}>
<Image source={{ uri: item.url }} style={{ height: 200 }} />
<Text>{item.title}</Text>
<Text>{item.id}</Text>
</View>
);
const _getData = async () => {
const url =
'https://jsonplaceholder.typicode.com/photos?_limit=10&_page=' + page;
const res = await axios.get(url);
setData(data.concat(res.data));
setPage(page + 1);
};
const _handleLoadMore = () => {
_getData();
};
// useEffect with an empty dependency array replaces componentDidMount()
useEffect(() => {
// put async functions inside curly braces to that you aren't returing the Promise
_getData();
}, []);
return (
<FlatList
data={data}
renderItem={_renderItem}
keyExtractor={(item, index) => item.id}
onEndReached={_handleLoadMore}
onEndReachedThreshold={1}
/>
);
}
Expo Link -- It works on my device, but the infinite scroll doesn't seem to work in the web preview.
There are additional more "advanced" improvements that you can make:
set state with a callback of previous state to ensure that values are always correct. setPage(current => current + 1) setData(current => current.concat(res.data))
memoization with useCallback so that functions like _renderItem maintain a constant reference across re-renders.
exhaustive useEffect dependencies (requires memoization).

How to display array elements in react native using FlatList

I'm trying to fetch data from an API and display that in a FlatList component in react. But when displaying data in the array, it displays an empty screen. Below is my code
useEffect(() => {
axios.get(`https://api.github.com/users/${user}/repos`)
.then(response => {
console.log(response.data)
response.data.map(repo => {
let rep = {
id:repo.id,
name:repo.name,
created:repo.created_at,
url:repo.html_url
}
repos.push(rep)
})
console.log(repos)
})
.catch(err => {
setError(true)
console.log(err)
})
},[])
return (
<View>
<Header navigate={navigation}/>
<FlatList
contentContainerStyle={{backgroundColor:'grey',marginTop:25}}
data={repos}
renderItem={({repo}) => (
<Text>
{repo.name}
</Text>
)}
/>
</View>
)
Here is the full working example: Expo Snack
import React, { useState, useEffect } from 'react';
import { Text, View, StyleSheet, FlatList } from 'react-native';
import Constants from 'expo-constants';
import axios from 'axios';
export default function App() {
const [repos, setRepos] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
axios
.get(`https://api.github.com/users/theketan2/repos`)
.then((response) => {
let data = [];
//console.log(response.data);
response.data.map((repo) => {
let rep = {
id: repo?.id,
name: repo?.name,
created: repo?.created_at,
url: repo?.html_url,
};
data.push(rep);
});
// console.log(data);
setRepos(data);
})
.catch((err) => {
setError(true);
console.log(err);
});
}, []);
useEffect(() => {
console.log(repos);
}, [repos]);
return (
<View style={styles.container}>
<FlatList
data={repos}
renderItem={({item}) => (
<View style={styles.list}>
<Text>{item?.name}</Text>
</View>
)}
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'rgba(21,21,21,0.1)',
},
list: {
padding: 10,
marginTop: 5,
borderRadius: 5,
backgroundColor: 'white',
marginHorizontal: 5,
},
});
Try this way
const [data, setData] = useState([]);
useEffect(() => {
axios.get(`https://api.github.com/users/${user}/repos`)
.then(response => {
setData(response.data);
})
.catch(err => {
setError(true)
console.log(err)
})
},[])
return (
<View>
<Header navigate={navigation}/>
<FlatList
data={data}
renderItem={(item) => (
<Text>
{item.name}
</Text>
)}
/>
</View>
)

Navigate to another page passing variables

I'm very new to React Native. I've got the following page which loads dates and results from a database, all displaying ok:
import React, { useEffect, useState } from "react";
import {
Text,
FlatList,
StyleSheet,
TouchableOpacity,
Platform,
} from "react-native";
import AsyncStorage from "#react-native-community/async-storage";
import Colors from "../../../res/Colors/Colors";
import { s, scale } from "react-native-size-matters";
import Axios from "axios";
import moment from "moment";
import { SafeAreaView} from "react-navigation";
export default function HistoryScreen({ navigation }) {
const [userDetails, setUserDetails] = useState(null);
const [loading, setLoading] = useState(true);
const [Star, setStar] = useState(null);
const [people, setPeople] = useState(null);
useEffect(() => {
_getHistory();
}, []);
const expandMonth = (month,year) => {
console.log(month);
console.log(year);
}
const _getHistory = async () => {
let star = await AsyncStorage.getItem("star");
star = JSON.parse(star);
setStar(star);
var formData = new FormData();
formData.append("StarID", star.StarID);
Axios({
method: "post",
url: "**API URL**",
data: formData,
//headers: { "Content-Type": "multipart/form-data" },
})
.then(async (response) => {
var responseBody = response.data.detail;
setPeople(responseBody);
setLoading(false);
})
.catch((error) => {
console.log({ error });
});
};
if (loading)
return (
<SafeAreaView>
</SafeAreaView>
);
return (
<SafeAreaView
style={{
flex: 1,
backgroundColor: Colors.lightGrey,
paddingTop: Platform.OS === "android" ? 40 : 40,
justifyContent: "space-evenly",
}}
>
<Text style={styles.headerText}>History</Text>
<FlatList
//keyExtractor={(item) => item.id}
data={people}
renderItem={({ item }) => (
<TouchableOpacity onPress={() => {
expandMonth(item.Month,item.Year)
navigation.navigate("HistoryMonthScreen", {
Month: item.Month,
Year: item.Year
});
}}
style={styles.month}>
<Text style={styles.monthname}>{moment(item.Month, 'M').format('MMMM')} {item.Year}</Text>
<Text style={styles.monthcount}>{item.Count} Bookings</Text>
</TouchableOpacity>
)}
/>
</SafeAreaView>
);
}
However when clicking the TouchableOpacity object it doesn't navigate to the next page passing the year and month variables. I assume I need to call some form of navigation to it, however everything I have tried has resulted in error after error.
For React Navigation 5 you have to use the route prop that is passed in alongside navigation for whatever screen you're navigating to.
Put the below code into HistoryMonthScreen.js
const HistoryMonthScreen = ({route, navigation}) => {
console.log(route.params.Month, route.params.Year);
return <View />
}
Edit: For context:
I'm going off what you have here
navigation.navigate("HistoryMonthScreen", {
Month: item.Month,
Year: item.Year
});
So I'm assuming you have some React Navigator somewhere that looks like this:
const AppNavigator5 = () => {
return (
<NavigationContainer>
<Stack.Navigator
initialRouteName="HistoryScreen"
screenOptions={{
headerShown: false,
animationEnabled: false,
}}>
<Stack.Screen
name="HistoryScreen"
component={HistoryScreen}
/>
<Stack.Screen
name="HistoryMonthScreen"
component={HistoryMonthScreen}
/>
<Stack.Screen name="Egg" component={Egg} />
</Stack.Navigator>
</NavigationContainer>
);
};
You put the code with route and navigation inside that HistoryMonthScreen component. You can pass different params into route.params every time, you don't have to change HistoryMonthScreen.

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.