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.
Related
I am using FlatList in my component which is working fine. All is wanted is that my entire screen should have an automatic height to it and FlatList contents never does out of the screen height.
Also, the height of FlatList should be changed based on content/items in it rather than giving any fixed height.
How I can be acheived both cases like automatic screen height and auto height of FlatList content?
Code:
import React, { useState, useEffect } from 'react';
import {
View,
StyleSheet,
useWindowDimensions,
Text,
FlatList,
SafeAreaView,
TouchableOpacity,
} from 'react-native';
import { LinearGradient as LinearGradientView } from 'expo-linear-gradient';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { ActivityIndicator, ThemeProvider } from 'react-native-paper';
import { ScrollView } from 'react-native-gesture-handler';
import { useIsFocused, useNavigation } from '#react-navigation/native';
import Svg, { Path } from 'react-native-svg';
import { BASE_GRADIENT_HEAVY } from '../rooms/Background';
import { useResolution } from '../browse/useResolution';
import { OnboardingNavigationProp } from '../navigation/LinkingConfiguration';
import { LIGHT_THEME } from '../Theme';
import { OnboardingBack } from './OnboardingBack';
import {
useOnboardingYearsOfExperiencePage,
useOnboardingYearsOfExperienceSubmit,
} from './useOnboardingYearsOfExperiencePage';
export function OnboardingYearsOfExperience({ route }: { route?: any }) {
const { height } = useWindowDimensions();
const { top: safeAreaTop } = useSafeAreaInsets();
const [selectedId, setSelectedId] = useState<{ id: string }>();
const [role_id, setRoleId] = useState<number>();
const { navigate } =
useNavigation<OnboardingNavigationProp<'OnboardingConfirmAllDetails'>>();
const parentUrl = route?.params.result.onboarding._links;
const initialUrl = parentUrl.onboarding_years_of_experience.href;
const onboardingUrl = parentUrl.self.href;
useEffect(() => {
setRoleId(route?.params.role_id);
}, []);
const isFocused = useIsFocused();
const { contentType } = useResolution();
const {
data: result,
isLoading,
error,
} = useOnboardingYearsOfExperiencePage(initialUrl, contentType, role_id, {
enabled: isFocused,
notifyOnChangeProps: ['data', 'isLoading', 'error'],
});
const {
mutateAsync: updateYearsOfExperience,
isLoading: isUpdatingYearsOfExperience,
error: yearsOfExperienceUpdateError,
} = useOnboardingYearsOfExperienceSubmit(onboardingUrl);
const Arrow = ({ style }: { style: object }) => {
return (
<Svg width="7" height="11" viewBox="0 0 7 11" fill="none" style={style}>
<Path
d="M6.28711 5.53931C6.28711 5.31649 6.19783 5.12476 6.02452 4.95376L1.9911 1.07251C1.85455 0.937781 1.68649 0.865234 1.48692 0.865234C1.07728 0.865234 0.751664 1.18651 0.751664 1.58552C0.751664 1.78243 0.830441 1.96898 0.977493 2.11407L4.54875 5.53413L0.977492 8.95937C0.835692 9.10446 0.751663 9.28583 0.751662 9.48792C0.751662 9.88693 1.07728 10.2082 1.48692 10.2082C1.68649 10.2082 1.85455 10.1357 1.99635 10.0009L6.02452 6.11968C6.20308 5.93832 6.28711 5.75695 6.28711 5.53931Z"
fill="#4D4D4D"
/>
</Svg>
);
};
const Item = ({
item,
onPress,
}: {
item?: { id: string; title: string };
onPress: () => void;
}) => (
<TouchableOpacity onPress={onPress}>
<Text
style={{
color: '#4D4D4D',
fontFamily: 'Inter_400Regular',
fontStyle: 'normal',
fontWeight: '400',
fontSize: 16,
lineHeight: 32,
padding: 3,
margin: 2,
}}
>
{item?.title}
</Text>
</TouchableOpacity>
);
const renderItem = ({ item }: { item: { id: string; title: string } }) => {
return (
<React.Fragment>
<View
style={{
flexDirection: 'row',
justifyContent: 'space-between',
borderRadius: 5,
backgroundColor:
selectedId?.id === item?.id ? '#F2F2F2' : 'transparent',
}}
>
<View style={{ width: '90%' }}>
<Item
item={item}
onPress={() => {
setSelectedId({ id: item.id });
updateYearsOfExperience({ id: item.id, role_id })
.then((result) => {
navigate('Onboarding', {
screen: 'OnboardingConfirmAllDetails',
params: { result },
});
})
.catch(() => {});
}}
/>
</View>
<View style={{ width: '10%' }}>
<Arrow style={{ position: 'absolute', top: 16, right: 10 }} />
</View>
</View>
</React.Fragment>
);
};
return (
<ThemeProvider theme={LIGHT_THEME}>
<View style={{ height: safeAreaTop }} />
<View style={styles.topHeader}>
<LinearGradientView
{...BASE_GRADIENT_HEAVY}
style={[styles.gradiantStyle]}
/>
</View>
<View style={styles.innerContainer}>
<ScrollView
contentContainerStyle={{
marginHorizontal: 'auto',
alignSelf: 'center',
width: '100%',
backgroundColor: '#ffffff',
height: '100%',
}}
>
<OnboardingBack
style={{
left: -10,
}}
/>
<Text style={styles.topHeadline}>
How many years of experience do you have?
</Text>
<Text style={styles.middleHeadline}>
Jump-start the conversation by sharing your years of experience
within healthcare.
</Text>
{isLoading || isUpdatingYearsOfExperience ? (
<ActivityIndicator size="large" />
) : (
<SafeAreaView>
<FlatList
contentContainerStyle={{ flexGrow: 1 }}
data={Object.values(
result!['years_of_experiences']['years_of_experiences_list']
)}
renderItem={renderItem}
keyExtractor={(item) => item.id}
extraData={selectedId}
style={{
borderWidth: 1,
borderStyle: 'solid',
borderColor: '#D7D7D7',
borderRadius: 5,
padding: 12,
height: '27%',
}}
ListEmptyComponent={
<View>
<Text>No data found</Text>
</View>
}
/>
</SafeAreaView>
)}
</ScrollView>
</View>
</ThemeProvider>
);
}
const styles = StyleSheet.create({
innerContainer: {
display: 'flex',
position: 'relative',
backgroundColor: 'white',
padding: 18,
},
topHeader: {
backgroundColor: '#e5dede',
padding: 0,
width: '100%',
},
topHeadline: {
fontSize: 24,
fontFamily: 'CircularStd_Medium',
fontStyle: 'normal',
fontWeight: '500',
color: '#222222',
marginTop: 40.6,
marginBottom: 14.6,
display: 'flex',
alignItems: 'center',
lineHeight: 32,
},
middleHeadline: {
fontSize: 16,
fontFamily: 'Inter_400Regular',
fontStyle: 'normal',
fontWeight: '400',
lineHeight: 24,
color: '#4D4D4D',
marginBottom: 30,
},
gradiantStyle: {
width: 248.4,
height: 4.71,
},
});
add the following style to your flatList =>
style={{maxHeight:'what ever you want',flexGrow:0}}
i have a problem updating the parent state. indeed I want to change the edge of an edge after the user has clicked and chosen his level of performance in sport. after having chosen its level of performance, it is then that the selected sport takes a blue border.
please i need help
Sport.js
import React, { useState, useEffect } from "react";
import {
Modal,
Alert,
View,
SafeAreaView,
StyleSheet,
Image,
Text,
TouchableOpacity,
TouchableHighlight,
ScrollView,
} from "react-native";
import AppLoading from "expo-app-loading";
import { useFonts } from "expo-font";
import * as Font from "expo-font";
import { useNavigation } from "#react-navigation/native";
import { ProgressBar, Colors } from "react-native-paper";
import { Ionicons } from "#expo/vector-icons";
import SportItem from "../../Components/SportItem";
import HeadCSearch from "../../Components/HeadCSearch";
import SearchTitle from "../../Components/SearchTitle";
import ModalPicker from "./ModalPicker";
export default function Sport() {
const navigation = useNavigation();
const [chooseData, setchooseData] = useState("0%");
const [sports, setSports] = useState([
{
id: 1,
urlIcon: "ios-football",
name: "Football",
},
{
id: 2,
urlIcon: "hand-left-sharp",
name: "Handball",
},
{
id: 3,
urlIcon: "ios-basketball",
name: "Basketball",
},
{
id: 4,
urlIcon: "ios-car-sport",
name: "Course",
},
{
id: 5,
urlIcon: "ios-baseball",
name: "BaseBall",
},
{
id: 6,
urlIcon: "ios-tennisball",
name: "Tennis",
},
/* {
id: 7,
urlIcon:
"https://img.icons8.com/material-outlined/50/000000/basketball-net.png",
name: "Basketball",
}, */
]);
const [toggle, setToggle] = useState(false);
const [modalOpen, setModalOpen] = useState(false);
const changeModalVisibility = (bool, id) => {
setModalOpen(bool);
sports.map((sport) => {
if (id === sport.id) {
const newState = !toggle;
setToggle(newState);
}
});
};
const setData = (option) => {
setchooseData(option);
};
const OPTIONS = ["Débutant", "Moyen", "Intermédiaire", "Expert"];
let [fontsLoaded] = useFonts({
"Gilroy-ExtraBold": require("../../assets/fonts/Gilroy-ExtraBold.otf"),
"Gilroy-Light": require("../../assets/fonts/Gilroy-Light.otf"),
});
if (!fontsLoaded) {
return <AppLoading />;
} else {
const borderColorValue = toggle ? "#49B5F2" : "white";
return (
<SafeAreaView style={styles.infoCont}>
<ProgressBar
style={{ marginTop: 35, borderRadius: 10 }}
progress={1}
color="#49B5F2"
/>
<View style={styles.containWhite}>
<SearchTitle
styl={styles.whiteText}
partenaire={"Choississez vos Sports Favoris"}
/>
{/* <Modal visible={modalOpen} animationType="slide" transparent={true}>
<View style={{ flex: 1 }}>
<Text>Salut toi</Text>
</View>
</Modal> */}
<Modal
animationType="slide"
transparent={true}
visible={modalOpen}
onRequestClose={() => {
changeModalVisibility(false);
}}
>
<ModalPicker
sports={sports}
OPTIONS={OPTIONS}
setData={setData}
changeModalVisibility={changeModalVisibility}
/>
</Modal>
<ScrollView
contentContainerStyle={{
backgroundColor: "#FFFFFF",
flexWrap: "wrap",
flexDirection: "row",
/* borderColor: "yellow",
borderWidth: 3,
borderStyle: "solid", */
}}
>
{sports.map((sport) => (
<TouchableOpacity
onPress={() => {
changeModalVisibility(true, sport.id);
}}
style={{
alignItems: "center",
display: "flex",
flexDirection: "row",
bottom: 10,
width: 150,
height: 50,
margin: 10,
backgroundColor: "#E4E9DD",
borderColor: borderColorValue,
borderWidth: 1,
borderRadius: 5,
}}
key={sport.id}
>
<View
style={{
alignItems: "center",
display: "flex",
flexDirection: "row",
}}
>
<Ionicons
style={{ left: 8, top: 0 }}
name={sport.urlIcon}
size={20}
color="black"
/>
<Text
style={{
fontFamily: "Gilroy-Light",
fontWeight: "bold",
fontSize: 16,
color: "#000000",
left: 15,
}}
>
{sport.name}
</Text>
</View>
<View style={styles.rate}>
<Text style={styles.rateText}>{chooseData}</Text>
</View>
</TouchableOpacity>
))}
</ScrollView>
</View>
<TouchableOpacity
onPress={() => navigation.navigate("Search")}
style={styles.textinputcont}
>
<Text style={styles.textR}>Continuer</Text>
</TouchableOpacity>
</SafeAreaView>
);
}
}
ModalPicker.js
import React from "react";
import {
View,
Dimensions,
TouchableOpacity,
StyleSheet,
TouchableHighlight,
Text,
} from "react-native";
import { Fontisto } from "#expo/vector-icons";
import SearchTitle from "../../Components/SearchTitle";
export default function ModalPicker(props) {
const WIDTH = Dimensions.get("window").width;
const HEIGHT = Dimensions.get("window").height;
const onPressItem = (option) => {
props.changeModalVisibility(false);
props.setData(option);
};
const option = props.OPTIONS.map((item, index) => {
return (
<TouchableOpacity
style={styles.option}
key={index}
onPress={() => onPressItem(item)}
>
<Text
style={[
styles.textStyle,
{
width: 100,
height: 40,
backgroundColor: "#F7F7F7",
borderRadius: 4,
/* borderColor: "#49B5F2",
borderWidth: 1,
borderStyle: "solid", */
color: "black",
flexDirection: "row",
margin: 2,
top: 5,
flex: 1,
//top: 15,
justifyContent: "center",
alignItems: "center",
},
]}
>
{item}
</Text>
</TouchableOpacity>
);
});
return (
<TouchableOpacity
onPress={() => props.changeModalVisibility(false)}
style={styles.container}
>
<View style={styles.centeredView}>
<View
style={[styles.modalView, { width: WIDTH - 85, height: HEIGHT / 3 }]}
>
<SearchTitle
styl={[styles.whiteText, { top: -50 }]}
partenaire={"Votre Niveau"}
/>
<Text style={[styles.modalText, styles.centeredView, { top: -50 }]}>
{option}
</Text>
<TouchableOpacity
style={{
...styles.openButton,
top: -30,
left: 30,
}}
onPress={() => {
console.log("remove border color");
}}
>
<Text style={styles.textStyle}>Retirez des favoris {" "} </Text>
<Fontisto
style={{ position: "absolute", right: 0, top: 10 }}
name="minus-a"
size={20}
color="black"
/>
</TouchableOpacity>
</View>
</View>
</TouchableOpacity>
);
}
[enter image description here][1]
you need to have the styles dynamically inserted into your code.
and having an event raised from the child component and handled in the parent component, the following code can help to understand that
class Parent extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
backgroundColor: 'yellow'
}
}
onChangeStyle(backgroundColor) {
this.setState({
backgroundColor: backgroundColor
})
}
render() {
return <div style={{backgroundColor: this.state.backgroundColor, padding: 10}}>
<Child onChangeParentStyle={this.onChangeStyle.bind(this)}/>
</div>
}
}
class Child extends React.Component {
onClick() {
this.props.onChangeParentStyle('red');
}
render() {
return <span onClick={this.onClick.bind(this)} style={{background: 'white', cursor: 'pointer'}}>
Change parent style
</span>
}
}
React.render(<Parent />, document.getElementById('container'));
You need to pass from parent to child callback function, and then call it in the child.
const initState = {
show: false
}
const Parent = (props) => {
const [state, setState] = useState({ ...initState })
const updateState = (childState) => {
setState({
show: !state.show
});
}
return (
<Child {...{ updateState }} />
);
}
const Child = (props) => {
const [state, setState] = useState({})
const handleClick = () => {
// You can also pass data to parent
props.updateState(state);
}
return (
<Button onClick={handleClick}>Click Me</Button>
);
}
Below is the code component for the customer picker
import React, { useEffect, useState } from "react";
import { connect } from 'react-redux';
import {
TouchableOpacity,
FlatList,
SafeAreaView,
StatusBar,
StyleSheet,
Text,
View,
Button,
Alert,
} from "react-native";
import { screenHeight, screenWidth } from "../constants";
const DATA = [
{
id: "bd7acbea-c1b1-46c2-aed5-3ad53abb28ba",
title: "Client Not Found"
},
{
id: "58694a0f-3da1-471f-bd96-145571e29d72",
title: "Client refused"
},
];
const Item = ({ item, onPress, style }) => (
<TouchableOpacity onPress={onPress} style={[styles.item, style]}>
<Text style={styles.title}>{item.title}</Text>
</TouchableOpacity>
);
const StatusOptions = (props) => {
const [selectedId, setSelectedId] = useState(null);
const renderSeparator = () => (
<View
style={{
backgroundColor: "grey",
height: 0.8
}}
/>
);
const ListHeader = () => {
//View to set in Header
return (
<View style={{ height: 20 }}></View>
);
};
const renderItem = ({ item }) => {
const backgroundColor = item.id === selectedId ? "#6cd9ff" : "white";
return (
<Item
item={item}
onPress={() => {
setSelectedId(item.id);
console.log("SELECTED ID _ STATUSOPTIONS component : ", selectedId);
const val = DATA.filter(status => status.id == selectedId).map(filteredStatus => filteredStatus.title);
console.log("VALLLLLLLLLLLLLLLLLLLLLLLUUUUUUEEEEEEEEEEEEEEEEEEEE:::: ", val);
props.navigation.navigate('AnotherScreen');
}}
style={{ backgroundColor }}
/>
);
};
return (
<View style={{ bottom: 0, flex: 1, position: 'absolute', width: screenWidth, }}>
<View style={styles.container}>
<FlatList
data={DATA}
renderItem={renderItem}
keyExtractor={(item) => item.id}
extraData={selectedId}
ItemSeparatorComponent={renderSeparator}
ListHeaderComponent={ListHeader}
style={{
backgroundColor: "white",
width: "100%",
borderTopRightRadius: 20,
borderTopLeftRadius: 20,
zIndex: 1,
}}
/>
</View>
<View style={{ backgroundColor: "grey", height: 0.4 }} />
<View style={styles.closeButtonContainer}>
<TouchableOpacity style={styles.closeButton}
onPress={() => {
props.setStatusOptionsVisible(false)
}}>
<Text style={styles.title}>Close</Text>
</TouchableOpacity>
</View>
</View>
);
};
function mapStateToProps(state) {
return {
StatusOptionsVisible: state.volunteerItems.statusOptionsVisible,
currentTaskItemId: state.taskItems.taskItemId,
};
}
function mapDispatchToProps(dispatch) {
return {
setStatusOptionsVisible: (visible) => dispatch({ type: 'SET_STATUS_VISIBLE', statusVisibility: visible }),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(StatusOptions);
const styles = StyleSheet.create({
closeButton: {
backgroundColor: 'lightgrey',
borderRadius: 10,
height: 50,
justifyContent: 'center',
width: '90%',
},
closeButtonContainer: {
alignItems: 'center',
height: 90,
justifyContent: 'center',
backgroundColor: 'white',
},
textStyle: {
textAlign: "center",
},
container: {
borderRadius: 30,
flex: 4,
width: screenWidth,
},
item: {
padding: 20
},
title: {
fontSize: 20,
fontWeight: 'bold',
color: "black",
textAlign: "center"
}
});
the console Log : ("SELECTED ID _ STATUSOPTIONS component : ", selectedId)
in render Item function returns null for first picker item selection and the returns the previous value for the next picker item selection , can anyone please help with fixing it ?
Try to use this
useEffect(() => {
console.log("SELECTED ID _ STATUSOPTIONS component : ", selectedId);
if(selectedId != null) {
const val = DATA.filter(status => status.id == selectedId).map(filteredStatus => filteredStatus.title);
console.log("VALLUUEEEEEEEEEEEEEEEEEEEE:::: ", val);
}
}, [selectedId])
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>
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,
}
});