React Native Flat List doesn't call onEndReached handler after two successful calls - react-native

I implement a very simple list that calls a server that returns a page containing books.Each book has a title, author, id, numberOfPages, and price). I use a Flat List in order to have infinite scrolling and it does its job very well two times in a row (it loads the first three pages) but later it doesn't trigger the handler anymore.
Initially it worked very well by fetching all available pages, but it stopped working properly after I added that extra check in local storage. If a page is available in local storage and it has been there no longer than 5 seconds I don't fetch the data from the server, instead I use the page that is cached. Of course, if there is no available page or it is too old I fetch it from the server and after I save it in local storage.(Something went wrong after adding this behavior related to local storage.)
Here is my component:
export class BooksList extends Component {
constructor(props) {
super(props);
this.state = {
pageNumber: 0
};
}
async storePage(page, currentTime) {
try {
page.currentTime = currentTime;
await AsyncStorage.setItem(`page${page.page}`, JSON.stringify(page));
} catch (error) {
console.log(error);
}
}
subscribeToStore = () => {
const { store } = this.props;
this.unsubsribe = store.subscribe(() => {
try {
const { isLoading, page, issue } = store.getState().books;
if (!issue && !isLoading && page) {
this.setState({
isLoading,
books: (this.state.books ?
this.state.books.concat(page.content) :
page.content),
issue
}, () => this.storePage(page, new Date()));
}
} catch (error) {
console.log(error);
}
});
}
componentDidMount() {
this.subscribeToStore();
// this.getBooks();
this.loadNextPage();
}
componentWillUnmount() {
this.unsubsribe();
}
loadNextPage = () => {
this.setState({ pageNumber: this.state.pageNumber + 1 },
async () => {
let localPage = await AsyncStorage.getItem(`page${this.state.pageNumber}`);
let pageParsed = JSON.parse(localPage);
if (localPage && (new Date().getTime() - localPage.currentTime) < 5000) {
this.setState({
books: (
this.state.books ?
this.state.books.concat(pageParsed.content) :
page.content),
isLoading: false,
issue: null
});
} else {
const { token, store } = this.props;
store.dispatch(fetchBooks(token, this.state.pageNumber));
}
});
}
render() {
const { isLoading, issue, books } = this.state;
return (
<View style={{ flex: 1 }}>
<ActivityIndicator animating={isLoading} size='large' />
{issue && <Text>issue</Text>}
{books && <FlatList
data={books}
keyExtractor={book => book.id.toString()}
renderItem={this.renderItem}
renderItem={({ item }) => (
<BookView key={item.id} title={item.title} author={item.author}
pagesNumber={item.pagesNumber} />
)}
onEndReachedThreshold={0}
onEndReached={this.loadNextPage}
/>}
</View>
)
}
}
In the beginning the pageNumber available in the state of the component is 0, so the first time when I load the first page from the server it will be incremented before the rest call.
And here is the action fetchBooks(token, pageNumber):
export const fetchBooks = (token, pageNumber) => dispatch => {
dispatch({ type: LOAD_STARTED });
fetch(`${httpApiUrl}/books?pageNumber=${pageNumber}`, {
headers: {
'Authorization': token
}
})
.then(page => page.json())
.then(pageJson => dispatch({ type: LOAD_SUCCEDED, payload: pageJson }))
.catch(issue => dispatch({ type: LOAD_FAILED, issue }));
}
Thank you!

Related

React Native dynamic search with flatlist from API

function ManageData({props, navigation}) {
const [details, setDetails] = useState({
dataList: [],
loading: true,
offset: 1,
totalRecords: 0,
search: '',
});
useEffect(() => {
getData();
}, []);
const getData = async () => {
try {
// console.log('search',details.search);
var params = {};
params = {
'pagination[page]': details.offset,
'pagination[perpage]': 10,
};
if(details?.search?.length > 0){
params['query[search]'] = details?.search;
params['pagination[pages]'] = 30;
params['pagination[total]'] = 293;
}else{
params['query'] = ""
}
const result = await getPayeeDetails(session, params);
// console.log('result',result?.data?.data?.length);
if (result?.data?.data?.length > 0) {
setDetails(prev => ({
...prev,
offset: prev.offset + 1,
dataList: [...prev.dataList, ...result.data.data],
loading: false,
totalRecords: result.data.recordsFiltered,
}));
}
} catch (error) {
console.log('getPayeesError', error);
}
};
const loadMore = () => {
try {
if (details.dataList.length != details.totalRecords) {
setDetails(prev => ({
...prev,
loading: true,
}));
getData();
}
} catch (error) {
console.log('LoadMoreError', error);
}
};
const searchHandler=(data)=>{
try{
console.log('clearData',data);
setDetails(prev => ({
...prev,
dataList:[],
offset:1,
search: data == 'RESET'?"":data,
}));
getData();
}catch(error){
console.log("SearchError",error)
}
}
return (
<BackDropContainer
searchHandler={searchHandler}>
<View style={{backgroundColor: 'white', flex: 1}}>
<FlatList
style={{marginTop: '4%'}}
data={details?.dataList}
renderItem={({item}) => (
<TouchableOpacity onPress={() => showDialog(item)}>
<Item data={item} />
</TouchableOpacity>
)}
onEndReached={loadMore}
keyExtractor={(item, index) => index}
/>
</View>
</BackDropContainer>
);
}
I have a flatlist with searchview in my React Native application. Each time user scrolls to the end of flatlist the loadmore function will be called and also the offset value is increased as 1 to fetch next page from API.
Every time the API results array of 10 data from API so the flatlist will be loaded 10 by 10 for each scroll. When I type some data in searchview the searchHandler function will be called, and there I want to reset the offset as 1 and also need to send typed data to the API.
The issue is searched data and offset is not sending with API whenever I try to search the data. State is not updating properly when searching data.
Note: The data which is types has to be sent along with API whenever user search something.

How to force update single component react native

I'm using 2 usestate in my component
const [choosedH, setChoosedH] = useState([]);
const [h, setH] = useState([]);
I have async method which fetch data from api and convert it to final array.
useEffect(() => {
getH();
}, [])
async function getH(){
const username = await SecureStore.getItemAsync('username')
const token = await SecureStore.getItemAsync('token')
axiosInstance.get('/api/xxx/' + username,
{
headers: {
Cookie: token,
},
},
{ withCredentials: true }
)
.then((response) => {
if(response.data.length > 0){
let singleH = {};
response.data.forEach(element => {
singleH = {
label: element.name,
value: element.name
}
h.push(singleH);
});
console.log(h)
}
})
.catch(function (error) {
console.log('There has been a problem with your fetch operation: ' + error.message);
throw error;
})
}
and finally i have my component:
<RNPickerSelect
onValueChange={(value) => setChoosedH(value)}
items={h}
useNativeAndroidPickerStyle={false}
style={{
...pickerSelectStyles,
iconContainer: {
top: 10,
right: 10,
},
}}
placeholder={{
label: 'Select',
value: null,
}}
Icon={() => {
return <Icon name="arrow-down" size={24} />;
}}
value={choosedH}
/>
I have a problem. After render my picker contain empty array. When render end, hook useEffect call getH() which give me data from api and convert it as I want to value of useState "h". How to force update picker items when function getH will end? It it possible to get data from api before render? Any ideas ?
I guess the problem is that you try to access h directly instead of using setH.
This should work:
if(response.data.length > 0){
const myArray = []
response.data.forEach(element => {
const singleH = {
label: element.name,
value: element.name
}
myArray.push(singleH);
});
setH(myArray)
console.log(h)
}

Can't get the userID(PlayerID). It's undefinded (ReactNative, OneSignal)

Via my application I will send notification throught OneSignal. But I can't get userID from OneSignal. I read this userID from GET request and save it in DB. After I send notification via PHP.
How I can get this userID If I always get undefined?
export default class App extends Component {
constructor(props) {
super(props);
this.WEBVIEW_REF = React.createRef();
}
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
OneSignal.setLogLevel(6, 0);
OneSignal.setAppId("fdb89158-4072-4964-b490-6ba70fb6b5fd");
OneSignal.promptForPushNotificationsWithUserResponse(response => {
});
OneSignal.setNotificationWillShowInForegroundHandler(notificationReceivedEvent => {
let notification = notificationReceivedEvent.getNotification();
console.log("notification: ", notification);
const data = notification.additionalData
console.log("additionalData: ", notification.additionalData);
notificationReceivedEvent.complete(notification);
});
OneSignal.setNotificationOpenedHandler(notification => {
});
OneSignal.addPermissionObserver(event => {
console.log("OneSignal: permission changed:", event);
});
OneSignal.addSubscriptionObserver(event => {
console.log("OneSignal: subscription changed to userId:", event.to.userId);
});
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
handleBackButton = ()=>{
this.WEBVIEW_REF.current.goBack();
return true;
}
onNavigationStateChange(navState) {
this.setState({
canGoBack: navState.canGoBack
});
}
render() {
const deviceState = OneSignal.getDeviceState();
return (
<WebView
source={{ uri: 'https://www.google.com/search?q='+ deviceState.userId}}
style={{ marginTop: 35 }}
ref={this.WEBVIEW_REF}
onNavigationStateChange={this.onNavigationStateChange.bind(this)}
/>
);
}
}
getDeviceState returns a asynchronous so make sure you're awaiting on it.

How to get AsyncStorage key name from FlatList item to delete?

I am generating a random key name for AsyncStorage each time user saves an item. These are then displayed in FlatList (using SwipeListView library for swipe to delete button). Now if I call await AsyncStorage.removeItem(key); when the user taps "Delete", I presume the item will just disappear from the list. What I'm completely lost on is how I am supposed to get my random key name? Struggling to find much on FlatList and AsyncStorage, not sure what good practice is.
FlatList:
export default class RecentMealsScreen extends Component {
constructor() {
super();
this.state={
meals: []
}
}
componentDidMount() {
this.getAllMeals();
}
getAllMeals = async () => {
try {
const data = [];
let keys = await AsyncStorage.getAllKeys();
for (let inKey of keys) {
let obj = await AsyncStorage.getItem(inKey);
obj = JSON.parse(obj);
data.push(obj);
}
this.setState({
meals: data
})
} catch (error) {
console.log("Error saving all meals. Error: " + error)
}
}
renderHiddenItem = () => (
<View style={styles.rowBack}>
<View style={[styles.backRightBtn, styles.backRightBtnRight]}>
<Text style={styles.backTextWhite}>Delete</Text>
</View>
</View>
);
deleteMeal = async (key) => {
try {
await AsyncStorage.removeItem(key);
} catch (error) {
console.log('Error deleting Meal: ' + error)
}
}
// Get Meal IDs and display them in list
render() {
return (
<View style={styles.container}>
<SwipeListView
data={this.state.meals}
renderItem={ ({item}) =>
<View style={styles.container}>
<Meal
image = {item.image}
order={item.orderName}
company={item.companyName}
price={item.price}
dateTime={item.dateTime}
notes={item.notes}
rating = {item.rating}
/>
</View>
}
disableRightSwipe
renderHiddenItem={this.renderHiddenItem}
rightOpenValue={-Dimensions.get('window').width}
useNativeDriver={false}
onSwipeValueChange={this.deleteMeal()}
/>
</View>
);
}
}
Save Logic:
saveMeal = async () => {
try {
let meal = {
image: this.state.imageSource,
orderName: this.state.orderText,
companyName: this.state.selectedCompany,
price: this.state.priceText,
dateTime: this.state.dateTimeText,
notes: this.state.notesTextField,
rating: this.state.starCount
};
const ID = await Random.getRandomBytesAsync(16);
await AsyncStorage.setItem(ID.toString(), JSON.stringify(meal)).then(() => {
// Redirect to new screen
Actions.recentMeals();
})
} catch (error) {
console.log("Save Meal error: " + error)
}
}

react-native-camera barcode scanner freezes, because it scans too fast

I am trying to use the barcode scanner from react-native-camera. First, off it scans a QR-code and extracts a String, after that it navigates to the next Screen with react-navigation. In the second screen, it makes an API-call.
Now if I go back to the scanner screen, de QR-code will be scanned immediately. That's where I run into an error and the scanner freezes. I usually get this error:
Can't call setState (or forceUpdate) on an unmounted component
I think it's because my componentWillUnmount cleanup doesn't work properly or fast enough, but I already cancel the axios request.
requestCode = (code) => {
if (cancel != undefined) {
cancel();
}
axios.get(API_URI + code, {
cancelToken: new CancelToken(function executor(c) {
cancel = c;
})
}).then(response => {
console.log(response)
//checks if code was already called
this.checkUsed(response.data)
})
.catch(error => {
this.setState({ isValid: false })
});
}
componentWillUnmount() {
cancel();
}
Maybe I could mount the camera-scanner a little bit later so it doesn't scan this fast or is it maybe even an error with React Navigation?
You can use a flag to control.
class QR extends Component {
constructor(props) {
super(props)
this.state = {
scanable: true
}
this.cameraAttrs = {
ref: ref => {
this.camera = ref
},
style: styles.preview,
type: RNCamera.Constants.Type.back,
barCodeTypes: [RNCamera.Constants.BarCodeType.qr],
onBarCodeRead: ({ data }) => {
this.callback(data)
}
}
}
componentWillMount() {
this._mounted = true
}
componentWillUnmount() {
this._mounted = false
}
callback(text) {
if (!this.state.scanable) {
return
}
console.log(text)
this.setState({ scanable: false })
setTimeout(() => {
if (this._mounted) {
this.setState({ scanable: true })
}
}, 1000) // 1s cooldown
}
render() {
return (
<View style={styles.container}>
<RNCamera
{...this.cameraAttrs}
>
</RNCamera>
</View>
)
}
}