Related
Good day.
I have managed to fetch an array of images from firestore database collection, the only problem is that it seems that I cannot loop over the array that I am retrieving from the database.
I need to dynamically display my images on the carousel.
import React, { useState, useEffect, useCallback } from 'react';
import { Text, Dimensions, StyleSheet, View, Image } from 'react-native';
import { SwiperFlatList } from 'react-native-swiper-flatlist';
import firestore from '#react-native-firebase/firestore';
import { white } from 'react-native-paper/lib/typescript/styles/colors';
import { color } from 'react-native-reanimated';
import Carousel from 'react-native-reanimated-carousel';
//const colors = ["tomato", "thistle", "skyblue", "teal"];
const names = [{"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/Ads%2FJB.png?alt=media&token=377f1629-6807-4343-b826-93d1c2bc5de6"}, {"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/Ads%2Fgymtarp.png?alt=media&token=b2d1c923-2b5c-4066-bf8f-dc12de399059"}, {"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/photos%2Fb75d0848-b37f-4584-ab46-f355ca838e83.jpg?alt=media&token=adaa8559-de91-4ced-b056-84300123102e"}, {"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/Ads%2FSANDO.png?alt=media&token=274c946b-7f20-4c07-8545-431a0558257c"}];
const App = () => {
const [ads, setAds] = useState([]); // Initial empty array of ads
useEffect(() => {
const subscriber = firestore()
.collection('AdsDB')
//.orderBy('Menu', 'asc')
.onSnapshot(querySnapshot => {
//const ads = [];
/*
querySnapshot.forEach(documentSnapshot => {
ads.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
*/
querySnapshot.forEach(doc => {
const { AdImage } = doc.data();
ads.push({
//id: doc.id,
AdImage,
//Price,
});
});
setAds(ads);
console.log(ads);
//console.log(Object.entries(ads));
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
return (
<View style={styles.container}>
<SwiperFlatList
autoplay
autoplayDelay={2}
autoplayLoop
index={2}
showPagination
data={ads}
renderItem={({ item }) => (
<View style={[styles.child, { backgroundColor: item }]}>
<Text style={styles.text}>{item.AdImage}</Text>
<Image
style={{
width: "100%",
height: "30%",
position: 'absolute',
top:0,
alignItems: 'center',
justifyContent: 'center'}}
source={{uri : item.AdImage}}
resizeMode={'stretch'} // cover or contain its upto you view look
/>
</View>
)}
/>
</View>
)
};
const { width } = Dimensions.get('window');
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: 'white', },
child: { width, justifyContent: 'center', height: '100%' },
text: { fontSize: width * 0.1, textAlign: 'center' },
});
export default App;
Thank you for answering my question. Mabuhay! I'm from the Philippines.
The problem I am seeing is that probably meanwhile you are receiving asynchronously the snapshots, the setState did not ended running (a race condition between the velocity receiving snapshots and the exact time needed to persist the state)
I would try to delay the state change and use the previous state value in the setter, like this:
useEffect(() => {.
const subscriber = firestore()
.collection('AdsDB')
.onSnapshot(querySnapshot => {
querySnapshot.forEach(doc => {
const { AdImage } = doc.data();
setTimeout(() => {
setAds((prevAds) => [...prevAds, AdImage]);
}, 500);
});
console.log(ads);
//console.log(Object.entries(adsFromFirebase));
});
These are my code, Sir. It gives an empty array.
import React, { useState, useEffect, useCallback } from 'react';
import { Text, Dimensions, StyleSheet, View, Image } from 'react-native';
import { SwiperFlatList } from 'react-native-swiper-flatlist';
import firestore from '#react-native-firebase/firestore';
import { white } from 'react-native-paper/lib/typescript/styles/colors';
import { color } from 'react-native-reanimated';
const colors = ["tomato", "thistle", "skyblue", "teal"];
const names = [{"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/Ads%2FJB.png?alt=media&token=377f1629-6807-4343-b826-93d1c2bc5de6"}, {"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/Ads%2Fgymtarp.png?alt=media&token=b2d1c923-2b5c-4066-bf8f-dc12de399059"}, {"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/photos%2Fb75d0848-b37f-4584-ab46-f355ca838e83.jpg?alt=media&token=adaa8559-de91-4ced-b056-84300123102e"}, {"AdImage": "https://firebasestorage.googleapis.com/v0/b/pindot-65e7c.appspot.com/o/Ads%2FSANDO.png?alt=media&token=274c946b-7f20-4c07-8545-431a0558257c"}];
const App = () => {
const [ads, setAds] = useState([]); // Initial empty array of ads
useEffect(() => {
const subscriber = firestore()
.collection('AdsDB')
.onSnapshot(querySnapshot => {
querySnapshot.forEach(doc => {
const { AdImage } = doc.data();
setTimeout(() => {
setAds((prevAds) => [...prevAds, AdImage]);
}, 500);
});
console.log(ads);
//console.log(Object.entries(adsFromFirebase));
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
/*
useEffect(() => {
const subscriber = firestore()
.collection('AdsDB')
//.orderBy('Menu', 'asc')
.onSnapshot(querySnapshot => {
//const ads = [];
/*
querySnapshot.forEach(documentSnapshot => {
ads.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
});
querySnapshot.forEach(doc => {
const { AdImage } = doc.data();
ads.push({
//id: doc.id,
AdImage,
//Price,
});
});
setAds(ads);
//console.log(ads);
//console.log(Object.entries(ads));
});
// Unsubscribe from events when no longer in use
return () => subscriber();
}, []);
*/
return (
<View style={styles.container}>
<SwiperFlatList
autoplay
autoplayDelay={2}
autoplayLoop
index={2}
data={ads}
renderItem={({ item }) => (
<View style={[styles.child, { backgroundColor: item.colors }]}>
<Text style={styles.text}>{item.AdImage}</Text>
<Image
style={{
width: "100%",
height: "30%",
position: 'absolute',
top:0,
alignItems: 'center',
justifyContent: 'center'}}
source={{uri : item.AdImage}}
resizeMode={'stretch'} // cover or contain its upto you view look
/>
{/*}*/}
</View>
)}
/>
</View>
)
};
const { width } = Dimensions.get('window');
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: 'white', },
child: { width, justifyContent: 'center', height: '100%' },
text: { fontSize: width * 0.1, textAlign: 'center' },
});
export default App;
This is the output.
LOG Running "myApp" with {"rootTag":21}
LOG []
When I click the hardware button on android the app closes, I want to go back on the previous page,
This is my code
import { StatusBar } from 'expo-status-bar';
import React, { useState } from 'react';
import { ActivityIndicator, Linking, SafeAreaView, StyleSheet, Text, View } from 'react-native';
import { WebView } from 'react-native-webview';
export default function App() {
const [isLoadong, setLoading] = useState(false);
return (
<SafeAreaView style={styles.safeArea}>
<WebView
originWhiteList={['*']}
source={{ uri: 'https://google.com' }}
style={styles.container }
onLoadStart={(syntheticEvent) => {
setLoading(true);
}}
onShouldStartLoadWithRequest={(event)=>{
if (event.navigationType === 'click') {
if (!event.url.match(/(google\.com\/*)/) ) {
Linking.openURL(event.url)
return false
}
return true
}else{
return true;
}
}}
onLoadEnd={(syntheticEvent) => {
setLoading(false);
}} />
{isLoadong && (
<ActivityIndicator
color="#234356"
size="large"
style={styles.loading}
/>
)}
</SafeAreaView>
);
}
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#234356'
},
loading: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
},
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
There are built-in goBack() method available in react-native-webview libraries
you can use the API method to implement back navigation of webview.
For this, you have to get the reference of react-native-webview component and call method from the reference object.
also, you are able to put the listener on the Android Native Back Button Press event to call the goBack() method of webview.
try the following code...
import { StatusBar } from 'expo-status-bar';
import React, { useState, useRef, useEffect } from 'react';
import { ActivityIndicator, Linking, SafeAreaView, StyleSheet, BackHandler } from 'react-native';
import { WebView } from 'react-native-webview';
export default function App() {
const webViewRef = useRef()
const [isLoadong, setLoading] = useState(false);
const handleBackButtonPress = () => {
try {
webViewRef.current?.goBack()
} catch (err) {
console.log("[handleBackButtonPress] Error : ", err.message)
}
}
useEffect(() => {
BackHandler.addEventListener("hardwareBackPress", handleBackButtonPress)
return () => {
BackHandler.removeEventListener("hardwareBackPress", handleBackButtonPress)
};
}, []);
return (
<SafeAreaView style={styles.safeArea}>
<WebView
originWhiteList={['*']}
source={{ uri: 'https://google.com' }}
style={styles.container}
ref={webViewRef}
onLoadStart={(syntheticEvent) => {
setLoading(true);
}}
onShouldStartLoadWithRequest={(event)=>{
if (event.navigationType === 'click') {
if (!event.url.match(/(google\.com\/*)/) ) {
Linking.openURL(event.url)
return false
}
return true
}
else{
return true;
}
}}
onLoadEnd={(syntheticEvent) => {
setLoading(false);
}}
/>
{isLoadong && (
<ActivityIndicator
color="#234356"
size="large"
style={styles.loading}
/>
)}
</SafeAreaView>
);
}
const styles = StyleSheet.create({
safeArea: {
flex: 1,
backgroundColor: '#234356'
},
loading: {
position: 'absolute',
left: 0,
right: 0,
top: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center'
},
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
first add ref for access your webview like that:
<WebView
ref={WEBVIEW_REF}
then for access to Hardware Back Button you can use this:
import { BackHandler } from 'react-native';
constructor(props) {
super(props)
this.handleBackButtonClick = this.handleBackButtonClick.bind(this);
}
componentWillMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButtonClick);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButtonClick);
}
handleBackButtonClick() {
this.refs[WEBVIEW_REF].goBack();
return true;
}
in handleBackButtonClick you can do back for webview and add this.refs[WEBVIEW_REF].goBack(); . I Hope that's helpful:)
Here is a simple solution using the magic of React's State.
Hope this helps.
import React, { useRef, useState } from 'react'
export default function Component () {
// This is used to save the reference of your webview, so you can control it
const webViewRef = useRef(null);
// This state saves whether your WebView can go back
const [webViewcanGoBack, setWebViewcanGoBack] = useState(false);
const goBack = () => {
// Getting the webview reference
const webView = webViewRef.current
if (webViewcanGoBack)
// Do stuff here if your webview can go back
else
// Do stuff here if your webview can't go back
}
return (
<WebView
source={{ uri: `Your URL` }}
ref={webViewRef}
javaScriptEnabled={true}
onLoadProgress={({ nativeEvent }) => {
// This function is called everytime your web view loads a page
// and here we change the state of can go back
setWebViewcanGoBack(nativeEvent.canGoBack)
}}
/>
)
}
I have a component that fetch data first. then I manipulate the data but it doesn't change value until I change the screen and return back. Here I change the data based on the categories and doctors which i fetch using redux. but transformed data remains empty until i back to the page for the second time. Thanks for your help
import React, { useState, useEffect, useCallback } from "react";
import {
View,
Text,
StyleSheet,
ActivityIndicator,
FlatList,
Dimensions,
} from "react-native";
import { useSelector, useDispatch } from "react-redux";
import Colors from "../../constants/Colors";
import * as DoctorsActions from "../../store/actions/Doctors";
const { height } = Dimensions.get("window");
const ConcultationMainScreen = (props) => {
const dispatch = useDispatch();
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("");
const categories = useSelector((state) => state.categories.categories);
const doctors = useSelector((state) => state.doctors.doctors);
const loadAllDoctors = useCallback(async () => {
try {
await dispatch(DoctorsActions.getDoctors());
} catch (err) {
setError(err);
console.log(error);
}
}, [dispatch, setError]);
useEffect(() => {
setIsLoading(true);
loadAllDoctors().then(() => {
setIsLoading(false);
});
}, [dispatch, loadAllDoctors]);
if (isLoading) {
return (
<View style={styles.centered}>
<ActivityIndicator size="large" color={Colors.blue} />
</View>
);
}
let transformedData = [];
for (const cat in categories) {
let doctorsOfCategory = [];
for (const doc in doctors) {
if (doctors[doc].categories[0] === categories[cat].name) {
doctorsOfCategory.push({
doctorName: doctors[doc].name,
});
} else {
continue;
}
}
transformedData.push({
categoryName: categories[cat].name,
doctorsOfCategory: doctorsOfCategory,
});
}
console.log(transformedData);
const renderConsultCategories = (ItemData) => {
return (
<View style={styles.item}>
<View style={styles.titleContainer}>
<Text>
گفتگو و مشاوره با متخصصین <Text>{ItemData.item.categoryName}</Text>
</Text>
</View>
</View>
);
};
return (
<View style={styles.screen}>
<FlatList
data={transformedData}
keyExtractor={(item) => item.categoryName}
renderItem={renderConsultCategories}
/>
</View>
);
};
export const screenOptions = (navData) => {
return {
headerTitle: "صفحه مشاوره",
headerTitleAlign: "center",
};
};
const styles = StyleSheet.create({
screen: {
flex: 1,
},
centered: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
item: {
flexDirection: "row-reverse",
width: "100%",
height: height / 8,
marginVertical: 10,
borderBottomWidth: 1,
},
});
export default ConcultationMainScreen;
Use this.forceUpdate() to re-render without calling setState().
I'm trying to create a Blinking function in an App using hooks and react-navigation 5 - No classes.
"react-native-reanimated" is new to me. I'm more familiar with Animated so this is why I need some help here. Thank you!
import React, { useState, useEffect, Component, useCallback } from "react";
import {
StyleSheet,
Text,
View
} from "react-native";
import Animated, { Easing } from "react-native-reanimated";
import { loop } from "react-native-redash";
function BlinkIt(props){
const [fadeAnim] = useState(new Animated.Value(0));
useEffect(() => {
Animated.set(
fadeAnim,
loop({
duration: 5000,
autoStart: true,
boomerang: true
})
)
}, []);
return (
<Animated.View // Special animatable View
style={{
...props.style,
opacity: fadeAnim
}}
>
{props.children}
</Animated.View>
);
}
export default function App() {
return (
<View style={styles.container}> <BlinkIt><Text>The text is blinking</Text></BlinkIt></View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
I made a video tutorial on how to create loop animation with react-native-reanimated https://youtu.be/W82p3WfwxrA. Or check code listing below
import React, {useMemo, useState, useEffect} from 'react';
import {
TouchableWithoutFeedback,
Image,
StyleSheet,
Dimensions,
} from 'react-native';
import Animated, { Easing, stopClock } from 'react-native-reanimated';
const imageSize = {
width: 256,
height: 256,
};
const screenWidth = Dimensions.get('window').width;
const animatedWidth = screenWidth + imageSize.width;
const {
useCode,
block,
set,
Value,
Clock,
eq,
clockRunning,
not,
cond,
startClock,
timing,
interpolate,
and,
} = Animated;
const runTiming = (clock) => {
const state = {
finished: new Value(0),
position: new Value(0),
time: new Value(0),
frameTime: new Value(0),
};
const config = {
duration: 5000,
toValue: 1,
easing: Easing.inOut(Easing.linear),
};
return block([
// we run the step here that is going to update position
cond(
not(clockRunning(clock)),
set(state.time, 0),
timing(clock, state, config),
),
cond(eq(state.finished, 1), [
set(state.finished, 0),
set(state.position, 0),
set(state.frameTime, 0),
set(state.time, 0),
]),
state.position,
]);
}
export const AnimatedBackground = () => {
const [play, setPlay] = useState(false);
const {progress, clock, isPlaying} = useMemo(
() => ({
progress: new Value(0),
isPlaying: new Value(0),
clock: new Clock(),
}),
[],
);
useEffect(() => {
isPlaying.setValue(play ? 1 : 0);
}, [play, isPlaying]);
useCode(
() =>
block([
cond(and(not(clockRunning(clock)), eq(isPlaying, 1)), startClock(clock)),
cond(and(clockRunning(clock), eq(isPlaying, 0)), stopClock(clock)),
set(progress, runTiming(clock)),
]),
[progress, clock],
);
return (
<TouchableWithoutFeedback
style={styles.container}
onPress={() => setPlay(!play)}
>
<Animated.View style={[styles.image, { opacity: progress }]}>
<Image
style={styles.image}
source={require('./cloud.png')}
resizeMode="repeat"
/>
</Animated.View>
</TouchableWithoutFeedback>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
image: {
width: animatedWidth,
height: '100%',
},
});
I have a horizontal FlatList, where each time it reaches the end, it automatically adds new elements to the list, so it kind of is an infinite list. I want the app to scroll through the list by itself automatically, while the user must still be able to scroll back and forth. This is what I have to far
export default class ImageCarousel extends Component {
constructor(props) {
super(props);
this.scrollX = 0;
this.offset = new Animated.Value(0);
this.scrollTo = this.scrollTo.bind(this);
this.handleScroll = this.handleScroll.bind(this);
this.stopAnimation = this.stopAnimation.bind(this);
// Listener to call the scrollToOffset function
this.offset.addListener(this.scrollTo);
}
_scroller() {
toValue = this.scrollX + 10; // Scroll 10 pixels in each loop
this.animation = Animated.timing(
this.offset,
{
toValue: toValue,
duration: 1000, // A loop takes a second
easing: Easing.linear,
}
);
this.animation.start(() => this._scroller()); //Repeats itself when done
}
scrollTo(e) {
this.carousel.scrollToOffset({offset: e.value});
}
handleScroll(event) {
// Save the x (horizontal) value each time a scroll occurs
this.scrollX = event.nativeEvent.contentOffset.x;
}
componentDidMount() {
this._scroller();
}
render() {
return (
<View>
<FlatList
ref={el => this.carousel = el}
data={someData}
renderItem={renderFunction}
horizontal={true}
keyExtractor={someKeyFunction}
onEndReached={loadMoreElementsFunction}
onScroll={this.handleScroll}
/>
</View>
);
}
}
It works in the sense that it is automatically scrolling through the list, the problem however, is I cannot manually scroll through the list, since the scroll position is constantly updated by the scrollTo listener. I have tried to add an onPress callback to disable the animation when the FlatList is pressed, I have however not been able to get it to work.
This is my Data.
Blockquote
state = {
link: [
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
],};
Define FlatList Ref
flatList = createRef();
FlatList component
<FlatList
style={{flex: 1}}
data={this.state.link}
keyExtractor={this._keyExtractor.bind(this)}
renderItem={this._renderItem.bind(this)}
horizontal={true}
flatListRef={React.createRef()}
ref={this.flatList}
/>
Next slide
_goToNextPage = () => {
if (CurrentSlide >= this.state.link.length-1) CurrentSlide = 0;
this.flatList.current.scrollToIndex({
index: ++CurrentSlide,
animated: true,
});
};
Start and stop Interval
_startAutoPlay = () => {
this._timerId = setInterval(this._goToNextPage, IntervalTime);
};
_stopAutoPlay = () => {
if (this._timerId) {
clearInterval(this._timerId);
this._timerId = null;
}
};
Associated function
componentDidMount() {
this._stopAutoPlay();
this._startAutoPlay();
}
componentWillUnmount() {
this._stopAutoPlay();
}
_renderItem({item, index}) {
return <Image source={{uri: item}} style={styles.sliderItems} />;
}
_keyExtractor(item, index) {
return index.toString();
}
Full Code:
import React, {Component, createRef} from 'react';
import {
Text,
View,
ScrollView,
Image,
StyleSheet,
Dimensions,
FlatList,
} from 'react-native';
let CurrentSlide = 0;
let IntervalTime = 4000;
export default class Slider extends Component {
flatList = createRef();
// TODO _goToNextPage()
_goToNextPage = () => {
if (CurrentSlide >= this.state.link.length-1) CurrentSlide = 0;
this.flatList.current.scrollToIndex({
index: ++CurrentSlide,
animated: true,
});
};
_startAutoPlay = () => {
this._timerId = setInterval(this._goToNextPage, IntervalTime);
};
_stopAutoPlay = () => {
if (this._timerId) {
clearInterval(this._timerId);
this._timerId = null;
}
};
componentDidMount() {
this._stopAutoPlay();
this._startAutoPlay();
}
componentWillUnmount() {
this._stopAutoPlay();
}
// TODO _renderItem()
_renderItem({item, index}) {
return <Image source={{uri: item}} style={styles.sliderItems} />;
}
// TODO _keyExtractor()
_keyExtractor(item, index) {
// console.log(item);
return index.toString();
}
state = {
link: [
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
// 'https://picsum.photos/200/300',
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
'https://image.shutterstock.com/image-vector/online-exam-computer-web-app-260nw-1105800884.jpg',
],
};
render() {
return (
<View style={{marginTop: 10, marginBottom: 10}}>
<FlatList
style={{
flex: 1,
// TODO Remove extera global padding
// marginLeft: -size.padding,
// marginRight: -size.padding,
}}
data={this.state.link}
keyExtractor={this._keyExtractor.bind(this)}
renderItem={this._renderItem.bind(this)}
horizontal={true}
flatListRef={React.createRef()}
ref={this.flatList}
/>
</View>
);
}
}
const styles = StyleSheet.create({
sliderItems: {
marginLeft: 5,
marginRight: 5,
height: 200,
width: Dimensions.get('window').width,
},
});
Just in case you're still not found the answer yet,
this is my approach to create autoscroll carousel using FlatList
import React, { Component } from 'react'
import {
StyleSheet,
View,
FlatList,
ScrollView,
Dimensions,
Image
} from 'react-native'
const { width } = Dimensions.get('window');
const height = width * 0.2844;
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
search: '',
sliderIndex: 0,
maxSlider: 2,
banners: [
{_id: 1, imageUrl: 'https://www.do-cart.com/img/slider/1.jpg'},
{_id: 2, imageUrl: 'https://www.do-cart.com/img/slider/2.jpg'},
{_id: 3, imageUrl: 'https://www.do-cart.com/img/slider/3.jpg'},
],
}
}
setRef = (c) => {
this.listRef = c;
}
scrollToIndex = (index, animated) => {
this.listRef && this.listRef.scrollToIndex({ index, animated })
}
componentWillMount() {
setInterval(function() {
const { sliderIndex, maxSlider } = this.state
let nextIndex = 0
if (sliderIndex < maxSlider) {
nextIndex = sliderIndex + 1
}
this.scrollToIndex(nextIndex, true)
this.setState({sliderIndex: nextIndex})
}.bind(this), 3000)
}
render() {
return (
<View style={styles.container}>
<View style={{height: 80, backgroundColor: '#123866', width:'100%'}}></View>
<ScrollView style={styles.scrollContainer} showsVerticalScrollIndicator={false}>
<FlatList
ref={this.setRef}
data={this.state.banners}
horizontal
showsHorizontalScrollIndicator={false}
pagingEnabled
keyExtractor={item => item._id}
renderItem={({item, i}) => (
<View key={i} style={{ height, width}}>
<Image style={{ height, width }} source={{ uri: item.imageUrl }} />
</View>
)}
onMomentumScrollEnd={(event) => {
let sliderIndex = event.nativeEvent.contentOffset.x ? event.nativeEvent.contentOffset.x/width : 0
this.setState({sliderIndex})
}}
/>
<View style={styles.sliderContainer}>
{
this.state.banners.map(function(item, index) {
return (
<View key={index} style={styles.sliderBtnContainer}>
<View style={styles.sliderBtn}>
{
this.state.sliderIndex == index ? <View style={styles.sliderBtnSelected}/> : null
}
</View>
</View>
)
}.bind(this))
}
</View>
</ScrollView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
scrollContainer: {
flex: 1
},
sliderContainer: {
flexDirection: 'row',
position: 'absolute',
top: 80,
alignSelf: 'center'
},
sliderBtn: {
height: 13,
width: 13,
borderRadius: 12,
borderWidth: 1,
borderColor: 'white',
alignItems: 'center',
justifyContent: 'center',
marginRight: 10
},
sliderBtnSelected: {
height: 12,
width: 12,
borderRadius: 6,
backgroundColor: 'white',
},
sliderBtnContainer: {
flexDirection: 'row', marginBottom: 24
},
});
https://snack.expo.io/rJ9DOn0Ef
For those looking for a function-based component, this is my approach. The user can interact with the carousel and the automatic scroller will simply continue from the current slide.
The trick to achieving this is using an "onViewableItemsChanged" callback, where the "itemVisiblePercentThreshold" is >= 50%. This ensures the callback fires after the scroll to the new page is more than 50% complete (otherwise the automatic scroller triggers the callback to early and makes it scroll back).
import { useCallback, useEffect, useRef, useState } from "react";
import { Dimensions } from "react-native";
import { FlatList, Image, StyleSheet } from "react-native";
const width = Dimensions.get("screen").width;
export const CarouselAutoScroll = ({ data, interval }) => {
const imageRef = useRef();
const [active, setActive] = useState(0);
const indexRef = useRef(active);
indexRef.current = active;
useInterval(() => {
if (active < Number(data?.length) - 1) {
setActive(active + 1);
} else {
setActive(0);
}
}, interval);
useEffect(() => {
imageRef.current.scrollToIndex({ index: active, animated: true });
}, [active]);
const onViewableItemsChangedHandler = useCallback(
({ viewableItems, changed }) => {
if (active != 0) {
setActive(viewableItems[0].index);
}
},
[]
);
return (
<FlatList
showsHorizontalScrollIndicator={false}
onViewableItemsChanged={onViewableItemsChangedHandler}
viewabilityConfig={{
itemVisiblePercentThreshold: 50,
}}
ref={imageRef}
pagingEnabled
data={data}
horizontal
renderItem={({ item, index }) => (
<Image
key={index}
source={item.image}
resizeMode={"contain"}
style={{
flex: 1,
height: "100%",
width: width,
}}
/>
)}
style={{ ...StyleSheet.AbsoluteFill }}
/>
);
};
const useInterval = (callback, delay) => {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
}, [callback]);
useEffect(() => {
const tick = () => {
savedCallback.current();
};
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
};