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?
Related
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!
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 have a datapicker where I select the country and with this I create a url.
<View style={styles.centrado}>
<Text style={styles.seleccion}>Elige tu País</Text>
{this.Paises()}
<Picker
selectedValue={this.state.value || ""}
style={{ height: 50, width: 120 }}
itemStyle={styles.seleccion}
onValueChange={(value, idx) => {
this.setState({ value, idx });
global.Pais = value;
console.log({ valor: value });
this.props.navigation.navigate("Noticias", {
pais: value
});
}}
>
<Picker.Item label="Seleccione" value="" />
<Picker.Item label="Argentina" value="ar" />
<Picker.Item label="Bolivia" value="bo" />
</Picker>
</View>
In the first load everything works but when I return to the home and select another country the global (global.Pais) variable remains with the initial value.
export default class noticias extends React.Component {
state = {
loading: true,
noticias: []
};
constructor(props) {
super(props);
this.fetchNoticias();
}
fetchNoticias = async () => {
console.log(global.Pais);
if (!global.Pais || global.Pais == "undefined") {
alert("Selecciona un país para ver las noticias");
this.props.navigation.navigate("Home");
} else if (global.Pais == "uy") {
const response = await fetch(
`https://prueba.${global.Pais}/wp-json/wp/v2/posts`
);
const res = await response.json();
this.setState({ noticias: res, loading: false });
} else {
const response = await fetch(
`https://prueba.com.${global.Pais}/wp-json/wp/v2/posts`
);
const res = await response.json();
this.setState({ noticias: res, loading: false });
}
};
componentDidMount() {
this.fetchNoticias();
}
render() {
const { loading, noticias } = this.state;
if (loading) {
return (
<View style={styles.container}>
<Text>Cargando .....</Text>
</View>
);
}
return (
<View>
<FlatList
data={this.state.noticias}
keyExtractor={(x, i) => i.toString()}
renderItem={({ item }) => (
<View>
<TouchableOpacity
onPress={() =>
this.props.navigation.navigate("Noticia", {
post_id: item.id
})
}
>
<Card
style={{
shadowOffset: { width: 5, height: 5 },
width: "90%",
borderRadius: 12,
alignSelf: "center",
marginBottom: 10
}}
>
<Card.Content>
<Title>{item.title.rendered}</Title>
</Card.Content>
<Card.Cover
source={{
uri:
item.better_featured_image.media_details.sizes.medium
.source_url
}}
/>
<Card.Content>
<HTMLRender html={item.excerpt.rendered} />
</Card.Content>
</Card>
</TouchableOpacity>
</View>
)}
/>
</View>
);
}
}
How can I solve it?
I think that won't work, you could use react-context or redux to save and update that value or
this.props.navigation.setParams({ pais: value })
and then get that value when you need it
this.props.navigation.getParam('pais')
i am using flatList in react native . i want to hide only perticular item from the list. current when i am hiding a button every button is hidden from the list.
when i am pressing the bid now button . then all the button is hidden if success. i want to hide the particular items bid now button.
/////////////// main.js
constructor (props){
super(props);
this.state = {
access_token: null,
user : null,
biddata: [],
totalBid : null,
page: 0,
showbidmodel: false,
bidValue: '',
biditem : '',
hideButton: false
}
}
placeBid = (e) => {
if (this.state.bidValue.length < 2) {
Toast.show({
text: ' please enter a valid bid!',
position: 'top',
buttonText: 'Okay'
})
}
else {
var url = `...............`;
const { params } = this.props.navigation.state;
console.log("all paramssssssssf ---> ", params.token);
console.log("check state-------------->>>>",this.state)
var data = {
price: this.state.bidValue,
material_type: this.state.biditem.material_type,
truck_type: this.state.biditem.truck_type,
material_name: this.state.biditem.material_name,
num_of_trucks: this.state.biditem.num_of_trucks,
truck_name: this.state.biditem.truck_name,
_id: this.state.biditem._id,
weight: this.state.biditem.weight,
extra_notes: this.state.biditem.extra_notes,
order_no: this.state.biditem.order_no,
staff: 'no',
created_by: this.state.biditem.created_by
};
console.log("post body ----------------->>>>>", JSON.stringify(data))
fetch(url, {
method: "POST",
body: JSON.stringify(data),
headers: {
"Content-Type": "application/json",
Accept: "application / json",
Authorization: "Bearer " + params.token
}
}).then(res => res.json())
.catch(error => {
console.log("bid error :", error)
})
.then(response => {
console.log("all BID SUCCESs response---> ", response);
const SUCCESS = 'Quotation Saved'
if (response.status === SUCCESS) {
Toast.show({
text: ' SUCCESS !!!! BID is placed . ',
position: 'top',
buttonText: 'Okay'
})
this.setState({ showbidmodel: false , hideButton: true })
}
else
return;
});
}
}
renderItem = ({ item }) => {
return (
<ListItem
id={item.index}
selected={() => { alert(item + "selected")}}
onPressItem ={ this.onPressItem }
onPressDetails ={ this.onPressDetails }
title={item.index}
hideButton = {this.state.hideButton}
items = { item }
/>
);
}
return (
<View style = {styles.container}>
<View style={styles.innerContainer} >
<Header title="Home" navigation={this.props.navigation} />
<Modal
animationType="slide"
transparent={false}
visible={this.state.showbidmodel}
onRequestClose={() => {
this.setState({ showbidmodel: false });
}}>
<KeyboardAvoidingView style={styles.modalStyle}>
<View style={styles.modalInnerStyle }>
<Text style={{ color: "white", fontWeight: "bold", textAlign: "center", fontSize: 20 }}>
Place your bid Here.
</Text>
<InputLocal
placeholder=" your bid "
onChange={(e) => { this.setState({ bidValue: e }); } }
value={this.state.bidValue}
/>
<TouchableOpacity
style={styles.login}
onPress={ this.placeBid }
>
<Text style={{ color: "white", fontWeight: 'bold' }}> Submit </Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
</Modal>
{typeof this.state.biddata !== "undefined" && this.state.biddata.length > 0 ?
<FlatList
data={this.state.biddata}
keyExtractor={this.keyExtractor}
extraData={this.state}
renderItem={this.renderItem}
onEndReached = { this.loadMoreTripData }
onEndReachedThreshold = {1}
/> : <ActivityIndicator
style = {styles.loading}
size="large" color="#0000ff"
/> }
</View>
</View>
);
//////////////// listItem.js
onPressDetail = () => {
this.props.onPressDetails( this.props.items._id.$oid );
};
onPressBid = () => {
this.props.onPressItem(this.props.items._id.$oid);
};
<View style={{ flex: 1, flexDirection: 'row',}} >
<MyIcon name="airport-shuttle" />
<Text style={styles.textStyle}>Truck Type: {this.props.items.truck_type}</Text>
</View>
{ !this.props.hideButton ? (
<View style={styles.buttonView}>
<View style={styles.flex} >
<TouchableOpacity
style={styles.detail}
onPress={this.onPressDetail}
>
<Text style={{ color: "white", fontWeight: 'bold', textAlign: "center" }}> Details </Text>
</TouchableOpacity>
</View>
<View style={styles.flex} >
<TouchableOpacity
style={styles.bid}
onPress={this.onPressBid}
>
<Text style={{ color: "white", fontWeight: 'bold', textAlign: "center", backgroundColor: '#ED4C67' }}> Bid Now </Text>
</TouchableOpacity>
</View>
</View>
) : null }
</View>
You can store index of the item on which you want to hide button. For that you have to pass index in your renderItem() and from there to your ListItem
renderItem={({ item, index }) => this. renderItem(item, index)}
or
You can have variable inside your each item, which indicates to show or hide button
"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".