Basically, I'm trying to setup a Flatlist in which multiple values can be selected.
My problem is with the styling of elements, when clicked the first time they don't get highlighted but when clicked the 2nd time they get highlighted.
FlatList Code
renderRow = ({item}) => (
<RowItem data={item} />
)
data = [
{
value: 'element1'
},
{
value: 'element2'
}
]
render(){
return (
<FlatList
data={this.data}
renderItem={this.renderRow}
keyExtractor={(item, index) => item + index}/>
)
}
RowItem Code
export default class RowItem extends React.Component {
state = {
isElementActive: false,
}
highlightElement = () => {
this.setState(prevState => ({
isElementActive: !prevState.isElementActive
}))
}
render() {
return (
<TouchableOpacity
activeOpacity={0.7}
onPress={this.highlightElement}
style={[styles.container, this.state.isElementActive ? styles.activeContainer : styles.inactiveContainer]}>
<Text>{this.props.data.value}</Text>
</TouchableOpacity>
)
}
}
const styles = Stylesheet.create({
container: {
height: 100,
width: 300,
backgroundColor: 'red',
},
activeContainer: {
opacity: 0.7,
},
inactiveContainer: {
opacity: 1,
}
});
When clicking on the element once, the value of the isElementActive returns true (when I console.log it) but the styling "activeContainer" does not apply. However, when I click it again, the styling applies even though the value of isElementActive then becomes false.
By default the value starts off as false, and they are not highlighted (i.e. have opacity of 1) and for this reason I'm kind of confused when clicked the first time, the value of isElementActive changes but the styling does not apply.
I was able to make it work with setOpacityTo after the setState.
Working example: https://snack.expo.io/SJNSKQPIB
import React from 'react';
import {TouchableOpacity, FlatList, StyleSheet, Text} from 'react-native';
type State = {
active: boolean;
};
type Props = {
value: string;
};
class RowItem extends React.Component<Props, State> {
state = {
active: null,
};
ref = null;
highlightElement = () => {
this.setState(
prevState => ({
active: !prevState.active,
}),
() => {
this.ref.setOpacityTo(this.state.active ? 0.7 : 1);
},
);
};
render() {
return (
<TouchableOpacity
ref={ref => (this.ref = ref)}
onPress={this.highlightElement}
style={[styles.container]}>
<Text>{this.props.value}</Text>
</TouchableOpacity>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
height: 100,
backgroundColor: 'red',
},
});
export default class App extends React.Component {
data = [
{
value: 'element1',
},
{
value: 'element2',
},
];
render() {
return (
<FlatList
keyExtractor={(_, index) => index.toString()}
data={this.data}
renderItem={({item}) => <RowItem value={item.value} />}
/>
);
}
}
Related
I have a flatlist of items. When I pass the item to the renderItem component everything works perfectly fine. Then when I pass that exact same item to a child within my component responsible for rendering items there are problems.
Normally it works perfectly fine but if there are multiple list items and the one above it gets deleted, it loses proper functionality and becomes very buggy. I think the issue is because an item assumes a previous item's index for whatever reason the grandchild still thinks it is that item rather than it was a different item moving into that index.
My flatlist:
<FlatList
data={this.props.items}
extraData={this.props.items}
keyExtractor={(item, index) => index.toString()}
renderItem={({ item }) => {
return (
<TodoItem
todoItem={item}
/>
);
}}
/>
Then in the TodoItem this is how I pass item to the grandchild:
class TodoItem extends Component {
render() {
const todoItem = this.props.todoItem;
return (
<View>
<ItemSwipeRow
item={todoItem}
completeItem={this.props.deleteTodo}
>
Then in the itemSwipeRow this is how I receive the prop
import React, { Component } from 'react';
import { Animated, StyleSheet, View } from 'react-native';
import { RectButton } from 'react-native-gesture-handler';
import Swipeable from 'react-native-gesture-handler/Swipeable';
import { Ionicons, MaterialCommunityIcons } from '#expo/vector-icons';
import { connect } from 'react-redux';
import { openNotesModal, openDateModal } from '../actions';
const AnimatedIcon = Animated.createAnimatedComponent(Ionicons);
class ItemSwipeRow extends Component {
constructor(props) {
super(props);
this.item = props.item;
}
renderLeftActions = (progress, dragX) => {
const trans = dragX.interpolate({
inputRange: [0, 50, 100, 101],
outputRange: [-20, 0, 0, 1],
});
return (
<RectButton style={styles.leftAction}>
<AnimatedIcon
name='md-checkmark'
color='#28313b'
size={45}
style={[
styles.actionText,
{
transform: [{ translateX: trans }],
},
]}
/>
</RectButton>
);
};
renderRightAction = (action, name, color, x, progress) => {
const trans = progress.interpolate({
inputRange: [0, 1],
outputRange: [x, 0],
});
const pressHandler = () => {
action(this.item);
};
return (
<Animated.View style={{ flex: 1, transform: [{ translateX: trans }] }}>
<RectButton
style={[styles.rightAction, { backgroundColor: color }]}
onPress={pressHandler}
>
<MaterialCommunityIcons
name={name}
size={35}
color='#28313b'
/>
</RectButton>
</Animated.View>
);
};
renderRightActions = progress => (
<View style={styles.rightSwipeButtons}>
{this.renderRightAction(
this.props.openDateModal, 'calendar', '#B8B8F3', 192, progress
)}
{this.renderRightAction(
this.props.openNotesModal, 'pencil', '#F0A202', 128, progress
)}
{this.renderRightAction(
this.props.openDateModal, 'bell', '#db5461', 64, progress
)}
</View>
);
updateRef = ref => {
this.currItem = ref;
};
close = () => {
if (this.currItem !== null) { this.currItem.close(); }
};
onSwipeableLeftOpen = () => {
this.props.completeItem();
this.close();
}
onSwipeableRightWillOpen = () => {
console.log(this.item.text); //tried passing in item here but didn't
} //work, instead of console logging on call it did every item # start
// then none when it was suppose to log it.
render() {
const { children } = this.props;
const { item } = this.props;
return (
<Swipeable
ref={this.updateRef}
friction={2}
leftThreshold={70}
rightThreshold={40}
renderLeftActions={this.renderLeftActions}
renderRightActions={this.renderRightActions}
onSwipeableLeftOpen={this.onSwipeableLeftOpen}
onSwipeableRightWillOpen={this.onSwipeableRightWillOpen}
>
{children}
</Swipeable>
);
}
}
const styles = StyleSheet.create({
leftAction: {
flex: 1,
backgroundColor: '#82ff9e',
justifyContent: 'center',
},
rightAction: {
alignItems: 'center',
flex: 1,
justifyContent: 'center',
},
rightSwipeButtons: {
width: 192,
flexDirection: 'row'
}
});
export default connect(null, { openNotesModal, openDateModal })(ItemSwipeRow);
My console logs prove the right item isn't always being rendered. The deleting of the item works properly however if an item is deleted and there is something under it the item that was under it assumes it was the item that was just deleted.
Any help with this would be greatly appreciated and I can provide more code if needed.
the code for deleting an item:
action that's sent on left swipe–
export const removeTodo = (item) => {
return {
type: REMOVE_TODO,
id: item.id
};
};
reducer action goes to–
case REMOVE_TODO: {
const newList = state.todos.filter(item => item.id !== action.id);
for (let i = 0, newId = 0; i < newList.length; i++, newId++) {
newList[i].id = newId;
}
return {
...state,
todos: newList
};
}
You could use destructuring assignment to copy the object, rather than refering to the same object:
const { todoItem } = this.props
further reading: https://medium.com/#junshengpierre/javascript-primitive-values-object-references-361cfc1cbfb0
I have a FlatList with data fetched from an API. There's a button on the screen that fetches data which is changed and sets the state, but the flat list doesn't refresh. I tried setting the extraData as per docs, but it didn't help. Here are the full code and snack.
If you click the Toggle List button, the alert correctly shows the new data, but the list isn't updated.
import React, {useState} from 'react';
import { SafeAreaView, View, FlatList, StyleSheet, Text, Button } from 'react-native';
import Constants from 'expo-constants';
const DATA2 = [
{
id: 0,
title: 'D2-0'
},
{
id: 1,
title: 'D2-1'
},
{
id: 2,
title: 'D2-2'
},
];
const DATA1 = [
{
id: 0,
title: 'D1-0'
},
{
id: 1,
title: 'D1-1'
},
{
id: 2,
title: 'D1-2'
},
];
export default function App(props) {
const [data, setData]=useState(DATA1);
const [dataUsed, setDataUsed]=useState(1);
return (
<SafeAreaView style={styles.container}>
<FlatList
data={data}
renderItem={({ item }) => <MyComponent data={item} /> }
keyExtractor={item => item.id}
extraData={data}
/>
<Button title="Toggle Data" onPress={() => {
let newData = dataUsed === 1 ? DATA2 : DATA1;
setDataUsed(dataUsed === 1 ? 2: 1);
alert(JSON.stringify(newData));
setData(newData);
}} />
</SafeAreaView>
);
}
class MyComponent extends React.Component {
constructor(props){
super(props);
this.state = {data: props.data};
}
render() {
return <Text>{this.state.data.title}</Text>;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: Constants.statusBarHeight,
padding: 50
}
});
<div data-snack-id="SkDYPf4wH" data-snack-platform="web" data-snack-preview="true" data-snack-theme="light" style="overflow:hidden;background:#fafafa;border:1px solid rgba(0,0,0,.08);border-radius:4px;height:505px;width:100%"></div>
<script async src="https://snack.expo.io/embed.js"></script>
I think you missed the reflection of the state.
Once you set the state, it could be reflected next time.
Do I think you need to use the Hook.
Please try to use it.
import React, {useState, useEffect} from 'react';
... ... ...
export default function App(props) {
const [data, setData]=useState(DATA1);
const [dataUsed, setDataUsed]=useState(1);
useEffect(()=>{
let newData = dataUsed === 1 ? DATA2 : DATA1;
setData(newData);
},[setData, dataUsed]);
return (
<SafeAreaView style={styles.container}>
<FlatList
data={data}
renderItem={({ item }) => <MyComponent data={item} /> }
keyExtractor={item => item.id}
extraData={data}
/>
<Button title="Toggle Data" onPress={() => {
setDataUsed(dataUsed === 1 ? 2: 1);
alert(JSON.stringify(newData));
}} />
</SafeAreaView>
);
}
And for the component.
class MyComponent extends React.Component {
constructor(props){
super(props);
this.state = {data: props.data};
}
componentDidUpdate(prevProps){
if( prevProps.data !== this.props.data ){
this.setData();
}
}
setData = ()=>{
this.setState({
data: this.props.data,
});
}
Render () {
return <Text>{this.state.data.title}</Text>;
}
Change MyComponent code like this
class MyComponent extends React.Component {
render() {
return <Text>{this.props.data.title}</Text>;
}
}
Your constructor code is actually useless.
I've a Custom Component for Tab View through which I can make dynamic tabs and below is the code for it.
TabView is the custom to make those custom tabs and Selected is the component for a single Tab.
How can I send a prop from TabView component to Selected component?
I know how to send props in a regular scenario, but I don't know to send one in this case.
I made this component from the below link:
https://medium.com/#abdelhalim.ahmed95/how-to-create-scrollable-and-dynamic-top-tabsbar-using-react-navigation-17ca52acbc51
export class TabView extends Component {
Tabs = navigation => {
let tabs=['A', 'B', 'C', 'D','E','F','G','H'];
tabs = tabs.reduce((val, tab) => {
val[tab] = {
screen: Selected
}
return val
}, {})
const bottomTabs = createMaterialTopTabNavigator(
{
...tabs
},
{
tabBarOptions: {
style: {
backgroundColor: Themes.colors.FC_PRIMARY,
},
indicatorStyle:{
height: 2,
backgroundColor: Themes.colors.TC_PRIMARY_LIGHT,
},
}
}
)
const Tabs = createAppContainer(bottomTabs);
return <Tabs />
}
render() {
const { navigation } = this.props;
return (
<View style={STYLES.tabView}>
{this.Tabs(navigation)}
</View>
);
}
}
export class Selected extends Component {
constructor(props){
super(props);
this.state = {
screen: '',
screenType: this.props.type
}
}
static navigationOptions = ({ navigation }) => {
return({
tabBarLabel: ({ focused }) => (
<View>
<View style={STYLES.secondaryTabLabel}>
<H3
type={ focused ? "light" : "disabled" }
text={navigation.state.routeName}
/>
</View>
</View>
)
})
};
screenIs = payload => {
this.setState({ screen: payload.state.routeName })
}
render() {
const { navigation } = this.props;
return (
<View style={{flex: 1}}>
<NavigationEvents onWillFocus={this.screenIs} />
<Text>{this.state.screen}</Text>
</View>
);
}
}
Use the following code,
val[tab] = {
screen: () => (<Selected val={val}/>) //in case if you want to send val as props
}
So the final code of yours will be,
export class TabView extends Component {
Tabs = navigation => {
let tabs=['A', 'B', 'C', 'D','E','F','G','H'];
tabs = tabs.reduce((val, tab) => {
val[tab] = {
screen: () => (<Selected val={val}/>), // for props
navigationOptions: {
title: 'Shows' // send anything here to get in navigationOptions
},
}
return val
}, {})
const bottomTabs = createMaterialTopTabNavigator(
{
...tabs
},
{
tabBarOptions: {
style: {
backgroundColor: Themes.colors.FC_PRIMARY,
},
indicatorStyle:{
height: 2,
backgroundColor: Themes.colors.TC_PRIMARY_LIGHT,
},
}
}
)
const Tabs = createAppContainer(bottomTabs);
return <Tabs />
}
render() {
const { navigation } = this.props;
return (
<View style={STYLES.tabView}>
{this.Tabs(navigation)}
</View>
);
}
}
export class Selected extends Component {
constructor(props){
super(props);
this.state = {
screen: '',
screenType: this.props.type
}
}
static navigationOptions = ({ navigation, navigationOptions }) => {
return({
tabBarLabel: ({ focused }) => (
<View>
<View style={STYLES.secondaryTabLabel}>
<H3
type={ focused ? "light" : "disabled" }
text={navigationOptions.title} // use here
/>
</View>
</View>
)
})
};
screenIs = payload => {
this.setState({ screen: payload.state.routeName })
}
render() {
const { navigation } = this.props;
return (
<View style={{flex: 1}}>
<NavigationEvents onWillFocus={this.screenIs} />
<Text>{this.state.screen}</Text>
</View>
);
}
}
I'm a complete beginner at react native and now I'm stuck with an update problem. I'm using react-native-paper and typescript.
In my app, I want to press a button and then the text field should change its text.
The problem is somehow at the button, or the called function because in the console log its always "before: true after:true" or "before: false after:false",
but what I expected is "before: true after: false" or vice-versa
I've also got a second Text View which is not shown at all.
Maybe someone can tell me what I am doing wrong?
My index.js
import * as React from 'react';
import { AppRegistry } from 'react-native';
import { Provider as PaperProvider } from 'react-native-paper';
import App from './src/App';
export default function Main() {
return (
<PaperProvider>
<App />
</PaperProvider>
);
}
AppRegistry.registerComponent('main', () => Main);
My MyNavigation.tsx (which contains currently my whole app).
import * as React from 'react';
import { BottomNavigation, Text, Avatar, Button, Card, Title, Paragraph, Banner } from 'react-native-paper';
import { View, Image, WebView } from 'react-native';
export default class MyNavi extends React.Component {
constructor(props, context) {
super(props, context);
this.setUnConnected = this.setUnConnected.bind(this);
}
state = {
index: 0,
routes: [
{ key: 'viewcamera', title: 'View', icon: 'remove-red-eye' },
{ key: 'viewsettings', title: 'Settings', icon: 'settings' },
{ key: 'viewhelp', title: 'How-To', icon: 'help' },
],
visible: true,
connected: false,
};
_handleIndexChange = index => { this.setState({ index }); }
setUnConnected = function () {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected });
console.log("after: " + this.state.connected);
console.log("--------------");
};
ViewRoute = () =>
<View style={{ flex: 1, marginTop: 40 }}>
{/* --------- This text field does not get updated -------------*/}
<Text>connected: {this.state.connected ? 'true' : 'false'}</Text>
{/* --------- This text field is not shown at all ------------*/}
<Text>
{this.state.connected}
</Text>
<Button icon="camera" mode="contained" onPress={this.setUnConnected}>
Press me
</Button>
<View style={{ height: 400, width: 400 }}>
<WebView
source={{ uri: 'https://stackoverflow.com/' }}
style={{ marginTop: 40 }}
// onLoad={() => this.setState({ connected: true })}
/>
</View>
</View>
SettingsRoute = () => <Text>Settings</Text>;
HelpRoute = () => <View></View>
_renderScene = BottomNavigation.SceneMap({
viewcamera: this.ViewRoute,
viewsettings: this.SettingsRoute,
viewhelp: this.HelpRoute,
});
render() {
return (
<BottomNavigation
navigationState={this.state}
onIndexChange={this._handleIndexChange}
renderScene={this._renderScene}
/>
);
}
}
State Updates May Be Asynchronous React Documentation
So You cannot test your console.log in this way. Use the callback function of setState method as follows,
this.setState({ connected: !this.state.connected }, () => {
console.log("after: " + this.state.connected);
console.log("--------------");
});
Hope this will help you.
Your issue is here,
setUnConnected = function () {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected });
console.log("after: " + this.state.connected);
console.log("--------------");
};
setState is async function and it takes some time to update the state. It does not block execution of next statements. So you will always get the previous state only for both the console.log.
To get the actual updated value, you should use callback in setState.
setUnConnected = function () {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected }, () => console.log("after: " + this.state.connected); ); //Now you will get updated value.
console.log("--------------");
};
For this,
{/* --------- This text field is not shown at all ------------*/}
<Text>
{this.state.connected}
</Text>
this.state.connected is either true or false (Boolean) which will never be shown on screen. If you still want to see the value on screen, then you can use this hack.
<Text>
{this.state.connected.toString()}
</Text>
Update
From the docs,
Pages are lazily rendered, which means that a page will be rendered the first time you navigate to it. After initial render, all the pages stay rendered to preserve their state.
Instead of this,
_renderScene = BottomNavigation.SceneMap({
viewcamera: this.ViewRoute,
viewsettings: this.SettingsRoute,
viewhelp: this.HelpRoute,
});
You should use this version of renderScene,
_renderScene = ({ route, jumpTo }) => {
switch (route.key) {
case 'viewcamera':
return <ViewRoute jumpTo={jumpTo} connected={this.state.connected} setUnConnected={this.setUnConnected}/>; //Here you can pass data from state and function to your component
case 'viewsettings':
return <SettingsRoute jumpTo={jumpTo} />;
case 'viewhelp':
return <HelpRoute jumpTo={jumpTo} />;
}
}
Your complete code should look like this,
import * as React from 'react';
import { BottomNavigation, Text, Avatar, Button, Card, Title, Paragraph, Banner } from 'react-native-paper';
import { View, Image, WebView } from 'react-native';
const ViewRoute = (props) =>
<View style={{ flex: 1, marginTop: 40 }}>
{/* --------- This text field does not get updated -------------*/}
<Text>connected: {props.connected ? 'true' : 'false'}</Text>
{/* --------- This text field is not shown at all ------------*/}
<Text>
{props.connected.toString()}
</Text>
<Button icon="camera" mode="contained" onPress={props.setUnConnected}>
Press me
</Button>
<View style={{ height: 400, width: 400 }}>
<WebView
source={{ uri: 'https://stackoverflow.com/' }}
style={{ marginTop: 40 }}
// onLoad={() => this.setState({ connected: true })}
/>
</View>
</View>
const SettingsRoute = () => <Text>Settings</Text>;
const HelpRoute = () => <View></View>
export default class MyNavi extends React.Component {
constructor(props, context) {
super(props, context);
this.setUnConnected = this.setUnConnected.bind(this);
}
state = {
index: 0,
routes: [
{ key: 'viewcamera', title: 'View', icon: 'remove-red-eye' },
{ key: 'viewsettings', title: 'Settings', icon: 'settings' },
{ key: 'viewhelp', title: 'How-To', icon: 'help' },
],
visible: true,
connected: false,
};
_handleIndexChange = index => { this.setState({ index }); }
setUnConnected = function() {
console.log("before: " + this.state.connected);
this.setState({ connected: !this.state.connected });
console.log("after: " + this.state.connected);
console.log("--------------");
};
_renderScene = ({ route, jumpTo }) => {
switch (route.key) {
case 'viewcamera':
return <ViewRoute jumpTo={jumpTo} connected={this.state.connected} setUnConnected={this.setUnConnected}/>; //Here you can pass data from state and function to your component
case 'viewsettings':
return <SettingsRoute jumpTo={jumpTo} />;
case 'viewhelp':
return <HelpRoute jumpTo={jumpTo} />;
}
}
render() {
return (
<BottomNavigation
navigationState={this.state}
onIndexChange={this._handleIndexChange}
renderScene={this._renderScene}
/>
);
}
}
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]);
};