Auto scrollToIndex FlatList doesn't work in long lists - react-native

I have an implementation for autoscrolling through FlatList by index. When the index is greater than 40-45 the autoscrolling doesn't work. On 1-39 indexes - autoscroll works good.
Btw: when I tried to make scroll on press the same behavior, only indexes up to 39 work.
My part of the code:
const [dataUpdated, setDataUpdated] = useState(false);
const [itemIn, setItemIn] = useState(-1);
useEffect(() => {
if (selectedItem.id.length > 0) {
setItemIn(items.map(object => object.itemId).indexOf(selectedItem.id));
} else {
setItemIn(0);
}
}, [items, selectedItem.id]);
const listRef = useRef<FlatList>(null);
useEffect(() => {
let screenListener = true;
setTimeout(() => {
if (itemIn >= 0 && screenListener) {
setDataUpdated(!dataUpdated);
listRef?.current?.scrollToIndex({
animated: true,
index: itemIn,
});
}
}, 500);
return () => {
screenListener = false;
};
}, [dataUpdated, itemIn]);
<ItemsList
data={items}
onItemPressed={onItemPressed}
selectedItem={selectedItem}
setSelectedItem={setSelectedItem}
refIt={listRef}
onScrollToIndexFailed={() => {}}
extraData={dataUpdated}
/>

I found the way to do it - FlashList from Shopify perfectly handles such scoll

Related

UI not rendering while useEffect fires

I'm trying to get dynamic next page with Object.entries[currentTaskIndex], everything works great and i'm getting the task data based on their indexes , but useEffects not rendering UI when dependency changes, actually it's rendering same task as before ,so this mean when useState fire, currentTaskIndex state changes to 2 but it's stillshowing index 1 items, but when i refresh the page it's showing index 2 items,so the question is that, how can i show index 2 items while useEffects fire?
This is what i have:
const [isCorrect, setIsCorrect] = useState(false);
const [currentTaskIndex, setCurrentTaskIndex] = useState(0);
useEffect(() => {
const checkIfCorrect = newData.map((data) => {
....
});
}, [!isCorrect ? newItems : !newItems]);
// Filtered task
useEffect(() => {
const filterObject = newData.filter((data) => {
return data.map((dataItems) => {
return dataItems.map((tasks, i) => {
return Object.entries(tasks)[currentTaskIndex].map((task, i) => {
setFilteredTasks(task);
});
});
});
});
}, [newData]);
useEffect(() => {
isCorrect && setCurrentTaskIndex((prev) => prev + 1);
setNewItems([]);
setIsCorrect(false);
const filterObject = newData.filter((data) => {
return data.map((dataItems) => {
return dataItems.map((tasks, i) => {
return Object.entries(tasks)[currentTaskIndex].map((task, i) => {
setFilteredTasks((currTask) => [...currTask, task]);
});
});
});
});
}, [isCorrect]);
{filteredTasks.map((taskItems, i) => (
<View key={i}>
{taskItems.en?.map((enItems, i) => (
.....
))}
</View>
React components automatically re-render whenever there is a change in their state or props. A simple update of the state, from anywhere in the code, causes all the User Interface (UI) elements to be re-rendered automatically.
So, by adding just currentTaskIndex on dependency array your useEffect also re-render.
when you just put newData on dependency array its could not update UI because newData is not a state or Props.
Yes this is your solution:
// Filtered task
useEffect(() => {
const filterObject = newData.filter((data) => {
return data.map((dataItems) => {
return dataItems.map((tasks, i) => {
return Object.entries(tasks)[currentTaskIndex].map((task, i) => {
setFilteredTasks(task);
});
});
});
});
}, [newData, currentTaskIndex]); <------
An finally fixed!
by just adding CurrentTaskIndex as dependency to Filtered task useEffects
// Filtered task
useEffect(() => {
const filterObject = newData.filter((data) => {
return data.map((dataItems) => {
return dataItems.map((tasks, i) => {
return Object.entries(tasks)[currentTaskIndex].map((task, i) => {
setFilteredTasks(task);
});
});
});
});
}, [newData, currentTaskIndex]); <------

React Native navigation with react hook form

In my React Native project I want to add edit, go back and save button in the header of my form screen.
To manage my form, I use react-hook-form.
The header come from react-navigation and I use the navigation.setOptions function to add my buttons.
This work well for the edit or go back button but save button don't fire handleSubmit function provide by react-hook-form.
If I put the same button in another place in my page, that work well.
const MemberScreen = (navigation: any) => {
const { control, handleSubmit, formState: { errors } } = useForm();
const [editMode, setEditMode] = useState(false);
useLayoutEffect(() => {
let title = "";
let headerRight: any;
let headerLeft: any;
if (editMode) {
title = "edit form"
headerRight = () => (<TouchableOpacity onPress={() => { save() }}><MaterialCommunityIcons name="content-save" color={AppConst.primaryColor} size={32} style={styles.iconItem} /></TouchableOpacity>)
headerLeft = () => (<TouchableOpacity onPress={() => { toggleEdit() }}><MaterialCommunityIcons name="close" color={AppConst.primaryColor} size={32} style={styles.iconItem} /></TouchableOpacity>)
} else {
headerRight = () => (<TouchableOpacity onPress={() => { toggleEdit() }}><MaterialCommunityIcons name="pencil" color={AppConst.primaryColor} size={32} style={styles.iconItem} /></TouchableOpacity>)
headerLeft = () => headerLeftWithBack(navigation);
}
navigation.navigation.setOptions({ title: title, headerRight: headerRight, headerLeft: headerLeft });
}, [navigation, editMode])
const toggleEdit = () => {
setEditMode(!editMode);
}
const save = () => {
handleSubmit((data) => {
onSubmit(data)
})
}
const onSubmit = async (data: any) => {
let body = { id: member.id, ...data }
// ...
}
return // ...
}
Do you have any idea or solution to fix this problem ?
This fix my problem because i miss parentheses :
const save = () => {
handleSubmit((data) => {
onSubmit(data)
})()
}

React Native Sound change speed time

I'm using react-native-sound library to play audio in my app. I would like to add option to control speed time of listening.
in my AudioPlayer component I have:
const [music, setMusic] = useState(null)
const [isPlaying, setPlaying] = useState(false)
const [duration, setDuration] = useState(0)
const [currentTime, setCurrentTime] = useState(0)
useEffect(() => {
const audio = new Sound(decodeURI(props.track.url), null, (err) => {
if (err) {
return
}
})
Sound.setActive(true)
Sound.setCategory('Playback', true)
Sound.setMode('Default')
setMusic(audio)
return function cleanup() {
Sound.setActive(false)
audio.release()
}
}, [props.track])
useEffect(() => {
const interval = setInterval(() => {
if (music && duration <= 0) {
setDuration(music.getDuration())
}
if (music && isPlaying) {
music.getCurrentTime((seconds: number) => {
setCurrentTime(seconds)
})
}
}, 100)
return () => clearInterval(interval)
})
const onPlayPausePress = async () => {
if (music.isPlaying()) {
music.pause()
setPlaying(false)
} else {
music.play(success => {
setPlaying(false)
// setCurrentTime(0)
// music.setCurrentTime((0))
})
setPlaying(true)
}
}
const manageSpeedTime = (speed) => {
if (music.isPlaying()) {
music.pause()
setPlaying(false)
music.setSpeed(speed)
music.getCurrentTime((seconds: number) => {
setCurrentTime(seconds)
})
music.setCurrentTime(currentTime)
await music.play(success => {
setPlaying(false)
})
setPlaying(true)
}
}
And later in my code:
<Slider
style={{ width: '55%', height: 40}}
minimumValue={0}
maximumValue={duration}
minimumTrackTintColor={props.audioStylesProps.sliderRunColor}
maximumTrackTintColor={props.audioStylesProps.sliderStartColor}
thumbTintColor={props.audioStylesProps.sliderCircleColor}
value={currentTime}
onValueChange={onSliderChange}
/>
<View style={{flexDirection: "column"}}>
<Button title={"1x"} onPress={() => {manageSpeedTime(1)}} color={"red"}/>
<Button title={"1.25x"} onPress={() => {manageSpeedTime(1.25)}} color={"red"}/>
<Button title={"2x"} onPress={() => {manageSpeedTime(2)}} color={"red"}/>
</View>
My problem is, when I speed time x2 it works fine, but when I want come back to normal speed, I got delay. For example, Im listening with x2 speed and at 40seconds I change speed to 1x, instead of starting from 40sec, my slider goes back to around 34-36 seconds and starts playing music at 40. My idea was to stop music when I change speed time, set speed, set current time and start playing, but looks like music.seetCurrentTime(currentTime) in manageSpeedTime doesn't work. Could somebody help my with solving this issue?

How to scroll FlatList to some index immediately after scrolling?

How to scroll FlatList component to some index/children after dragging the FlatList?
For Example:
As we can see in Youtube/TikTok stories, when we drag the screen the next video appears immidiatly after it. So, I am implementing it with FlatList, if we drag the item below then FlatList should move to the above item/index. So, what I am doing is that storing the currently displayed index and on onScrollEndDrag prop I am checking the position of Y, and accordingly run scrollToIndex function, but it's not working.
Reason
Because while scrolling after drag, FlatList ignores the scrollToIndex function.
Is their anyone to help me out of it???
import React from 'react';
import { View, Text, StyleSheet, AppState, FlatList, Animated, Dimensions } from 'react-native';
import fetchDataFromDirectory from '../data/fetchDataFromWhatsApp';
import PlayerVideo from '../components/VideoPlayer';
import Image from '../components/Image';
const { width, height } = Dimensions.get('window');
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
class VideoScreen extends React.Component {
state = {
pdfInfo: [], //[{id, name, path},...]
appState: '',
viewableIndex: 0
}
fetchData = async () => {
const data = await fetchDataFromDirectory('videos');
this.setState({ pdfInfo: data.pdfInfo });
}
componentDidUpdate(prevProps, prevState) {
if (this.state.pdfInfo.length > this.dataLength) { //We are seeing if we need to scroll to top or not
this.dataLength = this.state.pdfInfo.length;
try {
this.list.scrollToIndex({ animated: true, index: 0, viewPosition: 0 })
} catch (err) {
}
}
}
handleAppStateChange = (nextAppState) => {
//the app from background to front
if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') {
this.fetchData();
}
//save the appState
this.setState({ appState: nextAppState });
}
componentDidMount() {
this.videoHeight = height;
this.dataLength = 0;
this.fetchData();
AppState.addEventListener('change', this.handleAppStateChange);
}
onViewableItemsChanged = ({ viewableItems, changed }) => {
// console.log("Visible items are", viewableItems);
// console.log("Changed in this iteration", changed);
try {
this.setState({ viewableIndex: viewableItems[0]['index'] })
} catch (err) {
}
}
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChange)
}
render() {
return <AnimatedFlatList
onLayout={(e) => {
const { height } = e.nativeEvent.layout;
this.videoHeight = height;
}}
// onResponderRelease={e => {console.log(e.nativeEvent.pageY)}}
// onResponderRelease={(e) => console.log(e.nativeEvent.)}
// onScrollBeginDrag
// snapToAlignment={'top'}
// onMoveShouldSetResponderCapture={(e) => {e.nativeEvent.}}
// decelerationRate={'fast'}
decelerationRate={'fast'}
scrollEventThrottle={16}
// onScroll={(e) => console.log('+++++++++++++++++',Object.keys(e), e.nativeEvent)}
onScrollEndDrag={(e) => {
// this.list.setNativeProps({ scrollEnabled: false })
console.log(e.nativeEvent)
if (e.nativeEvent.velocity.y > 0.1) {
console.log('go to above')
this.list.scrollToIndex({animated: true, index: this.state.viewableIndex - 1, viewPosition: 0})
} else if (e.nativeEvent.velocity.y < -0.9){
console.log('go to below')
this.list.scrollToIndex({animated: true, index: this.state.viewableIndex + 1, viewPosition: 0})
}
// if (e.nativeEvent.velocity.y < 0.1000 && e.nativeEvent.velocity.y >= 0) {
// this.list.scrollToIndex({animated: true, index: this.state.viewableIndex, viewPosition: 0})
// }
// else if (e.nativeEvent.velocity.y < -0.1000 && e.nativeEvent.velocity.y < 0) {
// this.list.scrollToIndex({animated: true, index: this.state.viewableIndex, viewPosition: 0})
// }
// this.list.setNativeProps({ scrollEnabled: true })
console.log('h1')
}}
viewabilityConfig={{
// itemVisiblePercentThreshold: 90,
viewAreaCoveragePercentThreshold: 60
}}
// extraData={this.state.viewableIndex}
onViewableItemsChanged={this.onViewableItemsChanged}
// scr
contentContainerStyle={styles.screen}
data={this.state.pdfInfo}
keyExtractor={item => item.id}
ref={ref => this.list = ref}
renderItem={({ item, index }) => {
// console.log(index)
return <PlayerVideo
source={item.path}
refList={this.list}
height={this.videoHeight}
index={index}
isViewable={this.state.viewableIndex == index ? true : false} />
}}
</View>
}}
/>
}
}
export default VideoScreen;
const styles = StyleSheet.create({
screen: {
backgroundColor: '#111212',
// flex: 1
}
})
First add a ref flatListRef to the flatlist:
<Flatlist
ref={(ref) => this.flatListRef = ref}
data={data}
keyExtractor={keyExtractor}
renderItem={renderItem}
onScrollBeginDrag={onBeginScroll}/>
/>
Then define the onScrollBeginDrag function as below to scroll to a specific index:
onBeginScroll = () => {
this.flatListRef._listRef._scrollRef.scrollToIndex({ animating: true, index: [YOUR_INDEX_HERE] });
}
yes you should access the element _listRef then _scrollRef then call the scrollToIndex
react-native 0.64.1
react 17.0.2

How to make progress bar with minute interval?

library used react-native progress
for making an progress bar
import * as Progress from 'react-native-progress';
<Progress.Bar progress={0.3} width={200} />
use this library react-native progress
initial value set to 0
const [progressBarValue, setProgressBarValue] = useState(0)
useEffect(() => {
const intervalId = setInterval(() => {
setProgressBarValue((prev) => {
if (prev >= 1.2) {
setCongratulations('Congratulations')
clearInterval(intervalId);
return 1.120;
} else {
return prev + 0.01;
}
});
}, 1000);
return () => clearInterval(intervalId);
}, []);
render code
<Progress.Bar progress={progressBarValue} width={width-50} color={'rgba(221,196,145, 1)'}/>