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!
Related
I have implemented a list like Instagram explore page using this question React native grid like instagram
but it is not performant at all with the layout I have. is there any other way to implement this?
the other problem is in re-rendering different items for some rows when scrolling, I manage to reduce it by changing the conditions in which item should be rendered but there is still a problem.
I have uploaded a video of my app here: https://youtu.be/gP7kbwKDeNA
I chunck my content to 3 item arrays and then pass it to this component this is my code:
const albumIcon = require("../../assets/icons/stream.png");
const videoIcon = require("../../assets/icons/play.png");
const { width } = Dimensions.get("window");
let isLeft = false;
let url = null;
let icon = null;
function CustomGridList(props){
const onRefresh = (loadMore = false) => {
if (loadMore === true) {
loadMore();
} else {
props.refresh(props.query);
}
};
const loadMore = () => {
props.refresh(props.query, true);
};
const getItemKey = (index,item) => {
return "item".concat(index);
};
const _renderPhoto = (item, isLarge) => {
url = null;
icon = null;
if (item && item.videos.length > 0) {
url = item.videos[0].thumbnail;
icon = videoIcon;
} else if (item && item.images.length > 1) {
url = item.images[0].url;
icon = albumIcon;
} else if(item && item.images.length > 0){
url = item.images[0].url;
}else{
url = 'https://castlebba.co.uk/wp-content/uploads/2017/04/default-image-620x600.jpg'
}
return (
<TouchableOpacity style={{marginRight: 1}}>
<CachedImage
source={{uri: url}}
style={{ flex: 1}}
afterClickFunc={() => console.log('clicked')}
useTouchableOpacity
width={isLarge ? (width*(2/3)+4) : width/3}
height={isLarge ? 200: 100}
/>
<View
style={{
position: 'absolute',
top: 4,
right: 4,
}}
>
<CustomIcon boldColor={'#fff'} icon={icon} />
</View>
</TouchableOpacity>
);
};
const _renderRow = (index, item) => {
console.log(index, item );
console.log('indexable -> ', ++index%3 );
let item1 = item[0] !== null? item[0]: null;
let item2 = item[1] !== null? item[1]: null;
let item3 = item[2] !== null? item[2]: null;
if((index+1) % 3 === 0){
if (isLeft){
isLeft = false;
}else{
isLeft = true;
}
return (
<View style={{flexDirection: isLeft? 'row' : 'row-reverse', height: 200}}>
{_renderPhoto(item1, true)}
<View style={{flexDirection: 'column'}}>
<View style={{flex:1, height: 100}}>
{_renderPhoto(item2,false)}
</View>
<View style={{ flex:1, height: 100}}>
{_renderPhoto(item3,false)}
</View>
</View>
</View>
);
}else{
return (
<View style={{flexDirection: 'row', height: 100}}>
{_renderPhoto(item1, false)}
{_renderPhoto(item2, false)}
{_renderPhoto(item3, false)}
</View>
);
}
}
const { loading, content } = props;
if(loading && !content) {
return (
<View style={{ marginTop: 30, height: 100, alignItems: 'center' }}>
<ActivityIndicator
color={getThemeStyle().color_main}
size={'large'}
/>
</View>
);
}
if (content === null) {
return <View />;
}
return (
<View style={styles.flatListUserListWrapper}>
<View style={styles.albumContainer}>
<CustomFlatList
{...props}
showsVerticalScrollIndicator={false}
style={[
styles.albumContainer,
content.length > 0 ? { margin: 1 } : {},
]}
content={content}
renderItem={({ index,item }) => _renderRow(index,item)}
itemSeparator={() => <View style={{ width: '100%', height: 1 }} />}
keyExtractor={(index,item) => getItemKey(item)}
enableLoadMore={true}
loading={loading}
onRefresh={() => onRefresh()}
loadMore={()=>loadMore()}
pagePostType={"search"}
canSendPost={false}
/>
</View>
</View>
);
}
CustomGridList.propTypes = {
error: PropTypes.string,
loading: PropTypes.bool,
refresh: PropTypes.func,
navigation: PropTypes.object,
content: PropTypes.array,
query: PropTypes.string,
navigateTo: PropTypes.func, //TODO implement this one on click
};
export default CustomGridList = React.memo(CustomGridList);
I separated different rows and put them in different files then changed the way I was selecting rows with big tiles, it got a little bit better but still rerendering more than it's supposed to. I'm not doing anything dynamicly.
const albumIcon = require("../../../assets/icons/stream.png");
const videoIcon = require("../../../assets/icons/play.png");
const { width } = Dimensions.get("window");
let isLeft = false;
let url = null;
let icon = null;
function CustomGridList(props){
const onRefresh = (loadMore = false) => {
if (loadMore === true) {
loadMore();
} else {
props.refresh(props.query);
}
};
const loadMore = () => {
props.refresh(props.query, true);
};
const getItemKey = (index,item) => {
return "item".concat(index);
};
const _renderRow = (index, item) => {
if((index+1)%6 === 0){
return(<LargeTiledRow item={item} width={width} reverse={false} navigateTo={props.navigateTo}/>)
}else if((index+4)%6 === 0){
return(<LargeTiledRow item={item} width={width} reverse={true} navigateTo={props.navigateTo}/>)
}else{
return (<GridSimpleRow item={item} width={width} navigateTo={props.navigateTo}/>);
}
}
const { loading, content } = props;
if(loading && !content) {
return (
<View style={{ marginTop: 30, height: 100, alignItems: 'center' }}>
<ActivityIndicator
color={getThemeStyle().color_main}
size={'large'}
/>
</View>
);
}
if (content === null) {
return <View />;
}
return (
<View style={styles.flatListUserListWrapper}>
<View style={styles.albumContainer}>
<CustomFlatList
{...props}
showsVerticalScrollIndicator={false}
style={[
styles.albumContainer,
content.length > 0 ? { margin: 1 } : {},
]}
content={content}
renderItem={({ index,item }) => _renderRow(index,item)}
itemSeparator={() => <View style={{ width: '100%', height: 1 }} />}
keyExtractor={(index,item) => getItemKey(item)}
enableLoadMore={true}
loading={loading}
onRefresh={() => onRefresh()}
loadMore={()=>loadMore()}
pagePostType={"search"}
canSendPost={false}
/>
</View>
</View>
);
}
function areEqual(prevProps, nextProps) {
prevProps.content === nextProps.content
}
export default CustomGridList = React.memo(CustomGridList,areEqual);
I was trying to build a social media application. In the profile screen I have Flatlist that renders the information. It is alright when I'm in dubugging mode or in normal exporting mode (without different cpu architecures) but when I try to export and assemble the output apk with different architecture ap stops working when we are touching the profile bottom tap icon.
Do you have any idea what is happening when we are trying to export and assemble the release apk?
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable react-native/no-inline-styles */
import React, {useState, useEffect, useContext} from 'react';
import {
StyleSheet,
View,
TouchableOpacity,
KeyboardAvoidingView,
FlatList,
RefreshControl,
Text,
} from 'react-native';
import {Snackbar} from 'react-native-paper';
import jwtDecode from 'jwt-decode';
import Feather from 'react-native-vector-icons/dist/Feather';
import {AppScreen} from '../../Layouts';
import {
AppProfileHeader,
AppProfileBio,
AppProfileBioContent,
AppProfilePosts,
AppProfileTopNavHeader,
AppBioCallToActions,
AppProfileStepsSlider,
} from '../../Containers';
import {
AppInputContainer,
AppText,
ContactUserModal,
AppButton,
ProfileCommentsListModal,
AppUserPostComment,
AppCommentInput,
AppPostUploaderBox,
} from '../../Components';
import {Colors, Fonts, ScreenSize} from '../../Constants';
import {ScrollView} from 'react-native-gesture-handler';
import {AppContext, AuthStorage} from '../../Services';
import {ProfileApis, SettingsApis} from '../../Services/Apis';
export default function ProfileScreen({navigation}) {
const {
user,
setUser,
setUserPosts,
userPosts,
setUserProfileInfo,
userProfileInfo,
} = useContext(AppContext);
const restoreToken = async () => {
const token = await AuthStorage.getToken();
if (!token) {
return;
}
const decodedToken = jwtDecode(token);
setUser(decodedToken);
};
const [refreshing, setRefreshing] = useState(false);
const [userCategoryType, setUserCategoryType] = useState();
const [commentAccountText, setCommentAccountText] = useState('');
const [accountCommentList, setAccountCommentList] = useState('');
const [editUserConnectionInputs, setEditUserConnectionInputs] = useState(
false,
);
const [contactUserInfo, setContactUserInfo] = useState({});
const contactUserInfoTextChangeHandler = (key, text) => {
setContactUserInfo((prev) => {
return {
...prev,
[key]: text,
};
});
};
const [snackbarVisible, setSnackbarVisible] = useState(false);
const onDismissSnackBar = () => setSnackbarVisible(false);
const [contactModalVisible, setContactModalVisible] = useState(false);
const [profileCommentsVisible, setProfileCommentsVisible] = useState(false);
const modalVisibilityHandler = () => {
setContactModalVisible(!contactModalVisible);
};
const updateUserContactInfoHandler = async () => {
try {
const result = await SettingsApis.updateAccountDatum({
datum: {...contactUserInfo},
});
if (result.data.status) {
setEditUserConnectionInputs(false);
modalVisibilityHandler();
setSnackbarVisible(true);
}
} catch (error) {
console.log("can't update user contactInfo", error.message);
}
};
const profileCommentVisibilityHandler = () => {
setProfileCommentsVisible(!profileCommentsVisible);
};
const topHeaderNavigationHandler = (route, param) => {
navigation.navigate(route, param);
};
const imagePickerHandler = () => {
navigation.jumpTo('CreatePost');
};
const fetchProfileInfo = async () => {
if (await AuthStorage.getToken()) {
try {
const result = await ProfileApis.profileInfo({
account: user.xdefaultaccount,
});
if (result.data.status) {
setUserProfileInfo(result.data.datum);
setContactUserInfo(result.data.datum);
setRefreshing(false);
}
} catch (error) {
console.log(error.message);
}
}
};
const fetchPosts = async () => {
try {
const result = await ProfileApis.getPosts({
account: userProfileInfo.id,
});
if (result.data.status) {
setUserPosts(result.data.datum);
} else {
console.log('fetch error');
}
} catch (error) {}
};
const updateStar = async (score) => {
try {
const result = await ProfileApis.updateStar({
node: userProfileInfo.id,
vote: score,
});
if (result.status) {
fetchProfileInfo();
}
} catch (error) {
console.log(error.message);
}
};
const getAccountComments = async () => {
try {
const result = await ProfileApis.getAccountComments({
account: userProfileInfo.id,
});
if (result) {
setAccountCommentList(result.data.datum);
}
} catch (error) {
console.log(error.message);
}
};
const registerCommentAccount = async () => {
try {
const result = await ProfileApis.registerCommentAccount({
node: userProfileInfo.id,
content: commentAccountText,
});
if (result) {
getAccountComments();
setCommentAccountText('');
}
} catch (error) {
console.log(error.message);
}
};
const registerCommentLike = async (id) => {
try {
const result = await ProfileApis.registerCommentLike({
node: id,
});
if (result.data.status) {
getAccountComments();
}
} catch (error) {
console.log("can't register comment like", error.message);
}
};
const followUser = async () => {
try {
const result = await ProfileApis.follow({
node: userProfileInfo.id,
});
if (result) {
console.log(result);
}
} catch (error) {
console.log(error.message);
}
};
const updateStepHandler = async (step) => {
let fetchedSteps = userProfileInfo.step;
try {
if (fetchedSteps) {
fetchedSteps.splice(fetchedSteps.indexOf(step), 1);
}
const result = await SettingsApis.updateAccountStep({
step: fetchedSteps,
});
if (result.data.status) {
fetchProfileInfo();
}
} catch (error) {
console.log("can't update account step", error.message);
}
};
useEffect(() => {
restoreToken();
}, []);
useEffect(() => {
fetchProfileInfo();
}, [user]);
useEffect(() => {
fetchPosts();
getAccountComments();
setUserCategoryType(userProfileInfo.category);
}, [userProfileInfo]);
return (
<AppScreen style={styles.container}>
<AppProfilePosts
{...{
refreshControl: (
<RefreshControl refreshing={refreshing} onRefresh={restoreToken} />
),
posts: userPosts,
navigation,
ListEmptyComponent: () => {
return (
<>
<AppPostUploaderBox {...{imagePickerHandler}} />
</>
);
},
ListHeader: () => {
return (
<>
<AppProfileTopNavHeader
{...{
navigation,
topHeaderNavigationHandler,
username: userProfileInfo.account,
userProfileInfo,
user,
}}
/>
<AppProfileHeader
{...{fetchedProfileData: userProfileInfo, navigation}}
/>
<AppProfileBio
{...{
fetchedProfileData: userProfileInfo,
updateStar,
navigation,
}}>
<AppProfileBioContent
{...{fetchedProfileData: userProfileInfo}}
/>
<AppBioCallToActions
{...{
userProfileInfo,
user,
followUser,
modalVisibilityHandler,
profileCommentVisibilityHandler,
}}
/>
</AppProfileBio>
{userCategoryType === 'personal' ? (
<AppProfileStepsSlider
{...{
avatarUri: userProfileInfo.avatar,
steps: userProfileInfo.step,
updateStepHandler,
fetchProfileInfo,
}}
/>
) : null}
</>
);
},
}}
/>
<ContactUserModal
style={[styles.wrapperStyle]}
{...{
modalVisibilityHandler,
isVisible: contactModalVisible,
}}>
<ScrollView>
<View style={styles.contentWrapper}>
<TouchableOpacity
style={styles.closeButtonContainer}
onPress={modalVisibilityHandler}>
<Feather name="x" style={styles.closeIcon} />
</TouchableOpacity>
<View style={styles.titleContainer}>
<AppText
style={{
fontFamily: Fonts.iransansMedium,
fontSize: 16,
marginRight: 10,
textAlign: 'right',
}}>
{contactUserInfo.firstname} {contactUserInfo.lastname}
</AppText>
<AppText style={{padding: 8}}>
{contactUserInfo.biography}
</AppText>
</View>
<AppInputContainer
onChangeText={(text) =>
contactUserInfoTextChangeHandler('mobile', text)
}
editable={editUserConnectionInputs}
value={contactUserInfo.mobile}
placeholder="شماره موبایل"
inputStyle={{
textAlign: 'left',
fontSize: 16,
top: 2,
color: '#838383',
}}
IconComponent={
<AppText
style={{
fontFamily: Fonts.iransans,
fontSize: 16,
color: '#838383',
}}>
+98
</AppText>
}
/>
<AppInputContainer
onChangeText={(text) =>
contactUserInfoTextChangeHandler('phone', text)
}
placeholder="تلفن ثابت"
value={contactUserInfo.phone}
editable={editUserConnectionInputs}
inputStyle={{
textAlign: 'left',
fontSize: 16,
top: 2,
color: '#838383',
paddingLeft: 20,
}}
/>
<AppInputContainer
onChangeText={(text) =>
contactUserInfoTextChangeHandler('webpage', text)
}
value={contactUserInfo.webpage}
editable={editUserConnectionInputs}
placeholder="وبسایت"
inputStyle={{
textAlign: 'left',
paddingLeft: 15,
fontSize: 16,
top: 2,
color: '#838383',
}}
/>
<AppInputContainer
onChangeText={(text) =>
contactUserInfoTextChangeHandler('province', text)
}
placeholder="استان"
value={contactUserInfo.province}
editable={editUserConnectionInputs}
inputStyle={{
fontSize: 14,
top: 2,
color: '#838383',
}}
/>
<AppInputContainer
onChangeText={(text) =>
contactUserInfoTextChangeHandler('city', text)
}
placeholder="شهر"
value={contactUserInfo.city}
editable={editUserConnectionInputs}
inputStyle={{
fontSize: 14,
top: 2,
color: '#838383',
}}
/>
<AppInputContainer
onChangeText={(text) =>
contactUserInfoTextChangeHandler('address', text)
}
value={contactUserInfo.address}
editable={editUserConnectionInputs}
placeholder="آدرس"
inputStyle={{
fontSize: 14,
top: 2,
color: '#838383',
}}
/>
{user.xaccount === userProfileInfo.id && (
<View style={styles.buttonContainer}>
{editUserConnectionInputs ? (
<AppButton
onPress={updateUserContactInfoHandler}
activeOpacity={0.7}
fontSize={16}
fontFamily={Fonts.iransansMedium}
textColor={Colors.primary_component_bg}
style={[styles.ctaButton, {backgroundColor: 'green'}]}>
ذخیره
</AppButton>
) : (
<AppButton
onPress={() =>
setEditUserConnectionInputs(!editUserConnectionInputs)
}
activeOpacity={0.7}
fontSize={16}
fontFamily={Fonts.iransansMedium}
textColor={Colors.primary_component_bg}
style={styles.ctaButton}>
ویرایش
</AppButton>
)}
</View>
)}
</View>
</ScrollView>
</ContactUserModal>
<Snackbar
duration={1500}
style={{
backgroundColor: 'green',
padding: 0,
}}
visible={snackbarVisible}
onDismiss={onDismissSnackBar}>
<View
style={{
height: 20,
width: ScreenSize.width - 50,
}}>
<Text style={{color: 'white'}}>اطلاعات با موفقیت به ثبت رسید</Text>
</View>
</Snackbar>
<ProfileCommentsListModal
{...{modalVisibilityHandler: profileCommentVisibilityHandler}}
isVisible={profileCommentsVisible}>
<KeyboardAvoidingView>
<View style={[styles.contentWrapperProfileComments]}>
<View style={{paddingBottom: 58}}>
<FlatList
ListEmptyComponent={<Text> nothing here </Text>}
data={accountCommentList}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) => {
return (
<AppUserPostComment {...{item, registerCommentLike}} />
);
}}
/>
</View>
<AppCommentInput
{...{
registerCommentAccount,
commentAccountText,
setCommentAccountText,
}}
/>
</View>
</KeyboardAvoidingView>
</ProfileCommentsListModal>
</AppScreen>
);
}
const styles = StyleSheet.create({
container: {
alignItems: 'center',
justifyContent: 'center',
},
contentWrapper: {
height: ScreenSize.height,
width: '100%',
backgroundColor: 'white',
borderRadius: 10,
},
contentWrapperProfileComments: {
height: '100%',
width: '100%',
backgroundColor: 'white',
borderRadius: 10,
},
closeButtonContainer: {
height: 50,
width: '100%',
justifyContent: 'center',
},
closeIcon: {
fontSize: 30,
color: Colors.secondary_text,
marginLeft: 10,
},
titleContainer: {
padding: 5,
marginBottom: 20,
},
buttonContainer: {
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
width: '100%',
bottom: 20,
},
ctaButton: {
backgroundColor: Colors.primary_text,
width: '90%',
borderRadius: 4,
height: 45,
},
});
so can u add results from logcat? because self-closing apps because something wrong, like component cant rendering completely
Topic: Video Flatlist in React Native. (Normal Videos / YouTube Videos)
Any one will be displayed according to the array data simultaneously. So it will be a long flatlist containing two types of video.
Sometimes many onPress event doesn't work due to performance issue
Major Issues:
App crashes when scrolling bottom to top
Performance is very poor
Above two are the major issue need to solve. Are there any ways to solve these issues?
What solutions can I try to improve Flatlist performance and to solve Video Flatlist crashing issue?
Below is my code in App.js
import React from "react";
import {
StyleSheet,
Text,
View,
SafeAreaView,
FlatList,
TouchableWithoutFeedback,
Dimensions
} from "react-native";
import Video from "react-native-video";
import ProgressBar from "react-native-progress/Bar";
import YouTube from 'react-native-youtube';
import LightVideo from "./sample.mp4";
import Icon from "react-native-vector-icons/FontAwesome";
// console.disableYellowBox = true;
function secondsToTime(time) {
return ~~(time / 60) + ":" + (time % 60 < 10 ? "0" : "") + time % 60;
}
const { width } = Dimensions.get("window");
const height = width * 0.5625;
const list = [
{
'type': 'YouTube'
},
{
'type': 'Normal'
},
{
'type': 'YouTube'
},
{
'type': 'Normal'
},
{
'type': 'Normal'
},
];
export default class RNVideo extends React.PureComponent {
state = {
video: [],
duration: [0, 0, 0, 0, 0],
progress: [0, 0, 0, 0, 0]
};
constructor(props) {
super(props);
this.player = [];
}
componentDidMount() {
this.progress_state();
this.duration_state();
this.video_pause_state(false);
}
play_pause_button = (index) => {
if (this.state.progress >= 1) {
this.player[index].seek(0);
}
var statevisible = this.state.video;
statevisible[index] = !statevisible[index];
this.setState({
video: statevisible
});
this.forceUpdate();
};
progress_bar_touch(e, index) {
const position = e.nativeEvent.locationX;
const progress = (position / 250) * this.state.duration[index];
this.player[index].seek(progress);
};
handleProgress(progress, index) {
var statevisible = this.state.progress;
statevisible[index] = progress.currentTime / this.state.duration[index];
this.setState({
progress: statevisible
});
this.forceUpdate();
};
handleLoad(meta, index) {
var statevisible = this.state.duration;
statevisible[index] = meta.duration;
this.setState({
duration: statevisible
});
this.forceUpdate();
};
onViewableItemsChanged = async ({ viewableItems }) => {
this.video_pause_state(true);
if (viewableItems != '' && viewableItems != null) {
var indexvalue = viewableItems[0]['index'];
var statevisible = this.state.video;
statevisible[indexvalue] = !statevisible[indexvalue];
this.setState({
video: statevisible
});
this.forceUpdate();
}
}
// shouldComponentUpdate() {
// return false
// }
video_pause_state(value) {
var statevisible = [];
for (var i = 0; i < list.length; i++) {
statevisible[i] = value;
}
this.setState({
video: statevisible
});
}
progress_state() {
var statevisible = [];
for (var i = 0; i < list.length; i++) {
statevisible[i] = 0;
}
this.setState({
progress: statevisible
});
}
duration_state() {
var statevisible = [];
for (var i = 0; i < list.length; i++) {
statevisible[i] = 0;
}
this.setState({
duration: statevisible
});
}
video_end(index) {
var statevisible1 = this.state.duration;
statevisible1[index] = 0;
var statevisible2 = this.state.progress;
statevisible2[index] = 0;
this.setState({
duration: statevisible1,
progress: statevisible2
});
this.play_pause_button(index);
}
keyExtractor = (item, index) => index.toString()
renderItem = ({ item, index }) => (
<>
{item.type != 'YouTube' ?
<>
<View style={styles.container}>
<View>
<Video
paused={this.state.video[index]}
muted={this.state.video[index]}
source={LightVideo}
// controls={true}
// repeat={true}
style={styles.videos}
resizeMode="cover"
onLoad={(meta) => this.handleLoad(meta, index)}
onProgress={(progress) => this.handleProgress(progress, index)}
onEnd={() => this.video_end(index)}
ref={(ref) => {
this.player[index] = ref
}}
/>
<View style={styles.controls}>
<TouchableWithoutFeedback onPress={(event) => this.play_pause_button(index)}>
<Icon name={!this.state.video[index] ? "pause" : "play"} size={30} color="#FFF" />
</TouchableWithoutFeedback>
<TouchableWithoutFeedback onPress={(event) => this.progress_bar_touch(event, index, this)}>
<View>
<ProgressBar
progress={this.state.progress[index]}
color="#FFF"
unfilledColor="rgba(255,255,255,.5)"
borderColor="#FFF"
width={230}
height={20}
/>
</View>
</TouchableWithoutFeedback>
<Text style={styles.duration}>
{secondsToTime(Math.floor(this.state.progress[index] * this.state.duration[index]))}
</Text>
</View>
</View>
</View>
</>
:
<>
<View style={{ marginTop: 50 }}>
<YouTube
apiKey="YOUR_API_KEY"
videoId="YOUR_YOUTUBE_VIDEO_ID_FROM_URL"
play={this.state.video[index]}
style={styles.videos}
/>
</View>
</>
}
</>
)
render() {
return (
<>
<SafeAreaView style={{ flex: 1 }}>
<FlatList
keyExtractor={this.keyExtractor}
data={list}
renderItem={this.renderItem}
extraData={this.state}
onViewableItemsChanged={this.onViewableItemsChanged}
removeClippedSubviews={true}
maxToRenderPerBatch={3}
initialNumToRender={3}
legacyImplementation={true}
/>
</SafeAreaView>
</>
);
}
}
const wid = "95%";
const styles = StyleSheet.create({
container: {
flex: 1,
marginBottom: 60,
marginTop: 60,
},
controls: {
backgroundColor: "rgba(0, 0, 0, 0.5)",
height: 48,
left: 0,
bottom: 0,
right: 0,
position: "absolute",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-around",
width: wid,
margin: 8,
borderBottomLeftRadius: 10,
borderBottomRightRadius: 10,
paddingHorizontal: 10,
},
mainButton: {
marginRight: 15,
},
duration: {
color: "#FFF",
marginLeft: 15,
},
videos: {
width: wid,
backgroundColor: '#ccc',
borderRadius: 10,
overflow: 'hidden',
margin: 8,
height
},
});
Update
I have come up with this final solution. But not able to do autoplay. Need ideas on this.
import React from 'react';
import { View, StyleSheet, SafeAreaView } from 'react-native';
import YouTube from 'react-native-youtube';
import { OptimizedFlatList } from 'react-native-optimized-flatlist'
import VideoPlayer from 'react-native-video-player';
const list = [
{
'type': 'Normal'
},
{
'type': 'YouTube'
},
{
'type': 'Normal'
},
{
'type': 'Normal'
},
{
'type': 'YouTube'
},
{
'type': 'Normal'
},
{
'type': 'Normal'
},
{
'type': 'Normal'
},
];
export default class App extends React.PureComponent {
constructor() {
super();
this.player = [];
this.state = {
video: { width: undefined, height: undefined, duration: undefined },
thumbnailUrl: undefined,
videoUrl: undefined,
};
}
keyExtractor = (item, index) => index.toString()
renderItem = ({ item, index }) => (
<>
{item.type != 'YouTube' ?
<>
<View style={styles.videos}>
<VideoPlayer
// autoplay
// pauseOnPress
video={{ uri: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4', type: 'mp4' }}
resizeMode={'cover'}
ref={r => this.player[index] = r}
/>
</View>
</>
:
<>
<View style={{ marginTop: 50 }}>
<YouTube
apiKey="YOUR_API_KEY"
videoId="3NQRhE772b0"
style={{...styles.videos,...styles.videos1}}
/>
</View>
</>
}
</>
)
onViewableItemsChanged = async ({ viewableItems }) => {
if (viewableItems != '' && viewableItems != null) {
var indexvalue = viewableItems[0]['index'];
indexvalue++;
if (indexvalue != 1) {
if (!this.player[indexvalue].state.isPlaying) {
this.player[indexvalue].pause();
} else {
this.player[indexvalue].resume();
}
}
}
}
render() {
return (
<>
<SafeAreaView style={{ flex: 1 }}>
<OptimizedFlatList
keyExtractor={this.keyExtractor}
data={list}
renderItem={this.renderItem}
removeClippedSubviews={true}
onViewableItemsChanged={this.onViewableItemsChanged}
/>
</SafeAreaView>
</>
);
}
}
const styles = StyleSheet.create({
videos: {
width: "95%",
backgroundColor: '#ccc',
borderRadius: 10,
overflow: 'hidden',
margin: 10,
marginBottom: 20,
marginTop: 20
},
videos1: {
height: 250
}
});
I have a Child Component that needs to update when a click event occurs in the Parent Component, Below is the code and i need startLoader function to be called.
Child Component:
class Footer extends Component {
constructor(props) {
super(props);
this.state = {
showText: true,
showLoader:false,
};
}
startLoader = () => {
this.setState({
showText:false,
showLoader:true
});
}
finishLoader = () => {
this.setState({
showText:false,
showLoader:true
});
}
render() {
return (<View style={styles.container}>
{ this.state.showText ? <Text style={styles.text}>Load More</Text> : null }
{ this.state.showLoader ? <Image style={{width: 30, height: 30}} source={require('./images/loader.gif')}/> : null }
</View>);
}
}
Parent Component:
class Social extends Component {
constructor(props) {
super(props);
var tabLabel = this.props.tabLabel;
var dataSource = new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2});
this.state = {
text: 'Search ' + tabLabel + ' here...' ,
textInput: '',
dataSource:dataSource,
height:height,
searchHeight: 40,
nextPageToken: '',
arrVideos: new Array(),
listHeight: height,
};
}
renderRow(rowData: object, sectionID: number, rowID: number) {
return (
<TouchableHighlight
underlayColor = '#ddd'>
<View style ={styles.row}>
<View style={styles.imagecon}>
<Image style={{width: 100, height: 80}} source={{uri: rowData.thumbnail}} />
</View>
<View style={styles.textcon}>
<Text style={{ fontSize: 11, fontWeight: 'bold' }} > {rowData.title} </Text>
<Text style={{ fontSize: 11}} > {rowData.desc} </Text>
</View>
</View>
</TouchableHighlight>
)
}
doneClick = () => {
this.props.showTrans();
fetch("https://www.googleapis.com/youtube/v3/search?part=snippet&q=tupac&key=AIzaSyDFOz50C_XkuEh2T-2XTN9fqm_5xHYzbg8&pageToken=" + this.state.nextPageToken)
.then((response) => response.json())
.then((responseData) => {
console.log('fxbxfbxf' + responseData.toString());
console.log('ghghgh' + responseData["items"].length);
//let doc = new DomParser().parseFromString(html,'text/html')
for(var i = 0; i < responseData["items"].length; i++) {
var title = responseData["items"][i]["snippet"].title;
var thumbnail = responseData["items"][i]["snippet"]["thumbnails"]["default"]["url"];
var desc = responseData["items"][i]["snippet"]["description"];
var video = {
title : title,
thumbnail: thumbnail,
desc: desc
}
this.state.arrVideos.push(video);
}
this.setState({
dataSource: this.state.dataSource.cloneWithRows(this.state.arrVideos),
nextPageToken: responseData.nextPageToken,
listHeight: (120 * this.state.arrVideos.length) + 80,
});
this.props.hideTrans();
})
.done();
}
loadMore = () => {
this.refs['footer'].startLoader();
fetch("https://www.googleapis.com/youtube/v3/search?part=snippet&q=tupac&key=AIzaSyDFOz50C_XkuEh2T-2XTN9fqm_5xHYzbg8&pageToken=" + this.state.nextPageToken)
.then((response) => response.json())
.then((responseData) => {
console.log('fxbxfbxf' + responseData.toString());
console.log('ghghgh' + responseData["items"].length);
//let doc = new DomParser().parseFromString(html,'text/html')
for(var i = 0; i < responseData["items"].length; i++) {
var title = responseData["items"][i]["snippet"].title;
var thumbnail = responseData["items"][i]["snippet"]["thumbnails"]["default"]["url"];
var desc = responseData["items"][i]["snippet"]["description"];
var video = {
title : title,
thumbnail: thumbnail,
desc: desc
}
this.state.arrVideos.push(video);
}
this.setState({
dataSource: this.state.dataSource.cloneWithRows(this.state.arrVideos),
nextPageToken: responseData.nextPageToken,
listHeight: (120 * this.state.arrVideos.length) + 80,
});
this.props.hideTrans();
})
.done();
}
render() {
return (
<View style={styles.menu} >
<View style={{alignItems: 'flex-start',
flexDirection: 'row',
backgroundColor: '#90CAF9', height: this.state.searchHeight}} >
<Image style={{marginTop: 10 ,marginLeft: 10 , width: 20, height: 20}} source={require('./images/search.png')} />
<TextInput
onSubmitEditing={ this.doneClick }
style={{marginLeft: 5, color: '#757575', width: width - 80, height: 40, fontSize: 11,}}
onChangeText={(textInput) => this.setState({textInput})}
placeholder={this.state.text}
underlineColorAndroid="transparent"
/>
</View>
<View style={styles.sep} >
</View>
<ScrollView style={{flex:1}}>
<SGListView
stickyHeaderIndices={[]}
onEndReachedThreshold={1}
scrollRenderAheadDistance={1}
pageSize={1}
initialListSize={9}
renderScrollComponent={props => <RecyclerViewBackedScrollView {...props} />}
style = {{flex:1, flexDirection: 'column',
backgroundColor: 'white', height: this.state.listHeight}}
dataSource = {this.state.dataSource}
renderRow = {this.renderRow.bind(this)}
renderFooter={() => <TouchableOpacity onPress={() => this.loadMore() }><Footer ref='footer' /></TouchableOpacity>}
>
</SGListView>
</ScrollView>
</View>
);
}
}
Where Footer is the onpress that needs to update the child component. How can i do this?
"objects are not valid as a react child (found: object with keys {date, events}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React add-ons. Check the render method of View."
So I have kind of a cascade of method calls. I'm retrieving dates with events inside of those. It feels like I'm doing this correctly, but am getting the above error. I've tried setting createFragment on places, but still getting the error. Here's the code:
import React, { Component } from 'react';
import {
AppRegistry,
Text,
View,
ScrollView,
RefreshControl,
StyleSheet,
Dimensions,
TextInput,
Linking,
TouchableNativeFeedback
} from 'react-native';
var _ = require('lodash');
var {width, height} = Dimensions.get('window');
var renderif = require('render-if');
var createFragment = require('react-addons-create-fragment');
var IMAGES_PER_ROW = 1
class FunInATL extends Component {
constructor(props) {
super(props);
this.state = {
currentScreenWidth: width,
currentScreenHeight: height,
dates: [],
boxIndex: 0,
showBox: false,
refreshing: false
};
}
handleRotation(event) {
if (!this.state) {
return;
}
var layout = event.nativeEvent.layout
this.state({currentScreenWidth: layout.width, currentScreenHeight: layout.height })
}
calculatedSize() {
var size = this.state.currentScreenWidth / IMAGES_PER_ROW
return {width: size}
}
renderRow(events) {
return events.map((events, i) => {
return (
<Image key={i} style={[this.getImageStyles(), styles.image, this.calculatedSize() ]} source={{uri: event.image}} />
)
})
}
openUrl(url) {
Linking.canOpenURL(url).then(supported => {
if (supported) {
Linking.openURL(url);
} else {
console.log('nope :: ' + url);
}
}).catch(err => console.error('An error occurred', err));
}
getImageStyles(featured, category) {
let options = {
borderColor: 'gold',
borderWidth: featured ? 1 : 0
}
if (!category) {
options.height = featured ? 250 : 125
}
return options;
}
_clickImage(event, index) {
if (event.title) {
let new_val = !this.state.showBox
this.setState({
dates: this.state.dates,
showBox: new_val,
boxIndex: new_val ? index : 0
});
}
}
componentDidMount() {
this.state = {
dates: [],
boxIndex: 0,
showBox: false,
refreshing: false
};
this.getApiData();
Linking.addEventListener('url', this.handleUrl);
}
componentWillUnmount() {
Linking.removeEventListener('url', this.handleUrl);
}
getApiData() {
var _this = this;
return fetch('https://www.funinatl.com/mobile2.php?v1')
.then(function(response) {
return response.json()
})
.then((responseJson) => {
var dates = createFragment(responseJson.events)
return;
let _this = this;
date.events.map((event, i) => (
date.events[i] = event
))
var datesData = [];
dates.map((date, i) => (
datesData.push({
date: i,
events: createFragment(date.events)
})
))
_this.setState({
dates: createFragment(datesData),
boxIndex: 0,
showBox: false
})
console.error(this.state);
})
.catch((error) => {
console.error(error);
})
.done();
}
renderDates() {
return this.state.dates.map((date) =>
(
<View>
<Text style={styles.dateHeader}>{ date.date }</Text>
<View>
{this.renderEvents(date.events)}
</View>
</View>
))
}
renderImage(event, index) {
if (this.state.showBox && this.state.boxIndex == index) {
return (
<View>
<TouchableNativeFeedback onPress={()=>this._clickImage(event, index)}>
<Image source={{ uri: event.image }} style={[styles.image, this.calculatedSize(), this.getImageStyles(event.featured), { height: 100 }]} />
</TouchableNativeFeedback>
<View style={{ flexDirection:'row', padding: 15 }}>
<Text style={styles.price}>{event.price}</Text>
<Text style={styles.time}>{event.time}</Text>
<TouchableNativeFeedback onPress={()=>this.openUrl(event.website)}>
<Text style={styles.btn}>Website</Text>
</TouchableNativeFeedback>
</View>
{renderif(event.venue)(
<TouchableNativeFeedback onPress={()=>this.openUrl(event.venue)}>
<Text style={styles.btn}>Venue</Text>
</TouchableNativeFeedback>
)}
</View>
)
} else {
return (
<View>
<TouchableNativeFeedback onPress={()=>this._clickImage(event, index)}>
<Image source={{ uri: event.image }} style={[styles.image, this.calculatedSize(), this.getImageStyles(event.featured)]} />
</TouchableNativeFeedback>
</View>
)
}
}
renderEvents(events) {
return events.map((event, i) =>
(
<View>
<Text style={[styles.eventCategory, this.getImageStyles(event.featured, true)]}>{event.category}</Text>
<View>
{this.renderImage(event, i)}
</View>
<Text style={[styles.eventTitle, this.getImageStyles(event.featured, true)]}>{event.title}</Text>
</View>
));
}
_onRefresh() {
this.setState({refreshing: true});
fetchData().then(() => {
this.setState({refreshing: false});
});
}
render() {
return (
<ScrollView onLayout={this.handleRotation} contentContainerStyle={styles.scrollView} refreshControl={
<RefreshControl
refreshing={this.state.refreshing}
onRefresh={this._onRefresh.bind(this)}
tintColor="#ff0000"
title="Loading..."
titleColor="#00ff00"
colors={['#ff0000', '#00ff00', '#0000ff']}
progressBackgroundColor="#ffff00"
/>
}>
<Text style={styles.header}>FunInATL</Text>
{this.renderDates()}
</ScrollView>
)
}
}
var styles = StyleSheet.create({
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-start',
textAlign: 'center',
padding: 10
},
header: {
fontSize: 30,
fontWeight: 'bold',
padding: 20,
textAlign: 'center',
backgroundColor: '#000',
color: '#fff'
},
dateHeader: {
fontSize: 20,
fontWeight: 'bold',
padding: 20,
textAlign: 'left',
color: '#fff',
backgroundColor: '#283593'
},
eventCategory: {
backgroundColor: '#03a9f4',
textAlign: 'center',
color: '#ffffff',
padding: 3
},
eventTitle: {
borderTopWidth: 0,
textAlign: 'center',
fontWeight: 'bold',
padding: 3,
fontSize: 18,
},
image: {
},
btn: {
backgroundColor: 'green',
padding: 10,
color: '#fff',
textAlign: 'center',
flex: 1
},
price: {
marginLeft: 10,
fontSize: 16,
flex: 1
},
time: {
marginRight: 10,
fontSize: 16,
flex: 1,
width: 35
}
});
AppRegistry.registerComponent('FunInATL', () => FunInATL);
Thanks!
EDIT: Updated code per the map suggestion, still not working. complaining about {events} only now.
EDIT 2: Updated with FULL code.
The component's render helpers, such as renderDates(), are returning _.each(...). _.each() returns its first argument so this is why you are receiving the error.
To illustrate:
const myObject = { a: 1 };
_.each(myObject) === myObject // true
I recommend you use Array.prototype.map() instead:
return this.state.dates.map((date) => (
<View>...</View>
));
If you use arrow functions like I did in the example above, there's no need to save a reference to this. this in the body of the function passed to map() will be bound to the instance of the component. You can then call other helper methods such as getImageStyles() like this.getImageStyles(...).
This is not related to your original question but the getApiData() method will not work. You can replace the function in the chain that handles responseJson with something like:
(responseJson) => {
this.setState({
dates: Object.entries(responseJson.events).map(([date, { events }]) => ({
date,
events,
})),
boxIndex: 0,
showBox: false,
});
}
You also need to to remove the this.state = {...} in componentDidMount(). Notice the warning in the docs that indicates you should "NEVER mutate this.state directly".