How to render Image and Text inline using React Native? - javascript

How can I get the share icon on the same row as the text?
import React, {Component} from 'react';
import {
ActivityIndicator,
AsyncStorage,
Dimensions,
Image,
ScrollView,
Share,
StatusBar,
StyleSheet,
TouchableOpacity,
View
} from 'react-native';
import {Text} from 'react-native-elements';
import {Button} from 'react-native-share';
import Hyperlink from 'react-native-hyperlink'
import Icon from 'react-native-vector-icons/dist/FontAwesome';
const SCREEN_WIDTH = Dimensions.get('window').width;
const SCREEN_HEIGHT = Dimensions.get('window').height;
const IMAGE_SIZE = SCREEN_WIDTH - 80;
class CustomButton extends Component {
constructor() {
super();
this.state = {
selected: false
};
}
componentDidMount() {
const {selected} = this.props;
this.setState({
selected
});
}
render() {
const {title} = this.props;
const {selected} = this.state;
return (
<Button
title={title}
titleStyle={{fontSize: 15, color: 'white'}}
buttonStyle={selected ? {
backgroundColor: 'rgba(213, 100, 140, 1)',
borderRadius: 100,
width: 127
} : {
borderWidth: 1,
borderColor: 'white',
borderRadius: 30,
width: 127,
backgroundColor: 'transparent'
}}
containerStyle={{marginRight: 10}}
onPress={() => this.setState({selected: !selected})}
/>
);
}
}
class Fonts extends Component {
constructor(props) {
super(props);
this.state = {
...this.state,
selectedIndex: 0,
value: 0.5,
dataSource: null,
isLoading: true,
visible: false
};
this.componentDidMount = this.componentDidMount.bind(this);
}
getNavigationParams() {
return this.props.navigation.state.params || {}
}
componentDidMount() {
if (AsyncStorage.getItem('name')) {
this.setState({'name': AsyncStorage.getItem('name')});
}
return fetch('http://www.koolbusiness.com/newvi/' + this.props.navigation.state.params.id + '.json')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
...this.state,
isLoading: false,
dataSource: responseJson,
fontLoaded: true
}, function () {
});
})
.catch((error) => {
console.error(error);
});
}
onCancel() {
console.log("CANCEL")
this.setState({visible: false});
}
onOpen() {
console.log("OPEN")
this.setState({visible: true});
}
render() {
if (this.state.isLoading) {
return (
<View style={{flex: 1, padding: 20}}>
<ActivityIndicator/>
</View>
)
}
let shareOptions = {
message: 'http://www.koolbusiness.com/newvi/' + this.props.navigation.state.params.id + '.html',
url: 'http://bam.tech',
title: this.state.dataSource.title,
subject: this.state.dataSource.title // for email
};
return (
<View style={{flex: 1}}>
<StatusBar
barStyle="light-content"
/>
{this.state.fontLoaded ?
<View style={{flex: 1, backgroundColor: 'rgba(47,44,60,1)'}}>
<View style={styles.statusBar}/>
<View style={styles.navBar}><TouchableOpacity onPress={() => {
Share.share(shareOptions);
}}>
<View>
<Text> <Icon color="white" name="share-alt" size={42}/></Text>
</View>
</TouchableOpacity>
<Text style={styles.nameHeader}>
{this.state.dataSource.title}
</Text>
</View>
<ScrollView style={{flex: 1}}>
<View style={{justifyContent: 'center', alignItems: 'center'}}>
<Image
source={{uri: this.state.dataSource.img ? this.state.dataSource.img : "http://www.koolbusiness.com/_/images/icons/electronics_icon.png"}}
style={{width: IMAGE_SIZE, height: IMAGE_SIZE, borderRadius: 10}}
/>
</View>
<View style={{
flex: 1,
flexDirection: 'row',
marginTop: 20,
marginHorizontal: 40,
justifyContent: 'center',
alignItems: 'center'
}}>
<Text style={{flex: 1, fontSize: 26, color: 'white'}}>
{this.state.dataSource.title}
</Text>
</View>
<View style={{flex: 1, marginTop: 20, width: SCREEN_WIDTH - 80, marginLeft: 40}}>
<Text style={{flex: 1, fontSize: 15, color: 'white'}}>
{this.state.dataSource.date}
</Text>
<Hyperlink linkDefault={true}>
<Text style={{flex: 1, fontSize: 15, color: 'white'}} >
{this.state.dataSource.text}
</Text>
</Hyperlink>
</View>
</ScrollView>
</View> :
<Text>Loading...</Text>
}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
}, nameHeader: {
color: 'white',
fontSize: 22,
textAlign: 'center'
},
textStyle: {
fontSize: 18,
padding: 10,
},
});
export default Fonts;
It works as expected but I want to render the share icon on the same row as the title text.
Can it be done?

You can use "flexDirection" to 'row' and group share icon along with the title in a view:
<View style={styles.navBar}>
<TouchableOpacity onPress={() => {Share.share(shareOptions); }}>
<View>
<Text>
<Icon color="white" name="share-alt" size={42}/>
</Text>
</View>
</TouchableOpacity>
<Text style={styles.nameHeader}>
{this.state.dataSource.title}
</Text>
</View>
add the below styling to your navBar className :
navBar: {
flex: 1,
flexDirection: 'row'
},
Also, you are using Text and a View in your TouchableOpacity which I think is not needed so I removed those and did a snack on expo similar to your code, please check it here :
https://snack.expo.io/Hk3lrAQ9f

Related

Undefined is not an object evaluating title.length React Native

When I submit a form in React Native I get the below error undefined is not an object (evaluating 'title.length'). This problem occurs when I submit a form when the Card.js should be rendering the data from the form. I have checked and its getting the data back fine, seems to be a problem with rendering the data that its reading as undefined. After this error the form actually submits successfully and the Card.js displays the data.
Card.js
import React from "react";
import {
StyleSheet,
View,
Text,
ImageBackground,
TouchableOpacity,
} from "react-native";
const Card = (props) => {
const {
navigation,
title,
address,
homeType,
description,
image,
yearBuilt,
price,
id,
} = props;
return (
<TouchableOpacity
onPress={() => props.navigation.navigate("HomeDetail", { houseId: id })}
>
<View style={styles.card}>
<View style={styles.titleContainer}>
<Text style={styles.title}>
{title.length > 30 ? title.slice(0, 30) + "..." : title}
</Text>
</View>
<View style={styles.imageContainer}>
<ImageBackground source={{ uri: image }} style={styles.image}>
<Text style={styles.price}>${price}</Text>
<View style={styles.year}>
<Text style={styles.yearText}>{yearBuilt}</Text>
</View>
</ImageBackground>
</View>
<View style={styles.description}>
<Text style={styles.descriptionText}>
{description.length > 100
? description.slice(0, 100) + "..."
: description}
</Text>
</View>
</View>
</TouchableOpacity>
);
};
const styles = StyleSheet.create({
card: {
shadowColor: "black",
shadowOpacity: 0.25,
shadowOffset: { width: 0, height: 2 },
shadowRadius: 8,
borderRadius: 10,
backgroundColor: "#ffffff",
elevation: 5,
height: 300,
margin: 10,
},
titleContainer: {
height: "15%",
padding: 10,
},
title: {
fontSize: 18,
fontWeight: "bold",
color: "gray",
},
imageContainer: {
width: "100%",
height: "65%",
overflow: "hidden",
},
image: {
width: "100%",
height: "100%",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "flex-end",
},
price: {
fontSize: 30,
color: "#fff",
margin: 10,
},
year: {
margin: 10,
backgroundColor: "#2652B0",
height: 25,
width: 80,
borderRadius: 5,
},
yearText: {
fontSize: 20,
color: "#fff",
textAlign: "center",
},
description: {
margin: 10,
},
descriptionText: {
fontSize: 16,
color: "gray",
},
});
export default Card;
HomeListScreen.js
import React, { useEffect, useState } from "react";
import {
StyleSheet,
View,
Text,
FlatList,
ActivityIndicator,
} from "react-native";
import { FloatingAction } from "react-native-floating-action";
import { useDispatch, useSelector } from "react-redux";
import Card from "../components/Card";
import * as houseAction from "../redux/actions/houseAction";
const HomeListScreen = (props) => {
const dispatch = useDispatch();
const [isLoading, setIsLoading] = useState(false);
const { houses } = useSelector((state) => state.house);
useEffect(() => {
setIsLoading(true);
dispatch(houseAction.fetchHouses())
.then(() => setIsLoading(false))
.catch(() => setIsLoading(false));
}, [dispatch]);
if (isLoading) {
return (
<View style={styles.centered}>
<ActivityIndicator size="large" />
</View>
);
}
if (houses.length === 0 && !isLoading) {
return (
<View style={styles.centered}>
<Text>No home found. You could add one!</Text>
</View>
);
}
return (
<View style={styles.container}>
<FlatList
data={houses}
keyExtractor={(item) => item._id}
renderItem={({ item }) => (
<Card
navigation={props.navigation}
title={item.title}
address={item.address}
homeType={item.homeType}
description={item.description}
price={item.price}
image={item.image}
yearBuilt={item.yearBuilt}
id={item._id}
/>
)}
/>
<FloatingAction
position="right"
animated={false}
showBackground={false}
onPressMain={() => props.navigation.navigate("AddHome")}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
centered: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
});
export default HomeListScreen;
<Text style={styles.title}>
{ title ? (title.length > 30 ? title.slice(0, 30) + "..." : title):true}
</Text>
Make sure that title is not undefined.

Is there a way to use mutable arrays in FlatList?

I'm trying to make a toy stopwatch app in order to learn react-native.
I made a lap system, but it is getting way too slow when there are >15 laps. I think the poor performance point is the laps: this.state.laps.concat([d - this.state.lapTimerStart]) part, because of .concat is making a new object every time the Lap button is pressed.
I've heard that .push is way faster than .concat.
So I tried to use .push, but because .push was mutating the array, and FlatList was a PureComponent so it re-rendered only when the props have changed.
I found a way, but it was just the same as doing .concat because basically, it was
let lapArr = this.state.laps;
Array.prototype.push.apply(lapArr, [d - this.state.lapTimerStart]);
this.setState({
laps: lapArr,
})
The full code is
import React, {Component} from 'react';
import {
View,
Text,
StyleSheet,
TouchableHighlight,
FlatList,
} from 'react-native';
import {useTheme} from 'react-native-paper';
import TimeFormatter from 'minutes-seconds-milliseconds';
class Stopwatch extends Component {
constructor(props) {
super(props);
this.state = {
laps: [],
isRunning: false,
mainTimer: null,
lapTimer: null,
mainTimerStart: null,
lapTimerStart: null,
};
}
handleLapReset() {
let {isRunning, mainTimerStart, lapTimer} = this.state;
if (mainTimerStart) {
if (isRunning) {
const d = new Date();
this.setState({
lapTimerStart: d,
lapTimer: d - this.state.lapTimerStart + lapTimer,
laps: this.state.laps.concat([d - this.state.lapTimerStart]),
});
return;
}
this.state.laps = [];
this.setState({
mainTimerStart: null,
lapTimerStart: null,
mainTimer: 0,
lapTimer: 0,
});
}
}
handleStartStop() {
let {isRunning, mainTimer, lapTimer} = this.state;
if (isRunning) {
clearInterval(this.interval);
this.setState({
isRunning: false,
});
return;
}
const d = new Date();
this.setState({
mainTimerStart: d,
lapTimerStart: d,
isRunning: true,
});
this.interval = setInterval(() => {
const t = new Date();
this.setState({
mainTimer: t - this.state.mainTimerStart + mainTimer,
lapTimer: t - this.state.lapTimerStart + lapTimer,
});
}, 10);
}
_renderTimers() {
const {theme} = this.props;
return (
<View
style={[
styles.timerWrapper,
{backgroundColor: theme.colors.background},
]}>
<View style={styles.timerWrapperInner}>
<Text style={[styles.lapTimer, {color: theme.colors.text}]}>
{TimeFormatter(this.state.lapTimer)}
</Text>
<Text style={[styles.mainTimer, {color: theme.colors.text}]}>
{TimeFormatter(this.state.mainTimer)}
</Text>
</View>
</View>
);
}
_renderButtons() {
const {theme} = this.props;
return (
<View style={styles.buttonWrapper}>
<TouchableHighlight
underlayColor={theme.colors.disabled}
onPress={this.handleLapReset.bind(this)}
style={[styles.button, {backgroundColor: theme.colors.background}]}>
<Text style={[styles.lapResetBtn, {color: theme.colors.text}]}>
{this.state.mainTimerStart && !this.state.isRunning
? 'Reset'
: 'Lap'}
</Text>
</TouchableHighlight>
<TouchableHighlight
underlayColor={theme.colors.disabled}
onPress={this.handleStartStop.bind(this)}
style={[styles.button, {backgroundColor: theme.colors.background}]}>
<Text
style={[styles.startBtn, this.state.isRunning && styles.stopBtn]}>
{this.state.isRunning ? 'Stop' : 'Start'}
</Text>
</TouchableHighlight>
</View>
);
}
_renderLaps() {
return (
<View style={styles.lapsWrapper}>
<FlatList
data={this.state.laps}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
/>
</View>
);
}
keyExtractor(item, index) {
return index.toString();
}
renderItem({item, index}) {
return (
<View style={styles.lapRow}>
<View style={styles.lapStyle}>
<View style={styles.lapBoxStyle} />
<Text style={styles.lapNumber}>{index + 1}</Text>
</View>
<View style={styles.lapStyle}>
<Text style={styles.lapTime}>{TimeFormatter(item)}</Text>
</View>
</View>
);
}
render() {
return (
<View style={styles.container}>
<View style={styles.top}>{this._renderTimers()}</View>
<View style={styles.middle}>{this._renderButtons()}</View>
<View style={styles.bottom}>{this._renderLaps()}</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {flex: 1},
timerWrapper: {
justifyContent: 'center',
flex: 1,
},
top: {
flex: 1,
},
middle: {
flex: 1,
backgroundColor: '#F0EFF5',
},
bottom: {
flex: 2,
},
mainTimer: {
fontSize: 50,
fontFamily: 'CircularStd-Medium',
alignSelf: 'center',
},
lapTimer: {
fontSize: 18,
fontFamily: 'CircularStd-Medium',
alignSelf: 'flex-end',
},
timerWrapperInner: {
alignSelf: 'center',
},
buttonWrapper: {
flexDirection: 'row',
justifyContent: 'space-around',
paddingTop: 15,
paddingBottom: 30,
},
button: {
height: 80,
width: 80,
borderRadius: 40,
backgroundColor: '#FFF',
justifyContent: 'center',
alignItems: 'center',
},
lapRow: {
flexDirection: 'row',
justifyContent: 'space-between',
height: 40,
paddingTop: 10,
},
lapNumber: {
flexDirection: 'row',
fontSize: 16,
fontFamily: 'CircularStd-Book',
color: '#777',
flex: 1,
},
lapTime: {
flexDirection: 'row',
color: '#000',
fontSize: 20,
fontFamily: 'CircularStd-Book',
flex: 1,
},
startBtn: {
color: '#0C0',
fontFamily: 'CircularStd-Book',
},
stopBtn: {
color: '#C00',
fontFamily: 'CircularStd-Book',
},
lapsWrapper: {
backgroundColor: '#ddd',
},
lapResetBtn: {
fontFamily: 'CircularStd-Book',
},
lapStyle: {
width: '40%',
flexDirection: 'row',
},
lapBoxStyle: {
flexDirection: 'row',
flex: 1,
},
});
export default function StopwatchScreen(props) {
const theme = useTheme();
return <Stopwatch {...props} theme={theme} />;
}
I tried not to use arrow functions, many news, but it didn't help that much.
FlatList is a pure component and it is mandatory to give new ref of data prop to render list . You should use concat but it created new object.
i think the main issue is renderItem.
create a separate PureComponent to avoid re-rendeing of items
class Item extends PureComponent {
const {item,index} = this.props;
return (
<View style={styles.lapRow}>
<View style={styles.lapStyle}>
<View style={styles.lapBoxStyle} />
<Text style={styles.lapNumber}>{index + 1}</Text>
</View>
<View style={styles.lapStyle}>
<Text style={styles.lapTime}>{TimeFormatter(item)}</Text>
</View>
</View>
);
}
and use in render item
renderItem({item, index}) {
return (
<Item item={item} index={index} />
);
}

React Native Component Exception <Text strings must be rendered within a Text component>

I am trying to build a react native application with the expo, First I try to build this application with my chrome web browser, it was worked without any issue after that I try to test the application with my android device and I'm getting an exception - "Text strings must be rendered within a <Text> component" HomeScreen.js files. I have no idea why this happened. My code as follows,
/*This is an Example of Grid View in React Native*/
// import * as React from "react";
import React from 'react';
import { Image, FlatList, StyleSheet, View, Text, TouchableOpacity, TextInput } from 'react-native';
import { COLORS } from '../../asserts/Colors/Colors';
import { CATEGORIES } from '../../asserts/mocks/itemListData';
import CategoryGridTitle from '../components/HomeGridTile';
import { HeaderButtons, Item } from 'react-navigation-header-buttons';
import { HeaderButton } from '../components/HeaderButton';
import HomeScreenButton from '../components/HomeScreenButton';
//import all the components we will need
const renderTopBarItems = (topBarItems) => {
return (
<TouchableOpacity
style={styles.topBar}>
<Text style={styles.textStyle}> {topBarItems.item.category} </Text>
</TouchableOpacity>
)
}
const HomeScreen = props => {
const renderGridItem = (itemData) => {
return <CategoryGridTitle
title={itemData.item.title}
image={itemData.item.image}
onSelect={() => {
props.navigation.navigate({
routeName: 'PaymentHandlerScreen',
params: {
categoryId: itemData.item.id
}
});
}} />;
}
// const [images, setImages] = React.useState(picsumImages);
return (
<View style={styles.mainBody}>
<View style={styles.searchContainer}>
<TextInput
placeholder='Search'
style={styles.formField}
placeholderTextColor={'#888888'}
/>
<TouchableOpacity onPress={() => props.navigation.navigate('BarCodeScannerScreen')}
style={styles.saveFormField}>
<Image
source={require('../../../images/barcode.png')}
style={{
width: '100%',
height: '30%',
resizeMode: 'contain',
alignContent: 'center',
}}
/> </TouchableOpacity>
</View>
<View style={styles.tabBar}>
<FlatList
horizontal
pagingEnabled={true}
showsHorizontalScrollIndicator={false}
keyExtractor={(item, index) => item.id}
data={CATEGORIES}
renderItem={renderTopBarItems} />
</View>
<FlatList
keyExtractor={(item, index) => item.id}
data={CATEGORIES}
renderItem={renderGridItem}
numColumns={3} />
<HomeScreenButton style={styles.buttonView} />
</View>
);
};
HomeScreen.navigationOptions = navigationData => {
return {
headerTitle: 'Tickets',
headerRight: (
<HeaderButtons HeaderButtonComponent={HeaderButton}>
<Item
title='profile'
iconName='ios-star'
onPress={() => {
console.log('profile clicked');
}} />
<Item
title='more'
iconName='md-more'
onPress={() => {
console.log('more clicked');
}} />
</HeaderButtons>
)
};
};
export default HomeScreen;
const styles = StyleSheet.create({
mainBody: {
flex: 1,
justifyContent: 'center',
backgroundColor: COLORS.background,
paddingTop: '3%',
},
searchContainer: {
flex: 1,
flexDirection: 'row',
},
tabBar: {
paddingBottom: '3%',
},
topBar: {
width: 150,
borderWidth: 1,
borderRadius: 20,
borderColor: COLORS.primary_blue,
padding: '5%',
marginLeft: '5%',
},
textStyle: {
color: COLORS.primary_blue,
textAlign: 'center',
fontWeight: 'bold',
fontSize: 14,
},
formField: {
flex: 4,
borderWidth: 1,
padding: '4%',
marginLeft: '2%',
borderRadius: 10,
borderColor: COLORS.gray,
backgroundColor: COLORS.gray,
fontSize: 15,
height: '35%',
},
saveFormField: {
flex: 0.5,
justifyContent: 'space-between',
alignItems: 'center',
margin: 10,
},
buttonView: {
position: 'absolute',
bottom: 0,
left: 0,
},
});
Thank you.
I ran into this error a couple of times. RN doesn't like extra spaces in tags. try removing the spaces before and after {topBarItems.item.category}
<Text style={styles.textStyle}>{topBarItems.item.category}</Text>

Implemtation of Audio Progress bar in React Native

I have been trying to build a a media player in react native using Expo to be able to play audio on my music project.
I have successfully hacked one together with the preferred design but I still have a big limitation. I will love to implement a progress bar that will show how far the song has played.
Here is my player design. Secondly, How do I substitute this progress bar for IOS ??
render() {
return (
<View >
<View style={styles.container} >
<Image
style={styles.imageStyle}
source={{uri: this.state.coverName || this.MusicPlayer.getCurrentItemCover()}}
/>
<View >
<Text style = {styles.artistName}> {this.state.artistName || this.MusicPlayer.getCurrentItemArtistName()}</Text>
</View>
<View style={{paddingRight:2, paddingLeft:2}}>
<Text style={styles.songStyle}> {this.state.title || this.MusicPlayer.getCurrentSongTitle()}</Text>
</View>
<ProgressBarAndroid style={{marginLeft:10, marginRight:10}} styleAttr="Horizontal" color="#2196F3" indeterminate={false} progress={0.5} />
<View style={{flexDirection:'row', padding:10, alignItems:'center', justifyContent:'center'}}>
<Text style={styles.iconStyle2} onPress={this.playPrev}>
<Feather name="rewind" size={20} style={styles.text} />
</Text>
{this.state.playing?
<Text style={styles.iconStyle2} onPress={this.startStopPlay}>
<Feather name="pause" size={24} style={styles.text} />
</Text>
:
<Text style={styles.iconStyle2} onPress={this.startStopPlay}>
<Feather name="play-circle" size={24} style={styles.text} />
</Text>
}
<Text style={styles.iconStyle2} onPress={this.playNext}>
<Feather name="fast-forward" size={20} style={styles.text} />
</Text>
</View>
</View>
</View>
);
}
}
My Play Function
startPlay = async (index = this.index, playing = false) => {
const url = this.list[index].url;
this.index = index;
console.log(url);
// Checking if now playing music, if yes stop that
if(playing) {
await this.soundObject.stopAsync();
} else {
// Checking if item already loaded, if yes just play, else load music before play
if(this.soundObject._loaded) {
await this.soundObject.playAsync();
} else {
await this.soundObject.loadAsync(url);
await this.soundObject.playAsync();
}
}
};
My main goal is to get a small player on mobile close to this.
I am working with React native Expo version.
FullCode Of Music Player Android AND ios Both in working
SeekBar.js
import React, { Component } from 'react';
import { defaultString } from '../String/defaultStringValue';
import {
View,
Text,
StyleSheet,
Image,
Slider,
TouchableOpacity,
} from 'react-native';
function pad(n, width, z = 0) {
n = n + '';
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}
const minutesAndSeconds = (position) => ([
pad(Math.floor(position / 60), 2),
pad(position % 60, 2),
]);
const SeekBar = ({
trackLength,
currentPosition,
onSeek,
onSlidingStart,
}) => {
const elapsed = minutesAndSeconds(currentPosition);
const remaining = minutesAndSeconds(trackLength - currentPosition);
return (
<View style={styles.container}>
<View style={{ flexDirection: 'row' }}>
<Text style={[styles.text, { color: defaultString.darkColor }]}>
{elapsed[0] + ":" + elapsed[1]}
</Text>
<View style={{ flex: 1 }} />
<Text style={[styles.text, { width: 40, color: defaultString.darkColor }]}>
{trackLength > 1 && "-" + remaining[0] + ":" + remaining[1]}
</Text>
</View>
<Slider
maximumValue={Math.max(trackLength, 1, currentPosition + 1)}
onSlidingStart={onSlidingStart}
onSlidingComplete={onSeek}
value={currentPosition}
minimumTrackTintColor={defaultString.darkColor}
maximumTrackTintColor={defaultString.lightGrayColor}
thumbStyle={styles.thumb}
trackStyle={styles.track}
/>
</View>
);
};
export default SeekBar;
const styles = StyleSheet.create({
slider: {
marginTop: -12,
},
container: {
paddingLeft: 16,
paddingRight: 16,
paddingTop: 16,
},
track: {
height: 2,
borderRadius: 1,
},
thumb: {
width: 10,
height: 10,
borderRadius: 5,
backgroundColor: defaultString.darkColor,
},
text: {
color: 'rgba(255, 255, 255, 0.72)',
fontSize: 12,
textAlign: 'center',
}
});
Player.js
import React, { Component } from 'react';
import {
View,
Text,
StatusBar,
} from 'react-native';
import Header from './Header';
import AlbumArt from './AlbumArt';
import TrackDetails from './TrackDetails';
import SeekBar from './SeekBar';
import Controls from './Controls';
import Video from 'react-native-video';
export default class Player extends Component {
constructor(props) {
super(props);
this.state = {
paused: true,
totalLength: 1,
currentPosition: 0,
selectedTrack: 0,
repeatOn: false,
shuffleOn: false,
};
}
setDuration(data) {
this.setState({ totalLength: Math.floor(data.duration) });
}
setTime(data) {
this.setState({ currentPosition: Math.floor(data.currentTime) });
}
seek(time) {
time = Math.round(time);
this.refs.audioElement && this.refs.audioElement.seek(time);
this.setState({
currentPosition: time,
paused: false,
});
}
onBack() {
if (this.state.currentPosition < 10 && this.state.selectedTrack > 0) {
this.refs.audioElement && this.refs.audioElement.seek(0);
this.setState({ isChanging: true });
setTimeout(() => this.setState({
currentPosition: 0,
paused: false,
totalLength: 1,
isChanging: false,
selectedTrack: this.state.selectedTrack - 1,
}), 0);
} else {
this.refs.audioElement.seek(0);
this.setState({
currentPosition: 0,
});
}
}
onForward() {
if (this.state.selectedTrack < this.props.tracks.length - 1) {
this.refs.audioElement && this.refs.audioElement.seek(0);
this.setState({ isChanging: true });
setTimeout(() => this.setState({
currentPosition: 0,
totalLength: 1,
paused: false,
isChanging: false,
selectedTrack: this.state.selectedTrack + 1,
}), 0);
}
}
render() {
const track = this.props.tracks[this.state.selectedTrack];
const video = this.state.isChanging ? null : (
<Video source={{ uri: track.audioUrl }} // Can be a URL or a local file.
ref="audioElement"
playInBackground={true}
playWhenInactive={true}
paused={this.state.paused} // Pauses playback entirely.
resizeMode="cover" // Fill the whole screen at aspect ratio.
repeat={true} // Repeat forever.
onLoadStart={this.loadStart} // Callback when video starts to load
onLoad={this.setDuration.bind(this)} // Callback when video loads
onProgress={this.setTime.bind(this)} // Callback every ~250ms with currentTime
onEnd={this.onEnd} // Callback when playback finishes
onError={this.videoError} // Callback when video cannot be loaded
style={styles.audioElement} />
);
return (
<View style={styles.container}>
{/* <StatusBar hidden={true} /> */}
{/* <Header message="Playing From Charts" /> */}
<AlbumArt url={track.albumArtUrl} />
<TrackDetails title={track.title} artist={track.artist} />
<SeekBar
onSeek={this.seek.bind(this)}
trackLength={this.state.totalLength}
onSlidingStart={() => this.setState({ paused: true })}
currentPosition={this.state.currentPosition}
/>
<Controls
onPressRepeat={() => this.setState({ repeatOn: !this.state.repeatOn })}
repeatOn={this.state.repeatOn}
shuffleOn={this.state.shuffleOn}
forwardDisabled={this.state.selectedTrack === this.props.tracks.length - 1}
onPressShuffle={() => this.setState({ shuffleOn: !this.state.shuffleOn })}
onPressPlay={() => this.setState({ paused: false })}
onPressPause={() => this.setState({ paused: true })}
onBack={this.onBack.bind(this)}
onForward={this.onForward.bind(this)}
paused={this.state.paused} />
{video}
</View>
);
}
}
const styles = {
container: {
flex: 1,
backgroundColor: '#ffffff',
},
audioElement: {
height: 0,
width: 0,
}
};
AlbumArt.js
import React, { Component } from 'react';
import {
View,
Text,
StyleSheet,
Image,
TouchableHighlight,
TouchableOpacity,
Dimensions,
} from 'react-native';
const AlbumArt = ({
url,
onPress
}) => (
<View style={styles.container}>
<TouchableOpacity onPress={onPress}>
<View
style={[styles.image, {
elevation: 10, shadowColor: '#d9d9d9',
shadowOffset: { width: 0, height: 0 },
shadowOpacity: 1,
shadowRadius: 2,
borderRadius: 20,
backgroundColor: '#ffffff'
}]}
>
<Image
style={[styles.image, { borderRadius: 20 }]}
source={{ uri: url }}
/>
</View>
</TouchableOpacity>
</View>
);
export default AlbumArt;
const { width, height } = Dimensions.get('window');
const imageSize = width - 100;
const styles = StyleSheet.create({
container: {
alignItems: 'center',
marginTop: 30,
paddingLeft: 24,
paddingRight: 24,
},
image: {
width: imageSize,
height: imageSize,
},
})
App.js
import React, { Component } from 'react';
import Player from './Player';
import { BackHandler } from 'react-native';
import i18n from '../../Assets/I18n/i18n';
import { Actions } from 'react-native-router-flux';
export default class MusicPlayer extends Component {
constructor(props) {
super(props);
const { navigation } = this.props;
this.state = {
song: navigation.getParam('songid')
};
this.props.navigation.setParams({
title: i18n.t('Panchkhan')
})
}
componentWillMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
handleBackButton = () => {
Actions.pop();
return true;
};
render() {
const TRACKS = [
{
title: 'Stressed Out',
artist: 'Twenty One Pilots',
albumArtUrl: "https://cdn-images-1.medium.com/max/1344/1*fF0VVD5cCRam10rYvDeTOw.jpeg",
audioUrl: this.state.song
}
];
return <Player tracks={TRACKS} />
}
}
Controls.js
import React, { Component } from 'react';
import { defaultString } from '../String/defaultStringValue';
import {
View,
Text,
StyleSheet,
Image,
TouchableOpacity,
} from 'react-native';
const Controls = ({
paused,
shuffleOn,
repeatOn,
onPressPlay,
onPressPause,
onBack,
onForward,
onPressShuffle,
onPressRepeat,
forwardDisabled,
}) => (
<View style={styles.container}>
<TouchableOpacity activeOpacity={0.0} onPress={onPressShuffle}>
<Image style={[{ tintColor: defaultString.darkColor } , styles.secondaryControl, shuffleOn ? [] : styles.off]}
source={require('../img/ic_shuffle_white.png')} />
</TouchableOpacity>
<View style={{ width: 40 }} />
<TouchableOpacity onPress={onBack}>
<Image style={{ tintColor: defaultString.darkColor }} source={require('../img/ic_skip_previous_white_36pt.png')} />
</TouchableOpacity>
<View style={{ width: 20 }} />
{!paused ?
<TouchableOpacity onPress={onPressPause}>
<View style={styles.playButton}>
<Image style={{ tintColor: defaultString.darkColor }} source={require('../img/ic_pause_white_48pt.png')} />
</View>
</TouchableOpacity> :
<TouchableOpacity onPress={onPressPlay}>
<View style={styles.playButton}>
<Image style={{ tintColor: defaultString.darkColor }} source={require('../img/ic_play_arrow_white_48pt.png')} />
</View>
</TouchableOpacity>
}
<View style={{ width: 20 }} />
<TouchableOpacity onPress={onForward}
disabled={forwardDisabled}>
<Image style={[forwardDisabled && { opacity: 0.3 }, { tintColor: defaultString.darkColor }]}
source={require('../img/ic_skip_next_white_36pt.png')} />
</TouchableOpacity>
<View style={{ width: 40 }} />
<TouchableOpacity activeOpacity={0.0} onPress={onPressRepeat}>
<Image style={[{ tintColor: defaultString.darkColor }, styles.secondaryControl, repeatOn ? [] : styles.off]}
source={require('../img/ic_repeat_white.png')} />
</TouchableOpacity>
</View>
);
export default Controls;
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingTop: 8,
},
playButton: {
height: 72,
width: 72,
borderWidth: 1,
borderColor: defaultString.darkColor,
borderRadius: 72 / 2,
alignItems: 'center',
justifyContent: 'center',
},
secondaryControl: {
height: 18,
width: 18,
},
off: {
opacity: 0.30,
}
})
controlDetails.js
import React, { Component } from 'react';
import { defaultString } from '../String/defaultStringValue';
import {
View,
Text,
StyleSheet,
Image,
TouchableHighlight,
TouchableOpacity,
Dimensions,
} from 'react-native';
const TrackDetails = ({
title,
artist,
onAddPress,
onMorePress,
onTitlePress,
onArtistPress,
}) => (
<View style={styles.container}>
{/* <TouchableOpacity onPress={onAddPress}>
<Image style={styles.button}
source={require('../img/ic_add_circle_outline_white.png')} />
</TouchableOpacity> */}
<View style={styles.detailsWrapper}>
<Text style={styles.title} onPress={onTitlePress}>{title}</Text>
<Text style={styles.artist} onPress={onArtistPress}>{artist}</Text>
</View>
{/* <TouchableOpacity onPress={onMorePress}>
<View style={styles.moreButton}>
<Image style={styles.moreButtonIcon}
source={require('../img/ic_more_horiz_white.png')} />
</View>
</TouchableOpacity> */}
</View>
);
export default TrackDetails;
const styles = StyleSheet.create({
container: {
paddingTop: 24,
flexDirection: 'row',
paddingLeft: 20,
alignItems: 'center',
paddingRight: 20,
},
detailsWrapper: {
justifyContent: 'center',
alignItems: 'center',
flex: 1,
},
title: {
fontSize: 16,
fontWeight: 'bold',
color: defaultString.darkColor,
textAlign: 'center',
},
artist: {
color: defaultString.darkColor,
fontSize: 12,
marginTop: 4,
},
button: {
opacity: 0.72,
},
moreButton: {
borderColor: 'rgb(255, 255, 255)',
borderWidth: 2,
opacity: 0.72,
borderRadius: 10,
width: 20,
height: 20,
alignItems: 'center',
justifyContent: 'center',
},
moreButtonIcon: {
height: 17,
width: 17,
}
});

React native - remove object from array

I have a Modal that displays a list of contacts. onPress, contacts are dynamically added to a View.
If a contact has already been added, on second onPress I would like to remove it from the View. For that I'm amending the state/array containing the contacts, using splice but it removes all the contacts at once.
I'm also trying to update the state of the 'Add' icon. If a contact has been added the Add icon Image should become the active one.
Not sure what I'm doing wrong?
Here's the Modal opened up:
My code:
import React, {Component} from 'react'
import {
Text,
View,
ListView,
ScrollView,
StyleSheet,
Image,
TouchableHighlight,
TextInput,
Modal,
} from 'react-native'
const friends = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
}).cloneWithRows([
{
id: 1,
firstname: 'name1',
surname: 'surname1',
image: require('../images/friends/avatar-friend-01.png')
},
{
id: 2,
firstname: 'name2',
surname: 'surname2',
image: require('../images/friends/avatar-friend-02.png')
},
{
id: 3,
firstname: 'name3',
surname: 'surname3',
image: require('../images/friends/avatar-friend-03.png')
},
])
class AppView extends Component {
state = {
isModalVisible: false,
contactsPicked: [],
friendsState: {},
}
setModalVisible = visible => {
this.setState({isModalVisible: visible})
}
pickContact = (friend) => {
if(this.state.contactsPicked.indexOf(friend) < 0){
var tempFriendsState = this.state.friendsState
tempFriendsState[friend.id] = true
this.setState({
contactsPicked: [ ...this.state.contactsPicked, friend],
friendsState: tempFriendsState,
})
}
else{
let index = this.state.contactsPicked.indexOf(friend)
let nextContacts = this.state.contactsPicked
nextContacts.splice(index,1)
let tempFriendsState = this.state.friendsState
tempFriendsState[friend.id] = false
this.setState({
contactsPicked: nextContacts,
friendsState: tempFriendsState,
})
}
}
removeContact = (friend) => {
let index = this.state.contactsPicked.indexOf(friend)
let nextContacts = this.state.contactsPicked
nextContacts.splice(index,1)
this.setState({
contactsPicked: nextContacts,
})
}
_renderAddFriendTile = () => {
return(
<View style={[styles.step, styles.stepThree]}>
<View style={{flex:1}}>
<Text style={styles.heading}>Friend tile</Text>
</View>
{this.state.contactsPicked.length > 0 && (
<TouchableHighlight onPress={() => {this.removeContact(this.state.contactsPicked)}}>
<View>
{this.state.contactsPicked.map((contact,index) => (
<View key={index} style={[styles.row, styles.friendRow]}>
<Image source={contact.image} style={styles.friendIcon}></Image>
<Text style={styles.name}>{contact.firstname} </Text>
<Text style={styles.name}>{contact.surname}</Text>
<View style={styles.roundIconContainer}>
<View style={styles.roundIcon}>
<View style={[styles.removeButton, styles.buttonSmall]}>
<Image source={require('../images/button-cross-small.png')} style={styles.crossIconSmall}></Image>
</View>
</View>
</View>
</View>
))}
</View>
</TouchableHighlight>
)}
<TouchableHighlight style={styles.addFriendButtonContainer} onPress={() => {this.setModalVisible(true)}}>
<View style={styles.addFriendButton}>
<Text style={styles.addFriendButtonText}>Add friends</Text>
</View>
</TouchableHighlight>
</View>
)
}
render(){
return (
<ScrollView style={styles.container}>
<Modal
animationType={'fade'}
transparent={true}
visible={this.state.isModalVisible}
>
<View style={styles.addFriendModalContainer}>
<View style={styles.addFriendModal}>
<TouchableHighlight onPress={() => {this.setModalVisible(false)}}>
<View>
<Text>Close</Text>
</View>
</TouchableHighlight>
<ListView
dataSource={friends}
renderRow={(friend) => {
return (
<FriendRow
friend={friend}
pickContact={this.pickContact}
isSelected={this.state.friendsState[friend.id]}
/>
)
}}
/>
</View>
</View>
</Modal>
{this._renderAddFriendTile()}
</ScrollView>
)
}
}
class FriendRow extends Component {
render(){
return(
<TouchableHighlight onPress={() => {this.props.pickContact(this.props.friend)}}>
<View style={[styles.row, styles.friendRow]}>
<Image source={this.props.friend.image} style={styles.friendIcon}></Image>
<Text style={styles.name}>{this.props.friend.firstname} </Text>
<Text style={styles.name}>{this.props.friend.surname}</Text>
<View style={styles.roundIconContainer}>
<View style={styles.roundIcon}>
<View style={this.props.isSelected ? [styles.buttonActive, styles.buttonSmall]: [styles.modalButtonInactive, styles.buttonSmall]}>
<Image source={this.props.isSelected && require('../images/button-tick-small-on.png')} style={styles.buttonTickSmall}></Image>
</View>
</View>
</View>
</View>
</TouchableHighlight>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#e1e1e1'
},
row: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
step: {
backgroundColor: '#ffffff',
borderRadius: 4,
borderLeftWidth: 5,
flex: 1,
marginLeft: 10,
marginRight: 10,
marginBottom: 10,
paddingLeft: 15,
paddingRight: 10,
paddingTop: 15,
paddingBottom: 20,
shadowOffset: {
width: 0,
height: 2,
},
shadowRadius: 2,
shadowOpacity: 0.2,
shadowColor: '#000000',
},
stepThree: {
borderLeftColor: '#ffbd18',
},
heading: {
textAlign: 'center',
fontWeight: 'bold',
fontSize: 15,
color: '#333333',
},
addFriendButtonContainer: {
marginTop:15,
flex:1,
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'row',
},
addFriendButton: {
backgroundColor: '#ffbd18',
width: 270,
borderRadius: 4,
paddingTop: 15,
paddingBottom: 15,
},
addFriendButtonText: {
color: '#ffffff',
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
pickContainer: {
flex:1,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
borderRightWidth: 1,
},
pickWrapper: {
flex: 1,
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
marginTop: 10,
},
buttonBig: {
height: 60,
width: 60,
borderRadius: 30,
},
buttonSmall: {
height: 20,
width: 20,
borderRadius: 10,
},
buttonActive: {
backgroundColor: '#fd6769',
alignItems: 'center',
justifyContent: 'center',
},
buttonInactive: {
backgroundColor: '#eeeeee',
alignItems: 'center',
justifyContent: 'center',
},
removeButton:{
backgroundColor: '#cccbcb',
alignItems: 'center',
justifyContent: 'center',
},
modalButtonInactive: {
backgroundColor: '#ffffff',
borderWidth: 1,
borderColor: '#eeeeee',
},
buttonTickBig: {
width: 34,
height: 28,
},
buttonTickMinusBig: {
width: 18,
height: 8,
},
buttonTickSmall: {
width: 12,
height: 10,
},
crossIconSmall: {
width: 12,
height: 10,
},
pickText: {
color: '#c7c7c7',
fontWeight: 'bold',
},
addFriendModalContainer: {
flex: 1,
},
addFriendModal: {
flex: 1,
backgroundColor: '#ffffff',
borderRadius: 4,
paddingLeft: 15,
paddingRight: 10,
paddingTop: 20,
paddingBottom: 20,
shadowOffset: {
width: 0,
height: 2,
},
shadowRadius: 2,
shadowOpacity: 0.2,
shadowColor: '#000000',
textAlign: 'center',
},
nextButtonContainer: {
marginBottom: 20,
},
nextButton: {
textAlign:'right',
},
friendRow: {
height: 60,
borderBottomWidth: 1,
borderBottomColor: '#eeeeee',
justifyContent: 'flex-start',
},
friendIcon: {
width: 50,
height: 50,
marginRight: 25,
},
roundIconContainer:{
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'flex-end',
},
roundIcon: {
height: 20,
width: 20,
borderRadius: 10,
backgroundColor: '#fd6769',
justifyContent: 'center',
alignItems: 'center',
marginRight: 20,
},
})
export default AppView
'using splice but it removes all the contacts at once.'
Because you're using splice method wrong. Check the parameters the method gets.
http://www.w3schools.com/jsref/jsref_splice.asp
[ ...this.state.contactsPicked, this.state.contactsPicked.splice(friend)]
this doesn't work as you expected as well. It merges two arrays.
var parts = ['shoulders', 'knees'];
var parts2 = ['shoulders'];
var lyrics = [ ...parts, ...parts2 ];
console.log(lyrics)
It seems you don't have to use spread operator([...arr,.arr2]),
you can simply do that
Since it re creates subs every time modal's visiblity changes and even if you change state of parent component's state it does not re render subs so you need to keep internal state for you sub components
Also your _renderAddFriendTile method was working wrong too. When
you take a look at it carefully you will realize your mistake.
Don't forget to change your icons with my test icons
class AppView extends Component {
constructor(props) {
super(props);
this.state = {
isModalVisible: false,
contactsPicked: [],
friendsState: {},
}
}
setModalVisible = visible => {
this.setState({isModalVisible: visible})
}
pickContact = (friend) => {
if(this.state.contactsPicked.indexOf(friend) < 0){
var tempFriendsState = this.state.friendsState
tempFriendsState[friend.id] = true
this.setState({
contactsPicked: [ ...this.state.contactsPicked, friend],
friendsState: tempFriendsState,
})
}
else{
let index = this.state.contactsPicked.indexOf(friend)
let nextContacts = this.state.contactsPicked
nextContacts.splice(index,1)
let tempFriendsState = this.state.friendsState
tempFriendsState[friend.id] = false
this.setState({
contactsPicked: nextContacts,
friendsState: tempFriendsState,
})
}
}
removeContact = (friend) => {
let index = this.state.contactsPicked.indexOf(friend)
let nextContacts = this.state.contactsPicked
let tempFriendsState = this.state.friendsState
tempFriendsState[friend.id] = false
nextContacts.splice(index,1)
console.log('removeContact'+friend.id);
this.setState({
contactsPicked: nextContacts,
friendsState: tempFriendsState,
})
}
_renderAddFriendTile = () => {
return(
<View style={[styles.step, styles.stepThree]}>
<View style={{flex:1}}>
<Text style={styles.heading}>Friend tile</Text>
</View>
{ (this.state.contactsPicked.length) > 0 ?
this.state.contactsPicked.map((contact,index) => (
<TouchableHighlight onPress={() => {this.removeContact(contact)}}>
<View>
<View key={index} style={[styles.row, styles.friendRow]}>
<Image source={contact.image} style={styles.friendIcon}></Image>
<Text style={styles.name}>{contact.firstname} </Text>
<Text style={styles.name}>{contact.surname}</Text>
<View style={styles.roundIconContainer}>
<View style={styles.roundIcon}>
<View style={[styles.removeButton, styles.buttonSmall]}>
<Image source={require('./images/redtree.jpg')} style={styles.crossIconSmall}></Image>
</View>
</View>
</View>
</View>
</View>
</TouchableHighlight>
))
: null
}
<TouchableHighlight style={styles.addFriendButtonContainer} onPress={() => {this.setModalVisible(true)}}>
<View style={styles.addFriendButton}>
<Text style={styles.addFriendButtonText}>Add friends</Text>
</View>
</TouchableHighlight>
</View>
)
}
render(){
return (
<ScrollView style={styles.container}>
<Modal
animationType={'fade'}
transparent={true}
visible={this.state.isModalVisible}
>
<View style={styles.addFriendModalContainer}>
<View style={styles.addFriendModal}>
<TouchableHighlight onPress={() => {this.setModalVisible(false)}}>
<View>
<Text>Close</Text>
</View>
</TouchableHighlight>
<ListView
dataSource={friends}
renderRow={(friend) => {
return (
<FriendRow
friend={friend}
pickContact={this.pickContact}
isSelected={this.state.friendsState[friend.id]}
/>
)
}}
/>
</View>
</View>
</Modal>
{this._renderAddFriendTile()}
</ScrollView>
)
}
}
class FriendRow extends Component {
constructor(props) {
super(props);
this.state = {
isSelected:this.props.isSelected,
}
}
componentDidMount(){
console.log('didmount');
}
render(){
var imageSource = (this.state.isSelected==true) ? require('./images/tree.jpg') : ''
console.log('friend'+!this.props.isSelected)
return(
<TouchableHighlight onPress={() => {this.props.pickContact(this.props.friend);this.setState({isSelected:!this.state.isSelected})}}>
<View style={[styles.row, styles.friendRow]}>
<Image source={this.props.friend.image} style={styles.friendIcon}></Image>
<Text style={styles.name}>{this.props.friend.firstname} </Text>
<Text style={styles.name}>{this.props.friend.surname}</Text>
<View style={styles.roundIconContainer}>
<View style={styles.roundIcon}>
<View style={this.state.isSelected ? [styles.buttonActive, styles.buttonSmall]: [styles.modalButtonInactive, styles.buttonSmall]}>
<Image source={imageSource} style={styles.buttonTickSmall}></Image>
</View>
</View>
</View>
</View>
</TouchableHighlight>
)
}
}
You can use filter method to filter out contacts if it's already there in view.
To remove contacts from view if already added:
this.setState({
...state,
contactsPicked: this.state.contactsPicked.filter((contactsPicked,index)=> index != pickedContactsIndex)
})

Categories