Calling Method in React Native using Refs Does Nothing - reactjs

I'm making a simple pomodoro app in React Native, and I came across a problem with calling a method from a child component. In the code below, the method I am trying to call is reset, which I call from resetTimer in the parent. This does not work, though no errors are produced; console.logging within the method also produces nothing. I followed the model outlined here in the docs. Any help resolving this issue would be appreciated!
import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
class Timer extends React.Component {
constructor(props) {
super(props)
this.state = {
minutes: 25,
seconds: 0,
pomodoro: props.pomodoro,
}
}
componentDidMount() {
this.interval = setInterval(this.decrement, 1000)
}
reset = () => {
this.setState(prevState => ({
minutes: (prevState.pomodoro ? 5 : 25),
seconds: 0,
}))
}
decrement = () => {
if ((this.state.minutes+this.state.seconds)===0){
this.setState(prevState => ({
pomodoro: !prevState.pomodoro,
minutes: (prevState.pomodoro ? 25 : 5),
}))
} else{
if (this.props.start){
if (this.state.seconds===0){
this.setState(prevState => ({
minutes: prevState.minutes - 1,
seconds: 59,
}))
} else{
this.setState(prevState => ({
seconds: prevState.seconds - 1
}))
}
}
}
}
render() {
return (
<Text style={styles.time}>
{("0"+this.state.minutes).slice(-2)}:
{("0"+this.state.seconds).slice(-2)}
{this.props.start}
</Text>
);
}
}
export default class App extends React.Component {
constructor (props) {
super(props)
this.state = {
start: false,
pomodoro: false,
buttonText: "Start"
}
}
toggleStart = () => this.setState(prevState => ({
start: !prevState.start,
buttonText: (prevState.start ? "Start" : "Stop")
}))
resetTimer = () => {
this.toggleStart()
this._timer.reset()
}
render() {
return (
<View style={styles.container}>
<Timer
start={this.state.start}
pomodoro={this.state.pomodoro}
reset={this.state.reset}
toggleStart={() => this.toggleStart}
ref={component => { this._timer = component; }}
/>
<View style={styles.buttonRow}>
<Button
title={this.state.buttonText}
onPress={this.toggleStart}>
</Button>
<Button
title="Reset"
onPress={this.resetTimer}>
Timer.resetTime
</Button>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
time: {
fontSize: 70,
color: 'tomato',
alignItems: 'center',
justifyContent: 'center',
},
buttonRow: {
flexDirection: 'row'
},
});

Usually, you shouldn't have to call a childs function in the parent. When you find yourself in this situation, you might be overcomplicating your component structure. Why not move the reset button into the Timer component?
import React from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
class Timer extends React.Component {
constructor(props) {
super(props)
this.state = {
minutes: 25,
seconds: 0,
pomodoro: props.pomodoro,
}
}
componentDidMount() {
this.interval = setInterval(this.decrement, 1000)
}
reset = () => this.setState(prevState({
minutes: (prevState.pomodoro ? 5 : 25),
seconds: 0,
}))
decrement = () => {
if ((this.state.minutes+this.state.seconds)===0){
this.setState(prevState => ({
pomodoro: !prevState.pomodoro,
minutes: (prevState.pomodoro ? 25 : 5),
}))
} else{
if (this.props.start){
if (this.state.seconds===0){
this.setState(prevState => ({
minutes: prevState.minutes - 1,
seconds: 59,
}))
} else{
this.setState(prevState => ({
seconds: prevState.seconds - 1
}))
}
}
}
}
render() {
return (
<View>
<Text style={styles.time}>
{("0"+this.state.minutes).slice(-2)}:
{("0"+this.state.seconds).slice(-2)}
{this.props.start}
</Text>
<View style={styles.buttonRow}>
<Button
title={this.props.buttonText}
onPress={this.props.toggleStart}>
</Button>
<Button
title="Reset"
onPress={this.reset}>
Timer.resetTime
</Button>
</View>
</View>
);
}
}
export default class App extends React.Component {
constructor (props) {
super(props)
this.state = {
start: false,
pomodoro: false,
buttonText: "Start"
}
}
toggleStart = () => this.setState(prevState => ({
start: !prevState.start,
buttonText: (prevState.start ? "Start" : "Stop")
}))
render() {
return (
<View style={styles.container}>
<Timer
start={this.state.start}
pomodoro={this.state.pomodoro}
toggleStart={this.toggleStart}
buttonText={this.state.buttonText}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
time: {
fontSize: 70,
color: 'tomato',
alignItems: 'center',
justifyContent: 'center',
},
buttonRow: {
flexDirection: 'row'
},
});

Related

React native the method onChangeText does not work

I am trying to create a function called onHandleWork that takes an input , then I take an input from onChangeText if that input === this.state.count I want that the clock resets however when the clock reach this.state.count nothing happens what are your thoughts?
import React from 'react'
import {Button, StyleSheet,Text,View,TextInput} from 'react-native';
const styles = StyleSheet.create({
appContainer: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
counts: {
fontSize: 48,
}
})
export default class App extends React.Component {
constructor() {
super()
this.state = {
count: 0,
start: true,
}
}
render() {
return(
<View style={styles.appContainer}>
<Text style={styles.counts}>{this.state.count}</Text>
<Button onPress={() => this.onButtonStop()} title="stop"/>
<Button onPress={() => this.onButtonStart()} title="start"/>
<Button onPress={() => this.onClear()}title="clear"/>
<TextInput onChangeText={((value) => this.onHandleWork(value) )} keyboardType="phone-pad" placeholder="time of work"/>
<TextInput placeholder="enter your rest time"/>
</View>
)
}
componentDidMount(){
setInterval(this.inc,1000)
}
onButtonStop(){
clearInterval(this.state.count);
this.setState({start: false})
}
onButtonStart(){
clearInterval(this.state.count);
this.setState({start: true})
}
onClear(){
this.setState({
count:0,
start: false,
})
}
onHandleWork(value) {
if (this.state.count === value) {
clearInterval(this.state.count);
this.setState({
count: 0,
start: false,
})
}
}
inc = () => {
if (this.state.start) {
this.setState(PrevStep => ({
count: PrevStep.count + 1
}))
}
}
}
here is an Image that shows the problem
enter image description here
I want to make it work when the clock is running

Passing props to child doesn't make him update - React Native

I am writing because I can't figure why one of the two childs Graph.js won't update after I udpate the state of the parent Data.js (throught a "lift up" via the second child Bouton.js).
I feel giga dumb and it's been now hours, I'm desperate...
I am trying to display charts with buttons above to choose a period of time for the chart (day, week, month). Clicking the button can change the state of the parent but I can't make the child Graph to update. I know I am doing something wrong.
Parent: Data.js
export default class Data extends React.Component {
constructor(props) {
super(props);
this.state = { periode: "Jour" };
}
handleClick(p) {
this.setState({
periode: p
});
}
render() {
console.log(this.state);
return (
<View>
<Boutons
onClick={res => this.handleClick(res)}
cursor={this.state.periode}
/>
<Graph periode={this.state.periode} dataType="temp" />
<Graph periode={this.state.periode} dataType="press" />
</View>
);
}
}
Child 1 (everything seems fine)
export default class Boutons extends React.Component {
constructor(props) {
super(props);
}
_getNextEntryTime() {
var d = new Date();
var h = d.getHours();
var m = d.getMinutes();
var res;
if (m >= 30) {
res = (h + 1).toString() + ":00";
} else {
res = h.toString() + ":30";
}
return res;
}
//Gestion de la selection des boutons
_boutonStyle(periode) {
if (this.props.cursor == periode) {
return {
// backgroundColor: "#9c9c9c",
borderBottomWidth: 3,
borderColor: "#728FB5",
width: Dimensions.get("window").width / 3 - 10,
height: 30,
alignItems: "center",
justifyContent: "center"
};
} else {
return {
backgroundColor: "#dfdfdf",
width: Dimensions.get("window").width / 3 - 10,
height: 30,
borderRadius: 2,
alignItems: "center",
justifyContent: "center"
};
}
}
_textStyle(periode) {
if (this.props.cursor == periode) {
return { color: "#728FB5" };
} else {
return { color: "black" };
}
}
render() {
return (
<View style={styles.container}>
<View style={styles.container_top}>
<View style={styles.rect}>
<Text style={styles.text_top}>
Prochain relevé: {`\n`}
<Text style={styles.numbers}>{this._getNextEntryTime()}</Text>
</Text>
</View>
<Single />
</View>
<View style={styles.container_buttons}>
<TouchableOpacity
style={this._boutonStyle("Jour")}
onPress={() => this.props.onClick("Jour")}
>
<Text style={this._textStyle("Jour")}>Jour</Text>
</TouchableOpacity>
<TouchableOpacity
style={this._boutonStyle("Semaine")}
onPress={() => this.props.onClick("Semaine")}
>
<Text style={this._textStyle("Semaine")}>Semaine</Text>
</TouchableOpacity>
<TouchableOpacity
style={this._boutonStyle("Mois")}
onPress={() => this.props.onClick("Mois")}
>
<Text style={this._textStyle("Mois")}>Mois</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
Graph.js Child 2 that won't update, nothing is happening
class Graph extends React.Component {
constructor(props) {
super(props);
this.state = { isLoading: true, data: [], format_const: null };
}
// Chargement de la page et recherche des entrys
componentDidMount() {
const entrys = getEntry(this.props.periode);
entrys.then(reponse => {
reponse.map(donnee => {
this.setState({
data: this.state.data.concat(donnee[this.props.dataType])
});
});
this.setState({
format_const: Config.CHART[this.props.dataType],
isLoading: false
});
});
}
// Affichage du loading
_displayLoading() {
if (this.state.isLoading) {
return (
<View style={styles.loading_container}>
<ActivityIndicator size="large" />
</View>
);
}
}
_displayChart() {
return (
<LineChart
data={{
datasets: [
{
data: this.state.data,
strokeWidth: 2 // optional
}
],
legend: [this.state.format_const["label"]]
}}
width={Dimensions.get("window").width - 10} // from react-native
height={220}
withInnerLines={false}
yAxisSuffix={this.state.format_const["alert"]}
onDataPointClick={({ value, dataset, getColor }) =>
Alert.alert(`${value}` + this.state.format_const["alert"])
}
chartConfig={{
backgroundGradientFrom: this.state.format_const["color"],
backgroundGradientTo: this.state.format_const["color"],
decimalPlaces: 0, // optional, defaults to 2dp
color: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`,
style: {
borderRadius: 16
},
propsForDots: {
r: "2"
}
}}
bezier
style={{
marginVertical: 10,
borderRadius: 16
}}
/>
);
}
render() {
if (!this.state.isLoading) {
return <View>{this._displayChart()}</View>;
} else {
return <View>{this._displayLoading()}</View>;
}
}
}
It appears that your only use of the prop periode is in the componentDidMount method of Graph. So at mount time, Graph reads what the prop is, and then sets the state, which is used in the Graph render method. But when the parent component changes its state, and the new value for this.state.periode is passed as a prop to Graph, Graph doesnt doesn't necessarily know what to do with this updated information. So you'll need to use a componentDidUpdate statement to read new props coming in from the parent's state:
componentDidUpdate(prevProps){
if (prevProps.periode !== this.props.periode){
const entrys = getEntry(this.props.periode);
entrys.then(reponse => {
reponse.map(donnee => {
this.setState({
data: this.state.data.concat(donnee[this.props.dataType])
});
});
});
}
}
I'm assuming you want the same thing to happen in `componentDidMount` as is happening in `componentDidUpdate`, but you may need to change the code within `componentDidUpdate` to whatever you need.

(Native React)Cant get counter to work for both items individually in Native react

im trying to get my counter to work for both my items individually, but each time i touch increase button on 1 item it increases for both of them
this what the app looks like appimage
CounterApp.js file ///////////////////////////////////////////////////////////////////////////
import {Image} from 'react-native';
import React, { Component } from "react";
import {
View,
Text,
StyleSheet,
TouchableOpacity
} from "react-native";
import { connect } from 'react-redux'
class CounterApp extends Component {
render() {
return (
<View style={styles.container}>
<View style={{ flexDirection: 'row', width: 200, justifyContent: 'space-around' }}>
<TouchableOpacity onPress={() => this.props.increaseCounter()}>
<Text style={{ fontSize: 20 }}>Increase</Text>
</TouchableOpacity>
<Text style={{ fontSize: 20 }}>{this.props.counter}</Text>
<TouchableOpacity onPress={() => this.props.decreaseCounter()}>
<Text style={{ fontSize: 20 }}>Decrease</Text>
</TouchableOpacity>
</View>
<Image source={{uri: 'https://facebook.github.io/react/logo-og.png'}}
style={{width: 200, height: 200}} />
<View style={{ flexDirection: 'row', width: 200, justifyContent: 'space-around' }}>
<TouchableOpacity onPress={() => this.props.increaseCounter2()}>
<Text style={{ fontSize: 20 }}>Increase</Text>
</TouchableOpacity>
<Text style={{ fontSize: 20 }}>{this.props.counter}</Text>
<TouchableOpacity onPress={() => this.props.decreaseCounter2()}>
<Text style={{ fontSize: 20 }}>Decrease</Text>
</TouchableOpacity>
</View>
<Image source={{uri: 'https://facebook.github.io/react/logo-og.png'}}
style={{width: 200, height: 200}} />
</View>
);
}
}
function mapStateToProps(state) {
return {
counter: state.counter,
}
}
function mapDispatchToProps(dispatch) {
return {
increaseCounter: () => dispatch({ type: 'INCREASE_COUNTER' }),
decreaseCounter: () => dispatch({ type: 'DECREASE_COUNTER' }),
increaseCounter2: () => dispatch({ type: 'INCREASE_COUNTER' }),
decreaseCounter2: () => dispatch({ type: 'DECREASE_COUNTER' }),
}
}
export default connect(mapStateToProps, mapDispatchToProps )(CounterApp)
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}
});
app.js file //////////////////////////////////////////////////////////////////////
import {Image} from 'react-native';
import React, { Component } from "react";
import {
View,
Text,
StyleSheet,
TouchableOpacity
} from "react-native";
import { createStore } from 'redux'
import CounterApp from './src/CounterApp'
import { Provider } from 'react-redux'
/**
* Store - holds our state - THERE IS ONLY ONE STATE
* Action - State can be modified using actions - SIMPLE OBJECTS
* Dispatcher - Action needs to be sent by someone - known as dispatching an action
* Reducer - receives the action and modifies the state to give us a new state
* - pure functions
* - only mandatory argument is the 'type'
* Subscriber - listens for state change to update the ui
*/
const initialState = {
counter: 0
}
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREASE_COUNTER':
return { counter: state.counter + 1 }
case 'DECREASE_COUNTER':
return { counter: state.counter - 1 }
}
return state
}
const store = createStore(reducer)
class App extends Component {
render() {
return (
<Provider store={store}>
<CounterApp />
</Provider>
);
}
}
export default App
// export default App;
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}
});
they both are mapping from the same source of props
instead of :
increaseCounter: () => dispatch({ type: 'INCREASE_COUNTER' }),
decreaseCounter: () => dispatch({ type: 'DECREASE_COUNTER' }),
increaseCounter2: () => dispatch({ type: 'INCREASE_COUNTER' }),
decreaseCounter2: () => dispatch({ type: 'DECREASE_COUNTER' }),
you should have this structure :
increaseCounter: () => dispatch({ type: 'INCREASE_COUNTER' }),
decreaseCounter: () => dispatch({ type: 'DECREASE_COUNTER' }),
increaseCounter2: () => dispatch({ type: 'INCREASE_COUNTER2' }),
decreaseCounter2: () => dispatch({ type: 'DECREASE_COUNTER2' }),
next change this :
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREASE_COUNTER':
return { counter: state.counter1 + 1 }
case 'DECREASE_COUNTER':
return { counter: state.counter1 - 1 }
case 'DECREASE_COUNTER2':
return { counter: state.counter2 + 1 }
case 'INCREASE_COUNTER2':
return { counter: state.counter2 - 1 }
}
return state
}
next this :
const initialState = {
counter1: 0,
counter2: 0,
}
next assign specific prop to each counter :
return {
counter1: state.counter1,
counter2: state.counter2,
}
<Text style={{ fontSize: 20 }}>{this.props.counter1}</Text>
<Text style={{ fontSize: 20 }}>{this.props.counter2}</Text>

Executing multiple functions OnPress

I'm trying to execute a function and navigate to the next screen using React-navigation and creating an Axios post
I've already tried combining both function's but It doesn't seem to execute the createOrder function
If I run the createOrder function alone it does work
onPress={
() => {
this.createOrder
this.props.navigation.navigate('Cart', {
order : this.state.order
});
}
}
import React from 'react';
import {
View,
StyleSheet,
Text,
Image,
TouchableOpacity
} from 'react-native';
//Redux
import { connect } from 'react-redux';
import { addItemToCart, removeItem } from '../../actions/ProductActionCreators';
//Products
import Products from '../../components/products/Products';
// Api Url
import ApiUrl from '../../helpers/ApiUrl'
//UI LIBRARY
import { Input, Button } from 'react-native-elements';
import {LinearGradient} from "../../components/LinearGradient";
import { ButtonGroup } from 'react-native-elements';
import Icon from "react-native-vector-icons/Ionicons";
//AXIOS
import axios from 'axios';
export class ProductsListView extends React.Component {
constructor(props) {
super(props);
const { rows } = this.props.navigation.state.params;
const arrays = Object.values( {rows});
this.state = {
arrays,
filteredProducts: arrays,
selectedIndex: 2
};
this.updateIndex = this.updateIndex.bind(this)
}
createOrder () {
axios.post( ApiUrl + 'api/order/post', {
code: "4f",
status: "waiting",
user_name: "salman",
user_id: 1,
club_id: 1,
})
.then(response => {
this.setState({
order: response.data,
});
console.log('created order',this.state.order)
})
.catch(function (error) {
console.log('error',error);
})
}
updateIndex (selectedIndex) {
this.setState({selectedIndex})
}
filterAll(){
}
filterStrong(){
this.setState({
arrays: this.state.arrays[0].products.filter(item => item.type == "strong" )
})
console.log(this.state.arrays)
}
filterNormal(){
}
render() {
const component1 = () => <Icon
name="ios-star"
size={15}
color="gold"
/>
const component2 = () => <Icon
name="ios-beer"
size={15}
color="gold"
onPress={() => this.filterStrong}
/>
const component3 = () => <Icon
name="ios-wine"
size={15}
color="gold"
/>
const buttons = [{ element: component1 }, { element: component2 }, { element: component3 }]
const { selectedIndex } = this.state
return (
<View style={styles.container} >
<Image
style={styles.imageCard}
source={
{
uri:
this.state.arrays[0].image
}
}
/>
<Text style={styles.title} >
{this.state.arrays[0].name}
</Text>
<Products
products={this.state.arrays[0].products}
addItemToCart={this.props.addItemToCart}
removeItem={this.props.removeItem}
/>
<View style={{
justifyContent:'center',
width: '100%',
padding: 50,
paddingTop:20,
}}>
<Button
title="Go to my cart"
containerStyle={{ flex: -1 }}
buttonStyle={styles.signUpButton}
linearGradientProps={{
colors: ['#dd016b', '#dd016b'],
start: [1, 0],
end: [0.2, 0],
}}
ViewComponent={LinearGradient}
titleStyle={styles.signUpButtonText}
// onPress={this.createOrder}
onPress={
() => {
this.createOrder
this.props.navigation.navigate('Cart', {order : this.state.order});
}
}
/>
</View>
</View>
)
}
}
const mapDispatchToProps = {
addItemToCart,
removeItem
}
export default connect(null, mapDispatchToProps) (ProductsListView);
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
width:'100%',
backgroundColor: 'black',
},
signUpButtonText: {
// fontFamily: 'bold',
fontSize: 13,
},
signUpButton: {
width: 250,
borderRadius: 50,
height: 45,
},
title: {
color:'white',
fontSize:32,
height: 100,
position: 'relative',
backgroundColor: '#00000054',
width: "100%",
textAlign: 'center',
paddingTop: 30,
},
imageCard:{
height:100,
width:'100%',
position: "absolute",
top: 0,
backgroundColor: 'white'
},
button: {
padding: 5,
borderRadius: 25,
margin: 5,
backgroundColor: '#DD016B',
color: 'white',
alignItems: 'center',
justifyContent: 'center',
},
})
I'm trying to navigate to the next screen with the data from I get from my Axios post.
You are not calling the createOrder function.
Try this:
<Button
title="Go to my cart"
containerStyle={{ flex: -1 }}
buttonStyle={styles.signUpButton}
linearGradientProps={{
colors: ["#dd016b", "#dd016b"],
start: [1, 0],
end: [0.2, 0]
}}
ViewComponent={LinearGradient}
titleStyle={styles.signUpButtonText}
// onPress={this.createOrder}
onPress={this.onGoToMyCartPressed}
/>;
And onGoToMyCartPressed would look like:
onGoToMyCartPressed = () => {
this.createOrder(); // <- Call the function
this.props.navigation.navigate("Cart", { order: this.state.order });
};
And, if you want to navigate after the order has been created, then, have your createOrder return the promise, and you can chain off of it in the onGoToMyCartPressed
Like so:
createOrder() {
// Return the promise from here
return axios.post( ApiUrl + 'api/order/post', {
code: "4f",
status: "waiting",
user_name: "salman",
user_id: 1,
club_id: 1,
}).then(response => {
this.setState({
order: response.data,
});
console.log('created order',this.state.order)
}).catch(function (error) {
console.log('error',error);
})
}
And modify the onGoToMyCartPressed to use the promise returned.
onGoToMyCartPressed = () => {
// CHange the page once the order has been craeted
this.createOrder().then(() => {
this.props.navigation.navigate("Cart", { order: this.state.order });
})
};

How do I make a list (FlatList) automatically scroll through the elements using Animated?

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]);
};

Resources