How to detect an end of a drag event in React-Native Android - scrollview

I am using a scrollview, and want to detect when the user ends the drag event.
I have put a console.log for onScrollEndDrag and it doesnot give any console output, i.e. onScrollEndDrag is not being detected. Is there any way to get around this?
Please, see the code below.
var Main = React.createClass({
getInitialState: function() {
return {
LoadedPageState: false,
menuJson: [],
pageToLoad: "landingPage",
mainlogopic: 'mainlogo',
profilepic: 'profile',
notificationpic: 'notification',
bagpic: 'bag',
morepic: 'more',
moveend: 0,
count: 1,
frmDrag: false,
}
},
horScrollViewInstance: {
scrollTo: () => {}
},
control: {
onscroll: () => {}
},
touchStart: {
ontouchstart: () => {}
},
componentWillMount: function() {
var menuJson = require('./data/data.json');
this.setState({
menuJson: menuJson
});
},
currentPageAction: function(pageToLoad) {
this.setState({
LoadedPageState: true,
pageToLoad: pageToLoad
});
},
currentPageBackAction: function() {
this.setState({
LoadedPageState: false,
});
},
currentPageView: function() {
var currentPage = null;
if (this.state.pageToLoad == 'landingPage') {
currentPage = (<LandingPage/>);
} else if (this.state.pageToLoad == 'profilePage') {
currentPage = (<ProfilePage/>);
}
// <LoadedPage currentPageBackAction={this.currentPageBackAction}
// LoadedPageActive={this.state.LoadedPageState} />
return (<View>
<View style={{flex:1}}>
{currentPage}
</View>
</View>);
},
gotoLandingPage: function(isFrmDrag) {
this.horScrollViewInstance.scrollTo(0, 0);
this.setState({
pageToLoad: "landingPage",
frmDrag: isFrmDrag,
});
this.chkPage();
},
gotoProfilePage: function(isFrmDrag) {
this.setState({
pageToLoad: "profilePage",
frmDrag: isFrmDrag,
});
this.horScrollViewInstance.scrollTo(0, width);
this.chkPage();
},
gotoNotificationPage: function(isFrmDrag) {
this.setState({
pageToLoad: "notificatonPage",
frmDrag: isFrmDrag,
});
this.horScrollViewInstance.scrollTo(0, 2 * width);
this.chkPage();
},
gotoAddToBagPage: function(isFrmDrag) {
this.setState({
pageToLoad: "addToBagPage",
frmDrag: isFrmDrag,
});
this.horScrollViewInstance.scrollTo(0, 3 * width);
this.chkPage();
},
gotoMorePage: function(isFrmDrag) {
this.setState({
pageToLoad: "morePage",
frmDrag: isFrmDrag,
});
this.horScrollViewInstance.scrollTo(0, 4 * width);
this.chkPage();
},
restoreDefaultIcon: function() {
this.setState({
mainlogopic: 'mainlogochange',
profilepic: 'profile',
notificationpic: 'notification',
bagpic: 'bag',
morepic: 'more'
});
},
chkPage: function() {
this.restoreDefaultIcon();
if (this.state.pageToLoad == "landingPage") {
this.setState({
mainlogopic: 'mainlogo'
});
} else if (this.state.pageToLoad == "profilePage") {
this.setState({
profilepic: 'profilechange'
});
} else if (this.state.pageToLoad == "notificatonPage") {
this.setState({
notificationpic: 'notificationchange'
});
} else if (this.state.pageToLoad == "addToBagPage") {
this.setState({
bagpic: 'bagchange'
});
} else if (this.state.pageToLoad == "morePage") {
this.setState({
morepic: 'morechange'
});
}
},
_scrollToRef: function(instance) {
this.horScrollViewInstance.scrollTo = instance.scrollTo;
this.control.onscroll = instance.onscroll;
this.touchStart.ontouchstart = instance.ontouchstart;
},
onscroll: function(event: Object) {
var movestart;
movestart = event.nativeEvent.contentOffset.x;
// this.setState({ movestart: event.nativeEvent.contentOffset.x});
console.log(movestart);
if (this.state.frmDrag == true) {
if (movestart > 3.5 * width) {
this.gotoMorePage(false);
} else if (movestart > 2.5 * width) {
this.gotoAddToBagPage(false);
}
if (movestart > 1.5 * width) {
this.gotoNotificationPage(false);
} else if (movestart > 0.7 * width) {
this.gotoProfilePage(false);
} else if (movestart > 0) {
this.gotoLandingPage(false);
}
}
},
ontouchstart: function(event: Object) {
console.log("hello");
this.setState({
frmDrag: true
});
},
render: function() {
var navigationView = (
<View style={{flex: 1, backgroundColor: '#fff'}}>
<Text style={{margin: 10, fontSize: 15, textAlign: 'left'}}>Im in the Drawer!</Text>
</View>
);
return (
<DrawerLayoutAndroid drawerWidth={300} drawerPosition={DrawerLayoutAndroid.positions.Left} renderNavigationView={() => navigationView}>
<View style={styles.bodyWrapr}>
<View style={[{flex:1}]}>
<ScrollView
ref={this._scrollToRef}
onScroll={this.onscroll}
onTouchStart = {this.ontouchstart}
onScrollEndDrag={() => console.log('onScrollEndDrag')}
onScrollBeginDrag={() => console.log('onScrollBeginDrag')}
onScrollEndDrag={() => console.log('onScrollEndDrag')}
onMomentumScrollBegin={() => console.log('onMomentumScrollBegin')}
onMomentumScrollEnd={() => console.log('onMomentumScrollEnd')}
horizontal={true}
pagingEnabled={true}
showsHorizontalScrollIndicator={true}
snapToInterval={width}
snapToAlignment={'start'}
contentContainerStyle ={{flex:1}}>
<View style={[{flex:1, flexDirection:'row'}]}>
<View style={{flex:1}}>
<ScrollView showsVerticalScrollIndicator = {true}>
<LandingPage/>
</ScrollView>
</View>
<View style={{flex:1}}>
<ScrollView showsVerticalScrollIndicator = {true}>
<ProfilePage/>
</ScrollView>
</View>
<View style={{flex:1}}>
<ScrollView showsVerticalScrollIndicator = {true}>
<NotificatonPage/>
</ScrollView>
</View>
<View style={{flex:1}}>
<ScrollView showsVerticalScrollIndicator = {true}>
<AddToBagPage/>
</ScrollView>
</View>
<View style={{flex:1}}>
<ScrollView showsVerticalScrollIndicator = {true}>
<MorePage/>
</ScrollView>
</View>
</View>
</ScrollView>
</View>
</View>
</DrawerLayoutAndroid>
);
},
});

It seems to work for me.
If you paste these in as props, do you get any console output?
onTouchStart={() => console.log('onTouchStart')}
onTouchMove={() => console.log('onTouchMove')}
onTouchEnd={() => console.log('onTouchEnd')}
onScrollBeginDrag={() => console.log('onScrollBeginDrag')}
onScrollEndDrag={() => console.log('onScrollEndDrag')}
onMomentumScrollBegin={() => console.log('onMomentumScrollBegin')}
onMomentumScrollEnd={() => console.log('onMomentumScrollEnd')}
I get logs in roughly this order (at the end)-
onTouchEnd
onScrollEndDrag
onMomentumScrollEnd

there was a bug in the prev version of react-native. Has been solved in v.0.18.0

Related

Convert react-native class component to functional component with react-hooks

I'm trying convert "expo-react-native audio player and video example" to funtional component.
This's code base:
expo/playlist-example
and this's my code that I'm trying convert to funtional component:
import React, { useState } from "react";
import {
Dimensions,
Image,
StyleSheet,
Text,
TouchableHighlight,
View
} from "react-native";
import { Asset } from "expo-asset";
import {
Audio,
InterruptionModeAndroid,
InterruptionModeIOS,
ResizeMode,
Video
} from "expo-av";
import * as Font from "expo-font";
import Slider from "#react-native-community/slider";
import { MaterialIcons } from "#expo/vector-icons";
import { PLAYLIST } from "../data/PlayList";
import { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED } from "react/cjs/react.development";
const ICON_STOP_BUTTON = () => (<Image source={(require("../assets/images/stop_button.png"))} style={{ width: 22, height: 22 }} />);
const ICON_FORWARD_BUTTON = () => (<Image source={(require("../assets/images/forward_button.png"))} style={{ width: 33, height: 25 }} />);
const ICON_BACK_BUTTON = () => (<Image source={(require("../assets/images/back_button.png"))} style={{ width: 33, height: 25 }} />);
const ICON_LOOP_ALL_BUTTON = require("../assets/images/loop_all_button.png")
const ICON_LOOP_ONE_BUTTON = require("../assets/images/loop_one_button.png")
const ICON_THROUGH_EARPIECE = "speaker-phone";
const ICON_THROUGH_SPEAKER = "speaker";
const LOOPING_TYPE_ALL = 0;
const LOOPING_TYPE_ONE = 1;
const LOOPING_TYPE_ICONS = { 0: ICON_LOOP_ALL_BUTTON, 1: ICON_LOOP_ONE_BUTTON };
const { width: DEVICE_WIDTH, height: DEVICE_HEIGHT } = Dimensions.get("window");
const BACKGROUND_COLOR = "#FFF8ED";
const DISABLED_OPACITY = 0.5;
const FONT_SIZE = 14;
const LOADING_STRING = "... loading ...";
const BUFFERING_STRING = "...buffering...";
const RATE_SCALE = 3.0;
const VIDEO_CONTAINER_HEIGHT = (DEVICE_HEIGHT * 2.0) / 5.0 - FONT_SIZE * 2;
export default function MusicPlayerScreen() {
const [index, setIndex] = useState(0)
const [isSeeking, setIsSeeking] = useState(false)
const [shouldPlayAtEndOfSeek, setShouldPlayAtEndOfSeek] = useState(false)
const [playbackInstance, setPlaybackInstance] = useState(null)
const [state, setState] = useState({
showVideo: false,
playbackInstanceName: LOADING_STRING,
loopingType: LOOPING_TYPE_ALL,
muted: false,
playbackInstancePosition: null,
playbackInstanceDuration: null,
shouldPlay: false,
isPlaying: false,
isBuffering: false,
isLoading: true,
fontLoaded: false,
shouldCorrectPitch: true,
volume: 1.0,
rate: 1.0,
videoWidth: DEVICE_WIDTH,
videoHeight: VIDEO_CONTAINER_HEIGHT,
poster: false,
useNativeControls: false,
fullscreen: false,
throughEarpiece: false
})
componentDidMount = () => {
Audio.setAudioModeAsync({
allowsRecordingIOS: false,
staysActiveInBackground: false,
interruptionModeIOS: InterruptionModeIOS.DoNotMix,
playsInSilentModeIOS: true,
shouldDuckAndroid: true,
interruptionModeAndroid: InterruptionModeAndroid.DoNotMix,
playThroughEarpieceAndroid: false
});
(async () => {
await Font.loadAsync({
...MaterialIcons.font,
"cutive-mono-regular": require("../assets/fonts/CutiveMono-Regular.ttf")
});
setState({ fontLoaded: true });
})();
}
async function _loadNewPlaybackInstance(playing) {
if (playbackInstance != null) {
await playbackInstance.unloadAsync();
// playbackInstance.setOnPlaybackStatusUpdate(null);
setPlaybackInstance(null);
}
const source = { uri: PLAYLIST[index].uri };
const [initialStatus, setInitialStatus] = useState({
shouldPlay: playing,
rate: state.rate,
shouldCorrectPitch: state.shouldCorrectPitch,
volume: state.volume,
isMuted: state.muted,
isLooping: state.loopingType === LOOPING_TYPE_ONE
// // UNCOMMENT THIS TO TEST THE OLD androidImplementation:
// androidImplementation: 'MediaPlayer',
});
if (PLAYLIST[index].isVideo) {
await _video.loadAsync(source, initialStatus);
// _video.onPlaybackStatusUpdate(_onPlaybackStatusUpdate);
setPlaybackInstance = _video;
const status = await _video.getStatusAsync();
} else {
const { sound, status } = await Audio.Sound.createAsync(
source,
initialStatus,
_onPlaybackStatusUpdate
);
setPlaybackInstance = sound;
}
_updateScreenForLoading(false);
}
_mountVideo = component => {
_video = component;
_loadNewPlaybackInstance(false);
};
_updateScreenForLoading = (isLoading) => {
if (isLoading) {
setState({
showVideo: false,
isPlaying: false,
playbackInstanceName: LOADING_STRING,
playbackInstanceDuration: null,
playbackInstancePosition: null,
isLoading: true
});
} else {
setState({
playbackInstanceName: PLAYLIST[index].name,
showVideo: PLAYLIST[index].isVideo,
isLoading: false
});
}
};
_onPlaybackStatusUpdate = status => {
if (status.isLoaded) {
setState({
playbackInstancePosition: status.positionMillis,
playbackInstanceDuration: status.durationMillis,
shouldPlay: status.shouldPlay,
isPlaying: status.isPlaying,
isBuffering: status.isBuffering,
rate: status.rate,
muted: status.isMuted,
volume: status.volume,
loopingType: status.isLooping ? LOOPING_TYPE_ONE : LOOPING_TYPE_ALL,
shouldCorrectPitch: status.shouldCorrectPitch
});
if (status.didJustFinish && !status.isLooping) {
_advanceIndex(true);
_updatePlaybackInstanceForIndex(true);
}
} else {
if (status.error) {
console.log(`FATAL PLAYER ERROR: ${status.error}`);
}
}
};
_onLoadStart = () => {
console.log(`ON LOAD START`);
};
_onLoad = status => {
console.log(`ON LOAD : ${JSON.stringify(status)}`);
};
_onError = error => {
console.log(`ON ERROR : ${error}`);
};
_onReadyForDisplay = event => {
const widestHeight =
(DEVICE_WIDTH * event.naturalSize.height) / event.naturalSize.width;
if (widestHeight > VIDEO_CONTAINER_HEIGHT) {
setState({
videoWidth:
(VIDEO_CONTAINER_HEIGHT * event.naturalSize.width) /
event.naturalSize.height,
videoHeight: VIDEO_CONTAINER_HEIGHT
});
} else {
setState({
videoWidth: DEVICE_WIDTH,
videoHeight:
(DEVICE_WIDTH * event.naturalSize.height) / event.naturalSize.width
});
}
};
_onFullscreenUpdate = event => {
console.log(
`FULLSCREEN UPDATE : ${JSON.stringify(event.fullscreenUpdate)}`
);
};
_advanceIndex = (forward) => {
setIndex((index + (forward ? 1 : PLAYLIST.length - 1)) % PLAYLIST.length);
}
async function _updatePlaybackInstanceForIndex(playing) {
_updateScreenForLoading(true);
setState({
videoWidth: DEVICE_WIDTH,
videoHeight: VIDEO_CONTAINER_HEIGHT
});
_loadNewPlaybackInstance(playing);
}
_onPlayPausePressed = () => {
if (playbackInstance != null) {
if (state.isPlaying) {
playbackInstance.pauseAsync();
} else {
playbackInstance.playAsync();
}
}
};
_onStopPressed = () => {
if (playbackInstance != null) {
playbackInstance.stopAsync();
}
};
_onForwardPressed = () => {
if (playbackInstance != null) {
_advanceIndex(true);
_updatePlaybackInstanceForIndex(state.shouldPlay);
}
};
_onBackPressed = () => {
if (playbackInstance != null) {
_advanceIndex(false);
_updatePlaybackInstanceForIndex(state.shouldPlay);
}
};
_onMutePressed = () => {
if (playbackInstance != null) {
playbackInstance.setIsMutedAsync(!state.muted);
}
};
_onLoopPressed = () => {
if (playbackInstance != null) {
playbackInstance.setIsLoopingAsync(
state.loopingType !== LOOPING_TYPE_ONE
);
}
};
_onVolumeSliderValueChange = value => {
if (playbackInstance != null) {
playbackInstance.setVolumeAsync(value);
}
};
_trySetRate = async (rate, shouldCorrectPitch) => {
if (playbackInstance != null) {
try {
await playbackInstance.setRateAsync(rate, shouldCorrectPitch);
} catch (error) {
// Rate changing could not be performed, possibly because the client's Android API is too old.
}
}
};
_onRateSliderSlidingComplete = async value => {
_trySetRate(value * RATE_SCALE, state.shouldCorrectPitch);
};
_onPitchCorrectionPressed = async value => {
_trySetRate(state.rate, !state.shouldCorrectPitch);
};
_onSeekSliderValueChange = value => {
if (playbackInstance != null && !isSeeking) {
setIsSeeking(true);
shouldPlayAtEndOfSeek = state.shouldPlay;
playbackInstance.pauseAsync();
}
};
_onSeekSliderSlidingComplete = async value => {
if (playbackInstance != null) {
setIsSeeking(false);
const seekPosition = value * state.playbackInstanceDuration;
if (shouldPlayAtEndOfSeek) {
playbackInstance.playFromPositionAsync(seekPosition);
} else {
playbackInstance.setPositionAsync(seekPosition);
}
}
};
_getSeekSliderPosition = () => {
if (
playbackInstance != null &&
state.playbackInstancePosition != null &&
state.playbackInstanceDuration != null
) {
return (
state.playbackInstancePosition /
state.playbackInstanceDuration
);
}
return 0;
}
_getMMSSFromMillis = (millis) => {
const totalSeconds = millis / 1000;
const seconds = Math.floor(totalSeconds % 60);
const minutes = Math.floor(totalSeconds / 60);
const padWithZero = number => {
const string = number.toString();
if (number < 10) {
return "0" + string;
}
return string;
};
return padWithZero(minutes) + ":" + padWithZero(seconds);
}
_getTimestamp = () => {
if (
playbackInstance != null &&
state.playbackInstancePosition != null &&
state.playbackInstanceDuration != null
) {
return `${_getMMSSFromMillis(
state.playbackInstancePosition
)} / ${_getMMSSFromMillis(state.playbackInstanceDuration)}`;
}
return "";
}
_onPosterPressed = () => {
setState({ poster: !state.poster });
};
_onUseNativeControlsPressed = () => {
setState({ useNativeControls: !state.useNativeControls });
};
_onFullscreenPressed = () => {
try {
_video.presentFullscreenPlayer();
} catch (error) {
console.log(error.toString());
}
};
_onSpeakerPressed = () => {
setState(
state => {
return { throughEarpiece: !state.throughEarpiece };
},
() =>
Audio.setAudioModeAsync({
allowsRecordingIOS: false,
interruptionModeIOS: InterruptionModeIOS.DoNotMix,
playsInSilentModeIOS: true,
shouldDuckAndroid: true,
interruptionModeAndroid: InterruptionModeAndroid.DoNotMix,
playThroughEarpieceAndroid: state.throughEarpiece
})
);
};
return !state.fontLoaded ? (
<View style={styles.emptyContainer} />
) : (
<View style={styles.container}>
<View />
<View style={styles.nameContainer}>
<Text style={[styles.text, { fontFamily: "cutive-mono-regular" }]}>
{state.playbackInstanceName}
</Text>
</View>
<View style={styles.space} />
<View style={styles.videoContainer}>
<Video
ref={_mountVideo}
style={[
styles.video,
{
opacity: state.showVideo ? 1.0 : 0.0,
width: state.videoWidth,
height: state.videoHeight
}
]}
resizeMode={ResizeMode.CONTAIN}
onPlaybackStatusUpdate={_onPlaybackStatusUpdate}
onLoadStart={_onLoadStart}
onLoad={_onLoad}
onError={_onError}
onFullscreenUpdate={_onFullscreenUpdate}
onReadyForDisplay={_onReadyForDisplay}
useNativeControls={state.useNativeControls}
/>
</View>
<View
style={[
styles.playbackContainer,
{
opacity: state.isLoading ? DISABLED_OPACITY : 1.0
}
]}
>
<Slider
style={styles.playbackSlider}
trackImage={() => { require("../assets/images/track_1.png") }}
thumbImage={() => { require("../assets/images/thumb_1.png") }}
value={_getSeekSliderPosition()}
onValueChange={_onSeekSliderValueChange}
onSlidingComplete={_onSeekSliderSlidingComplete}
disabled={state.isLoading}
/>
<View style={styles.timestampRow}>
<Text
style={[
styles.text,
styles.buffering,
{ fontFamily: "cutive-mono-regular" }
]}
>
{state.isBuffering ? BUFFERING_STRING : ""}
</Text>
<Text
style={[
styles.text,
styles.timestamp,
{ fontFamily: "cutive-mono-regular" }
]}
>
{_getTimestamp()}
</Text>
</View>
</View>
<View
style={[
styles.buttonsContainerBase,
styles.buttonsContainerTopRow,
{
opacity: state.isLoading ? DISABLED_OPACITY : 1.0
}
]}
>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={_onBackPressed}
disabled={state.isLoading}
>
{ICON_BACK_BUTTON}
</TouchableHighlight>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={_onPlayPausePressed}
disabled={state.isLoading}
>
<Image
style={styles.button}
source={require(
state.isPlaying
? "../assets/images/pause_button.png"
: "../assets/images/play_button.png"
)}
/>
</TouchableHighlight>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={_onStopPressed}
disabled={state.isLoading}
>
{ICON_STOP_BUTTON}
</TouchableHighlight>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={_onForwardPressed}
disabled={state.isLoading}
>
{ICON_FORWARD_BUTTON}
</TouchableHighlight>
</View>
<View
style={[
styles.buttonsContainerBase,
styles.buttonsContainerMiddleRow
]}
>
<View style={styles.volumeContainer}>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={_onMutePressed}
>
<Image
style={styles.button}
source={require(
state.muted
? "../assets/images/muted_button.png"
: "../assets/images/unmuted_button.png"
)}
/>
</TouchableHighlight>
<Slider
style={styles.volumeSlider}
trackImage={() => { require("../assets/images/track_1.png") }}
thumbImage={() => { require("../assets/images/thumb_2.png") }}
value={1}
onValueChange={_onVolumeSliderValueChange}
/>
</View>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={_onLoopPressed}
>
<Image
style={styles.button}
source={LOOPING_TYPE_ICONS[state.loopingType]}
/>
</TouchableHighlight>
</View>
<View
style={[
styles.buttonsContainerBase,
styles.buttonsContainerBottomRow
]}
>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={() => _trySetRate(1.0, state.shouldCorrectPitch)}
>
<View style={styles.button}>
<Text
style={[styles.text, { fontFamily: "cutive-mono-regular" }]}
>
Rate:
</Text>
</View>
</TouchableHighlight>
<Slider
style={styles.rateSlider}
trackImage={() => { require("../assets/images/track_1.png") }}
thumbImage={() => { require("../assets/images/thumb_1.png") }}
value={state.rate / RATE_SCALE}
onSlidingComplete={_onRateSliderSlidingComplete}
/>
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={_onPitchCorrectionPressed}
>
<View style={styles.button}>
<Text
style={[styles.text, { fontFamily: "cutive-mono-regular" }]}
>
PC: {state.shouldCorrectPitch ? "yes" : "no"}
</Text>
</View>
</TouchableHighlight>
<TouchableHighlight
onPress={_onSpeakerPressed}
underlayColor={BACKGROUND_COLOR}
>
<MaterialIcons
name={
state.throughEarpiece
? ICON_THROUGH_EARPIECE
: ICON_THROUGH_SPEAKER
}
size={32}
color="black"
/>
</TouchableHighlight>
</View>
<View />
{state.showVideo ? (
<View>
<View
style={[
styles.buttonsContainerBase,
styles.buttonsContainerTextRow
]}
>
<View />
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={_onPosterPressed}
>
<View style={styles.button}>
<Text
style={[styles.text, { fontFamily: "cutive-mono-regular" }]}
>
Poster: {state.poster ? "yes" : "no"}
</Text>
</View>
</TouchableHighlight>
<View />
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={_onFullscreenPressed}
>
<View style={styles.button}>
<Text
style={[styles.text, { fontFamily: "cutive-mono-regular" }]}
>
Fullscreen
</Text>
</View>
</TouchableHighlight>
<View />
</View>
<View style={styles.space} />
<View
style={[
styles.buttonsContainerBase,
styles.buttonsContainerTextRow
]}
>
<View />
<TouchableHighlight
underlayColor={BACKGROUND_COLOR}
style={styles.wrapper}
onPress={_onUseNativeControlsPressed}
>
<View style={styles.button}>
<Text
style={[styles.text, { fontFamily: "cutive-mono-regular" }]}
>
Native Controls:{" "}
{state.useNativeControls ? "yes" : "no"}
</Text>
</View>
</TouchableHighlight>
<View />
</View>
</View>
) : null}
</View>
);
}
const styles = StyleSheet.create({
emptyContainer: {
alignSelf: "stretch",
backgroundColor: BACKGROUND_COLOR
},
container: {
flex: 1,
flexDirection: "column",
justifyContent: "space-between",
alignItems: "center",
alignSelf: "stretch",
backgroundColor: BACKGROUND_COLOR
},
wrapper: {},
nameContainer: {
height: FONT_SIZE
},
space: {
height: FONT_SIZE
},
videoContainer: {
height: VIDEO_CONTAINER_HEIGHT
},
video: {
maxWidth: DEVICE_WIDTH
},
playbackContainer: {
flex: 1,
flexDirection: "column",
justifyContent: "space-between",
alignItems: "center",
alignSelf: "stretch",
minHeight: 18 * 2.0,
maxHeight: 19 * 2.0
},
playbackSlider: {
alignSelf: "stretch"
},
timestampRow: {
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
alignSelf: "stretch",
minHeight: FONT_SIZE
},
text: {
fontSize: FONT_SIZE,
minHeight: FONT_SIZE
},
buffering: {
textAlign: "left",
paddingLeft: 20
},
timestamp: {
textAlign: "right",
paddingRight: 20
},
button: {
backgroundColor: BACKGROUND_COLOR
},
buttonsContainerBase: {
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between"
},
buttonsContainerTopRow: {
maxHeight: 51,
minWidth: DEVICE_WIDTH / 2.0,
maxWidth: DEVICE_WIDTH / 2.0
},
buttonsContainerMiddleRow: {
maxHeight: 58,
alignSelf: "stretch",
paddingRight: 20
},
volumeContainer: {
flex: 1,
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
minWidth: DEVICE_WIDTH / 2.0,
maxWidth: DEVICE_WIDTH / 2.0
},
volumeSlider: {
width: DEVICE_WIDTH / 2.0 - 67
},
buttonsContainerBottomRow: {
maxHeight: 19,
alignSelf: "stretch",
paddingRight: 20,
paddingLeft: 20
},
rateSlider: {
width: DEVICE_WIDTH / 2.0
},
buttonsContainerTextRow: {
maxHeight: FONT_SIZE,
alignItems: "center",
paddingRight: 20,
paddingLeft: 20,
minWidth: DEVICE_WIDTH,
maxWidth: DEVICE_WIDTH
}
});
I was searched all on google, github, and other questions in stackoverflow but I still haven't found the desired answer. I'm a newbie hoping someone can help me!

Understand error : Cannot update a component from inside the function body of a different component

I have this error and I don't understand where it came from.
I'm trying to set up a screen where I scan products and when it's done, I redirect to another page.
Could you help me identify the problem and why it behaves like this?
the error is :
Warning: Cannot update a component from inside the function body of a
different component.
My Code :
class Scan extends Component {
constructor(props) {
super(props);
this.state = {
Press: false,
hasCameraPermission: null,
reference: '',
displayArray: []
};
}
initListData = async () => {
let list = await getProducts(1);
if (list) {
this.setState({
displayArray: list,
});
}
};
async UNSAFE_componentWillMount() {
this.initListData();
if (parseInt(this.state.reference) > 0) {
let product_data = await getProductByRef(this.state.reference);
console.log(this.state.reference);
if (product_data && product_data.reference_id && parseInt(product_data.reference_id) > 0) {
this.props.navigation.navigate('ProductDetails', {reference : parseInt(this.state.displayArray.reference)})
} else {
this.props.navigation.goBack();
}
} else {
this.props.navigation.goBack();
}
};
componentDidMount() {
this.getPermissionsAsync();
}
getPermissionsAsync = async () => {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({ hasCameraPermission: status === "granted" });
};
_onPress_Scan = () => {
this.setState({
Press: true
});
}
handleBarCodeScanned = ({ type, data }) => {
this.setState({ Press: false, scanned: true, lastScannedUrl: data });
};
renderBarcodeReader = () => {
const { hasCameraPermission, scanned } = this.state;
if (hasCameraPermission === null) {
return <Text>{i18n.t("scan.request")}</Text>;
}
if (hasCameraPermission === false) {
return <Text>{i18n.t("scan.noaccess")}</Text>;
}
return (
<View
style={{
flex: 1,
flexDirection: "column",
justifyContent: "flex-end",
}}
>
<BarCodeScanner
onBarCodeScanned={scanned ? undefined : this.handleBarCodeScanned}
style={{ flex:1, ...StyleSheet.absoluteFillObject}}
/>
{scanned && (
<Button
title={"Tap to Scan Again"}
onPress={() => this.setState({ scanned: false })}
/>
)}
<Button
color="#F78400"
title= {i18n.t("scan.details")}
onPress={() => this.props.navigation.navigate('ProductDetails', {reference : parseInt(this.state.displayArray.reference)})}>{i18n.t("scan.details")}
</Button>
</View>
);
}
render() {
const { hasCameraPermission, scanned, Press } = this.state;
let marker = null;
console.log('displayArray', this.state.displayArray, 'reference', this.state.displayArray.reference)
return (
<View style={{flex:1}}>
<KeyboardAvoidingView behavior="padding" enabled style={{flex:1}}>
<ScrollView contentContainerStyle={{flexGrow: 1}} >
{Press ? (
<View style={{flex:1}}>
{this.renderBarcodeReader()}
</View>
) : (
<View style={{flex:1, justifyContent:'center', alignItems:'center'}}>
<TouchableOpacity
onPress={this._onPress_Scan}
activeOpacity={3}
>
<Text style={styles.viewDetails}>Scan BarCode</Text>
</TouchableOpacity>
</View>
)}
</ScrollView>
</KeyboardAvoidingView>
</View>
);
}
}
export default Scan;

React Native Select Contacts Checkmark with FlatList

I'm new to react native and i am trying to make a contacts checkmark from FlatList with Large data items.
We can select one or more items and I want to pass these all selected contacts to another screen when i clicked on DONE button ..
like this in the picture
React Code
import React, { Component } from 'react'
import {
View,
Text,
Dimensions,
TouchableOpacity,
FlatList,
ActivityIndicator,
Image,
TextInput,
PermissionsAndroid,
Platform,
} from 'react-native'
import Feather from "react-native-vector-icons/Feather"
import ContactsLib from 'react-native-contacts'
import { styles } from './ContactStyles'
import PropTypes from 'prop-types'
export class Contacts extends Component {
static navigationOptions = ({navigation}) => {
return {
headerTitle: () => (
<View style={{flex: 1, alignSelf: 'center'}}>
<Text style={{
color: '#fff',
alignSelf: 'center',
fontSize: 22,
}}>Contacts</Text>
</View>
),
headerRight: () => (
<TouchableOpacity onPress={() => navigation.goBack(null)}
style={{right: Platform.OS === 'ios' ? Dimensions.get("window").height < 667 ? '10%' : '5%' : '25%', backgroundColor: 'black', paddingLeft: 15}}>
<Text style={{fontSize: 18, color: '#fff'}}>Done</Text>
</TouchableOpacity>
),
headerLeft: () => (
<TouchableOpacity onPress={() => navigation.goBack(null)} style={{left: Dimensions.get("window").height < 667 ? '8%' : '3%', backgroundColor: 'black', width: '100%'}}>
<Feather style = {{paddingLeft : 10}} name="chevron-left" size={26} color="white" />
</TouchableOpacity>
),
headerStyle: {
backgroundColor: 'black',
},
headerTintColor: '#fff',
};
};
constructor(props) {
super(props)
this.state = {
contactList: [],
selectedContact: [],
text: '',
isLoading: true,
}
this.arrayholder = []
}
async componentDidMount() {
if (Platform.OS === 'android') {
try {
const granted = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
{
title: 'App Contact Permission',
message: 'This App needs access to your contacts ',
buttonNegative: 'Cancel',
buttonPositive: 'OK',
}
)
if (granted === PermissionsAndroid.RESULTS.GRANTED) {
this.getListOfContacts()
} else {
this.setState({ isLoading: false })
this.getOtherContacts()
}
} catch (err) {
this.setState({ isLoading: false })
}
} else {
ContactsLib.checkPermission((err, permission) => {
if (permission === 'denied') {
this.setState({ isLoading: false })
this.getOtherContacts()
} else {
this.getListOfContacts()
}
})
}
}
// Mics Method
getOtherContacts = () => {
const { otherContactList } = this.props
const arrFinal = []
if (otherContactList.length > 0) {
otherContactList.map((listItem) => {
arrFinal.push(listItem)
})
}
arrFinal.map((listItem, index) => {
listItem.isSelected = false
listItem.id = index
})
this.setState({ contactList: arrFinal, isLoading: false })
this.arrayholder = arrFinal
}
getListOfContacts = () => {
const { otherContactList } = this.props
const arrFinal = []
ContactsLib.getAll((err, contacts) => {
if (err) {
throw err
}
contacts.map((listItem) => {
arrFinal.push({
fullname: listItem.givenName + ' ' + listItem.familyName,
phoneNumber: listItem.phoneNumbers.length > 0 ? listItem.phoneNumbers[0].number : '',
avatar: listItem.thumbnailPath,
})
})
if (otherContactList.length > 0) {
otherContactList.map((listItem) => {
arrFinal.push(listItem)
})
}
arrFinal.map((listItem, index) => {
listItem.isSelected = false
listItem.id = index
})
this.setState({ contactList: arrFinal, isLoading: false })
this.arrayholder = arrFinal
})
}
getSelectedContacts = () => {
const { selectedContact } = this.state
return selectedContact
}
checkContact = (item) => {
const { onContactSelected, onContactRemove } = this.props
let arrContact = this.state.contactList
let arrSelected = this.state.selectedContact
arrContact.map((listItem) => {
if (listItem.id === item.id) {
listItem.isSelected = !item.isSelected
}
})
if (item.isSelected) {
arrSelected.push(item)
if (onContactSelected) {
onContactSelected(item)
}
} else {
if (onContactRemove) {
onContactRemove(item)
}
arrSelected.splice(arrSelected.indexOf(item), 1)
}
this.setState({ contactList: arrContact, selectedContact: arrSelected })
}
checkExist = (item) => {
const { onContactRemove } = this.props
let arrContact = this.state.contactList
let arrSelected = this.state.selectedContact
arrContact.map((listItem) => {
if (listItem.id === item.id) {
listItem.isSelected = false
}
})
if (onContactRemove) {
onContactRemove(item)
}
arrSelected.splice(arrSelected.indexOf(item), 1)
this.setState({ contactList: arrContact, selectedContact: arrSelected })
}
SearchFilterFunction = (text) => {
let newArr = []
this.arrayholder.map(function(item) {
const itemData = item.fullname.toUpperCase()
const textData = text.toUpperCase()
if (itemData.indexOf(textData) > -1) {
newArr.push(item)
}
})
this.setState({
contactList: newArr,
text: text,
})
}
//Render Method
_renderSeparator = () => {
const { sepratorStyle } = this.props
return <View style={[styles.sepratorStyle, sepratorStyle]} />
}
_renderItem = ({ item }) => {
const { viewCheckMarkStyle } = this.props
return (
<TouchableOpacity onPress={() => this.checkContact(item)}>
<View style={styles.viewContactList}>
<Image
source={item.avatar !== '' ? { uri: item.avatar } : require('./Resources/user.png')}
style={styles.imgContactList}
/>
<View style={styles.nameContainer}>
<Text style={styles.txtContactList}>{item.fullname}</Text>
<Text style={styles.txtPhoneNumber}>{item.phoneNumber}</Text>
</View>
{item.isSelected && (
<Image
source={require('./Resources/check-mark.png')}
style={[styles.viewCheckMarkStyle, viewCheckMarkStyle]}
/>
)}
</View>
</TouchableOpacity>
)
}
_renderItemHorizontal = ({ item }) => {
const { viewCloseStyle } = this.props
return (
<View style={styles.viewSelectedContactList}>
<Image
source={item.avatar !== '' ? { uri: item.avatar } : require('./Resources/user.png')}
style={styles.imgSelected}
/>
<TouchableOpacity
onPress={() => this.checkExist(item)}
style={[styles.viewCloseStyle, viewCloseStyle]}
>
<Image source={require('./Resources/error.png')} style={styles.imgCancelStyle} />
</TouchableOpacity>
<Text style={styles.txtSelectedContact} numberOfLines={1}>
{item.fullname}
</Text>
</View>
)
}
render() {
const { searchBgColor, searchPlaceholder, viewSepratorStyle } = this.props
const { navigate } = this.props.navigation;
return (
<View style={styles.container}>
<View style={[styles.viewSearch, { backgroundColor: searchBgColor }]}>
<TextInput
style={styles.searchInput}
placeholder={searchPlaceholder}
onChangeText={this.SearchFilterFunction}
/>
</View>
<View>
<FlatList
showsHorizontalScrollIndicator={false}
data={this.state.selectedContact}
extraData={this.state}
renderItem={this._renderItemHorizontal}
horizontal
/>
</View>
{this.state.selectedContact.length > 0 && (
<View style={[styles.viewSepratorStyle, viewSepratorStyle]} />
)}
{this.state.contactList.length > 0 && (
<FlatList
ListFooterComponent={this._renderSeparator}
ItemSeparatorComponent={this._renderSeparator}
showsVerticalScrollIndicator={false}
data={this.state.contactList}
renderItem={this._renderItem}
onEndReachedThreshold={0.3}
extraData={this.state}
keyExtractor={(item) => item.id.toString()}
/>
)}
{this.state.isLoading && (
<View style={styles.loading}>
<ActivityIndicator animating={true} size="large" color="gray" />
</View>
)}
</View>
)
}
}
StyleSheet
Contacts.propTypes = {
otherContactList: PropTypes.array,
viewCloseStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
viewCheckMarkStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
sepratorStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
viewSepratorStyle: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
searchBgColor: PropTypes.string,
searchPlaceholder: PropTypes.string,
onContactSelected: PropTypes.func,
onContactRemove: PropTypes.func,
}
Contacts.defaultProps = {
otherContactList: [],
viewCloseStyle: {},
viewCheckMarkStyle: {},
sepratorStyle: {},
viewSepratorStyle: {},
searchBgColor: 'rgb(202,201,207)',
searchPlaceholder: 'Search...',
onContactSelected: () => {},
onContactRemove: () => {},
}
export default Contacts
Please Help me..
Thanks.....

I have an array list saved in asyncstorage with key 'list' My goal is that i need them to be rendered as picker. Items How can i do this?

export default class Incomel extends Component {
state = {
isEdit: null,
list: [],
isLoading: false,
editText: '',
};
componentDidMount = () => {
this.setState({ isLoading: true });
// AsyncStorage.removeItem('list')
AsyncStorage.getItem('list')
.then(list => {
if (list) {
this.setState({ list: JSON.parse(list), isLoading: false });
} else {
this.setState({ list: [], isLoading: false });
}
})
.catch(err => {
this.setState({ isLoading: false });
});
};
add = () => {
let list = this.state.list;
list.push('');
this.setState({ list: list });
this.saveToStorage();
this.setEdit(list.length - 1);
};
setEdit = index => {
if (this.state.isEdit !== index) {
this.setState({ isEdit: index, editText: this.state.list[index] });
}
};
setList = (text, index) => {
let list = this.state.list;
list[index] = text;
this.setState({ list: list, isEdit: null, editText: '' });
this.saveToStorage();
};
saveToStorage = () => {
let data = JSON.stringify(this.state.list);
AsyncStorage.setItem('list', data);
};
deleteItem = index => {
let list = this.state.list;
list.splice(index, 1);
this.setState({ list: list });
this.saveToStorage();
};
render() {
return (
<ScrollView style={style.container}>
<View style={style.header}>
<Text style={style.headerText}>Incomes</Text>
<Image style={{ width: 50, height: 50 }} source={require('../android/assets/1322767.png')} />
</View>
{this.state.isLoading ? (
<ActivityIndicator color="#d28888" size="large" />
) : (
<View style={style.body}>
{this.state.list.map((item, key) => (
<React.Fragment>
{this.state.isEdit === null || this.state.isEdit !== key ? (
<TouchableOpacity
style={style.item}
activeOpacity={0.5}
onLongPress={() => this.setEdit(key)}>
<Text style={style.itemText}>{item}</Text>
<TouchableOpacity
style={style.itemDelete}
onPress={() => this.deleteItem(key)}>
<Image style={{ width: 30, height: 30 }} source={require('../android/assets/delete-icon.png')} />
</TouchableOpacity>
</TouchableOpacity>
) : null}
{this.state.isEdit !== null ? (
key == this.state.isEdit ? (
<TextInput
style={style.itemInput}
onBlur={() => this.setList(this.state.editText, key)}
onSubmitEditing={() =>
this.setList(this.state.editText, key)
}
value={this.state.editText}
autoFocus
onChangeText={editText => this.setState({ editText })}
/>
) : null
) : null}
</React.Fragment>
))}
<TouchableOpacity style={style.btnAdd} onPress={() => this.add()}>
<Image style={{ width: 50, height: 50 }} source={require('../android/assets/add-icon.png')} />
</TouchableOpacity>
</View>
)}
</ScrollView>
);
}
}

How to refresh a single item in a FlatList in React-Native?

I made a Like function with React and Flask API, it works pretty well, it makes the Like action but it only appears when I refresh the whole list. Somehow I want to refresh the Like picture and the Like count at the post.
I tried to put extraData in my FlatList but that does not solve my problem...
handleClick() {
const headers = {
'Authorization': 'jwt ' + this.props.jwt
};
const action = this.props.has_liked? 'unlike':'like'
axios.post("http://127.0.0.1:5000/API/likeaction",{
id: this.props.id,
action: action
}, {
headers: headers
})
.then((response) => {
console.log(response.data);
})
.catch((error) => {
console.log(error)
});
this.setState({liked: action})
}
render() {
const img = this.props.has_liked? require('../assets/icons/heart-filled.png') : require('../assets/icons/heart-no-fill.png')
return(
<View style={{flex:1, marginTop: 10, marginBottom:16, left: 20}}>
<View style={{flex: 1, flexDirection: 'row'}}>
<Image source={this.props.image_uri} style={{height:42, width: 42, borderRadius:21}}/>
<Text style={{left:20,paddingTop:6}}>{this.props.author}</Text>
<Text style={{position: 'absolute', right: 40, paddingTop:6,fontSize:12,color:'#babbbc'}}> {this.props.date} </Text>
</View>
<View style={{flex:1, left: 60, right:20,width: '70%', marginTop:-10}}>
<Text style={{fontWeight:'bold',fontSize:18,marginBottom:6}}>{this.props.title} </Text>
<Text style={{fontSize:16,marginBottom:6 }}>{this.props.content}</Text>
<View style={{flex: 1, flexDirection: 'row'}}>
<TouchableOpacity onPress={this.handleClick}>
<Image style={{width:24, height:24, marginBottom:6, marginTop:6}} source={img} />
</TouchableOpacity>
<Text style={{paddingTop:10, marginLeft:6, fontSize:14,color:'#bfbfbf'}}>{this.props.likes}</Text>
</View>
</View>
</View>
);
}
}
<FlatList
data={this.state.dataSource}
extraData={this.state}
renderItem={({item}) => <PostView title={item.title}
content={item.content}
author={item.author}
date={item.date_posted}
likes={item.likes}
has_liked={item.has_liked}
jwt = {this.props.screenProps.jwt}
id = {item.id}
image_uri={{uri:'http://127.0.0.1:5000/static/profile_pics/'+item.profile_image}}/> }
refreshing={this.state.refreshing}
onRefresh={this.handleRefresh}
keyExtractor={item => item.id}/>
</View>
extraData :
extraData={{this.state}}
and put this.setState({liked: action}) here:
.then((response) => {
console.log(response.data);
this.setState({liked: action})
})
try with in home
this.state = {
loading: true,
email: '',
error: '',
refreshing: false,
dataSource: [],
updated: false
}
handleChange = () => {
this.setState({ updated: true })
}
handleRefresh = () => {
this.setState({
refreshing: true,
data: this.state.dataSource
}, () => {
this.makeRemoteRequest()
})
}
Replace
<PostView title={item.title}
....
/>
with
<PostView title={item.title}
.....
handleChange={this.handleChange}/>
extraData={this.state}
and update
and
handleClick() {
const headers = {
'Authorization': 'jwt ' + this.props.jwt
};
axios.post("http://127.0.0.1:5000/API/likeaction",{
id: this.props.id,
}, {
headers: headers
})
.then((response) => {
console.log(response.data);
if(response.data.account == "liked"){
this.setState({liked:true})
}else{
this.setState({liked:false})
}
})
.catch((error) => {
console.log(error)
});
}
with
handleClick() {
const headers = {
'Authorization': 'jwt ' + this.props.jwt
};
axios.post("http://127.0.0.1:5000/API/likeaction",{
id: this.props.id,
}, {
headers: headers
})
.then((response) => {
console.log(response.data);
this.props.handleChange(true);
if(response.data.account == "liked"){
this.setState({liked:true})
}else{
this.setState({liked:false})
}
})
.catch((error) => {
console.log(error)
});
}
const img = this.state.liked? require('../assets/icons/heart-filled.png') : require('../assets/icons/heart-no-fill.png')
with
const img = this.state.liked ? require('../assets/icons/heart-filled.png') : require('../assets/icons/heart-no-fill.png')