React Native initialised index to pass props - javascript

I am making a music app using react-native-track-player. I made 3 classes called Clusters, Songlist and Play class.
How screen works
Clusters class -> Songlist class -> Play class. Problem for me is from Songlist to Play when I pass props from Songlist class to Play class, I would get error possible unhandled promise rejection and the song is not playing. I created data. First screen shows title and mood(Songlist class). Second screen(Songlist shows the playlist depending on the title that I clicked. Then the Third screen (Play) is where I will get an error after I try to pass props.
This is my where I get my data in another file
const ClusterData = [
{ title: 'Cluster1',
data:
[
{name: 'passionate'},
{name: 'rousing'},
{name: 'confident'},
{name: 'boisterous'},
{name: 'rowdy'}
],
songlist:
[
{
id: '2222',
url: 'http://tegos.kz/new/mp3_full/Post_Malone_-_Better_Now.mp3',
title: 'Better Now',
artist: 'Post Malone',
},
{
id: '2',
url: 'http://tegos.kz/new/mp3_full/5_Seconds_Of_Summer_-_Youngblood.mp3',
title: 'YoungBlood',
artist: '5SOS',
},
]
},
{ title: 'Cluster2',
data:
[
{name: 'rollicking'},
{name: 'cheerful'},
{name: 'fun'},
{name: 'sweet'},
{name: 'amiable'},
{name: 'natured'}
],
songlist:
[
{
id: '1111',
url: 'http://tegos.kz/new/mp3_full/Yellow_Claw_and_San_Holo_-_Summertime.mp3',
title: 'Summertime',
artist: 'Yellow Claw',
},
{
id: '1',
url: 'http://tegos.kz/new/mp3_full/Luis_Fonsi_feat._Daddy_Yankee_-_Despacito.mp3',
title: 'Despacito',
artist: 'Luis Fonsi',
},
]
},
This is my Clusters screen (first screen)
export default class Clusters extends Component{
render(){
return(
<View style={styles.container}>
<SectionList
renderItem={({item,index})=>{
return(
<SectionListItem item={item} index={index}> </SectionListItem>);}}
renderSectionHeader={({ section }) => {
return (<SectionHeader section={section} />);}}
sections={ClusterData}
keyExtractor={(item, index) => item.name}>
</SectionList>
</View>
);
}}
class SectionHeader extends Component {
render() {
return (
<View style={styles.header}>
<Text style={styles.headertext}>
{this.props.section.title}
</Text>
<TouchableOpacity onPress={ () => Actions.SongList({ section: this.props.section}) }>
<Text style ={styles.Play}> Play
</Text>
</TouchableOpacity>
</View>
);
}}
class SectionListItem extends Component{
render(){
return(
<View>
<Text style={styles.moodname}>{this.props.item.name}</Text>
</View>
);
}
}
This is my SongList screen (second screen)
export default class App extends Component {
render() {
return (
<View>
<FlatList
data={this.props.section.songlist}
renderItem={({item,index,rowId})=>{
return(
<FlatListItem item={item} index={index}>
</FlatListItem>);}}>
</FlatList>
</View>
);}}
class FlatListItem extends Component{
render(){
return(
<View>
<TouchableOpacity onPress={ () => Actions.Play({songlist: this.props.item.songlist, item: this.props.item}) }>
<Text style={styles.itemTitle}>{this.props.item.songtitle}</Text>
<Text style={styles.itemArtist}>{this.props.item.artist}</Text>
</TouchableOpacity>
</View>
);}}
So when I click on the songtitle/artist, the app will stop. I think the error could be await TrackPlayer.add(this.props.item.songlist[index]); but I am not sure.
This is my Play screen
import TrackPlayer from 'react-native-track-player';
export default class Play extends Component{
componentDidMount()
{
TrackPlayer.setupPlayer().then(async () => {
// Adds a track to the queue
await TrackPlayer.add(this.props.item.songlist[index]);
// Starts playing it
TrackPlayer.play();
});
}
onPressPlay = () => {
TrackPlayer.play();
};
onPressPause = () => {
TrackPlayer.pause();
};
render() {
return (
<View style={styles.container}>
<View style= {{flexDirection :'column'}}>
<TouchableOpacity style= {styles.play} onPress = {this.onPressPlay}>
<Text style = {{fontWeight:'bold',textAlign:'center',color:'white'}}>Play</Text>
</TouchableOpacity>
<TouchableOpacity style= {styles.pause} onPress = {this.onPressPause}>
<Text style = {{fontWeight:'bold',textAlign:'center',color:'white'}}>Pause</Text>
</TouchableOpacity>
</View>
</View>
);}}

TrackPlayer.setupPlayer().then(async () => {
// Adds a track to the queue
await TrackPlayer.add(this.props.item.songlist[index])
// Starts playing it
TrackPlayer.play();
});
the variable index is undefined, and that's why your app stops

Related

Changing values in a dropdown menu

I'm working with a custom dropdown menu in React Native and am having a problem changing the text when trying to reuse the dropdown in other components.
DropdownMenu.js
export const DropdownMenu = ({dropdownMenuItems}) => {
const [isActive, setIsActive] = React.useState(false);
const onPress = () => {
setIsActive(!isActive);
};
return (
<TouchableOpacity
activeOpacity={1}
onPress={() => setIsActive(false)}
style={{flex: 1}}>
<View>
<TouchableOpacity style={styles.img} onPress={onPress}>
<Image style={styles.imgimg} source={require('./icon.png')} />
</TouchableOpacity>
<Animated.View
style={{
...styles.menu,
opacity: fadeAnim,
transform: [{translateY: translateAnim}],
}}
pointerEvents={isActive ? 'auto' : 'none'}>
<FlatList
data={dropdownMenuItems}
renderItem={({item, index}) => (
<OpenURLButton
url={item.href}
label={item.name}
style={
index === 0
? {borderTopLeftRadius: 8, borderTopRightRadius: 8}
: index === dropdownMenuItems.length - 1
? {borderBottomLeftRadius: 8, borderBottomRightRadius: 8}
: {}
}
/>
)}
/>
</Animated.View>
</View>
</TouchableOpacity>
);
};
In the CompactMenu component, I import my <DropdownMenu /> and set my initial values for my menu:
CompactMenu.js
import React from 'react';
import {SafeAreaView} from 'react-native';
import {DropdownMenu} from './DropdownMenu';
const CompactMenu = () => {
const backgroundStyle = {
backgroundColor: '#fff',
flex: 1,
display: 'flex',
};
const dropdownMenuItems = [
{name: 'Messages', href: '/messages'},
{name: 'Trips', href: '/trips'},
{name: 'Saved', href: '/saved'},
];
return (
<SafeAreaView style={backgroundStyle}>
<DropdownMenu dropdownMenuItems={dropdownMenuItems} />
</SafeAreaView>
);
};
export default CompactMenu;
After importing <CompactMenu /> into another component, I try to change the name & the href in object:
example import:
<CompactMenu dropdownMenuItems={[{name: "changed name", href: "/somePath"}]} />
However, the same strings from CompactMenu.js are displayed in the dropdown.
Being new to RN, I'm not sure of two things here.
1.) Why do the text value for "name" not change when used in a different component?
2.) Shouldn't navigation to another screen use { navigation } instead of href:? I've tried adding onPress={() => navigation.navigate('SomeScreen') in the href but I get an error.
I'm not sure what the correct solution to this is. Any help would be appreciated.
You are not using the props dropdownMenuItems that you are passing to CompactMenu. Instead, you reuse the same menu items for the DropdownMenu component everytime you create a CompactMenu.
You need to use the props that you are passing. I have kept the static items as a default value. If you would like to have these items as well and to add additional items via props, then have a look at the second solution.
Notice that I have integrated some small changes to the rest of the code as well.
const defaultItems = [
{name: 'Messages', href: '/messages'},
{name: 'Trips', href: '/trips'},
{name: 'Saved', href: '/saved'},
];
const CompactMenu = ({dropdownMenuItems = defaultItems}) => {
return (
<SafeAreaView style={styles.backgroundStyle}>
<DropdownMenu dropdownMenuItems={dropdownMenuItems} />
</SafeAreaView>
);
};
const styles = StyleSheet.create({
backgroundStyle: {
backgroundColor: '#fff',
flex: 1,
}
});
Now using the CompactMenu component can receive dropdownMenuItems and will pas them to the DropdownMenu component.
<CompactMenu dropdownMenuItems={[{name: "changed name", href: "/somePath"}]} />
If you want to keep default items and add additional items via props, we could achieve this by merging the provided props with our default items.
const defaultItems = [
{name: 'Messages', href: '/messages'},
{name: 'Trips', href: '/trips'},
{name: 'Saved', href: '/saved'},
];
const CompactMenu = ({dropdownMenuItems}) => {
return (
<SafeAreaView style={styles.backgroundStyle}>
<DropdownMenu dropdownMenuItems={[...defaultItems, ...dropdownMenuItems]} />
</SafeAreaView>
);
};
Your second questions addresses the react-navigation framework for react-native. This is a very broad topic and I am assuming from your question that you don't know how this works yet, since you have not setup the necessary structure for using it. I encourage you to go through the documentation first.
In summary, you will need to define a navigator, e.g. a stack-navigator and add a name reference for each of your screens to the dropdown menu. To keep things short, here is a minimal example on how this could work.
const MenuScreen1 = (props) {
return (...)
}
const MenuScreen2 = (props) {
return (...)
}
const CompactMenu = ({dropdownMenuItems, navigation}) => {
return (
<SafeAreaView style={styles.backgroundStyle}>
<DropdownMenu dropdownMenuItems={dropdownMenuItems} navigation={navigation} />
</SafeAreaView>
);
};
const dropdownMenuItems = [
{name: 'Menu Item 1', screen: 'Item1'},
{name: 'Menu Item 2', screen: 'Item2'},
]
const Home = ({navigation}) {
return <CompactMenu navigation={navigation} dropdownMenuItems={dropdownMenuItems} />
}
const Stack = createNativeStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Item1" component={MenuScreen1} />
<Stack.Screen name="Item2" component={MenuScreen2} />
</Stack.Navigator>
</NavigationContainer>
);
}
export default App;
Notice that I have created two screens whose names, which I have defined in the stack navigator, are provided to the CompactMenu. Notice as well that the navigation framework will pas the navigation object only to components that are defined as a screen, thus I have decided to pass the navigation object to the CompactMenu and the DropdownMenu component. You can use the useNavigation hook if you prefer this method.
Now, in your DropdownMenu you can navigate on click to the defined screens.
export const DropdownMenu = ({dropdownMenuItems, navigation}) => {
const [isActive, setIsActive] = React.useState(false);
const onPress = () => {
setIsActive(!isActive);
};
return (
<TouchableOpacity
activeOpacity={1}
onPress={() => setIsActive(false)}
style={{flex: 1}}>
<View>
<TouchableOpacity style={styles.img} onPress={onPress}>
<Image style={styles.imgimg} source={require('./icon.png')} />
</TouchableOpacity>
<Animated.View
style={{
...styles.menu,
opacity: fadeAnim,
transform: [{translateY: translateAnim}],
}}
pointerEvents={isActive ? 'auto' : 'none'}>
<FlatList
data={dropdownMenuItems}
renderItem={({item, index}) => (
<Button
onPress={() => navigation.navigate(item.screen)}
title={item.name}
style={
index === 0
? {borderTopLeftRadius: 8, borderTopRightRadius: 8}
: index === dropdownMenuItems.length - 1
? {borderBottomLeftRadius: 8, borderBottomRightRadius: 8}
: {}
}
/>
)}
/>
</Animated.View>
</View>
</TouchableOpacity>
);
};

Both the parent and the child components get rendered simultaneously in react-native

I have a parent component that maps through an array of chapters and renders (an exercise) a child component for every item found and passes an array of exercises to it.
class ExercisesScreen extends Component {
showSelectedItemList = (screenName, text) => {
Navigation.push("ExercisesStack", {
component: {
name: screenName,
options: navOptionsCreator(text)
}
});
};
get chapters() {
return this.props.chapters.map(chapter => (
<TouchableOpacity key={chapter.id}>
<ExercisesList
onPress={() =>
this.showSelectedItemList(chapter.screenName, chapter.name)
}
exercises={chapter.exercises}
/>
</TouchableOpacity>
));
}
render() {
return <View>{this.chapters}</View>;
}
}
const mapStateToProps = state => ({
chapters: chaptersSelector(state)
});
When this child component receives the array of exercises, it maps through it and renders a list of exercises.
class ExercisesList extends Component {
render() {
return this.props.exercises.map(exercise => (
<View key={exercise.id}>
<TouchableOpacity
style={styles.button}
onPress={() =>
this.props.showSelectedItemList(exercise.screenName, exercise.name)
}
>
<Image source={exercise.icon}/>
<View>
<Text>{exercise.name}</Text>
</View>
<Image source={arrow} />
</TouchableOpacity>
<View />
</View>
));
}
}
ExercisesList.propTypes = {
onPress: PropTypes.func,
exercises: PropTypes.arrayOf(PropTypes.object)
};
The result I get from both components rendered simultaneously:
The question is, what should I do in order for them to render themselves separately and show the corresponding ExercisesList for every chapter in ExercisesScreen?
Make your child component ExercisesList as functional component that only show the corresponding ExercisesList for every chapter not perform any rendering.
Like below:
const ExercisesList = (props) => {
const { exercises } = props;
return({
exercises.map(exercise, index) => renderExcercise(exercise, index)
})
}
const renderExcercise = (exercise, index) => {
return(
<View key={exercise.id}>
<TouchableOpacity
style={styles.button}
onPress={() =>
this.props.showSelectedItemList(exercise.screenName, exercise.name)
}
>
<Image source={exercise.icon}/>
<View>
<Text>{exercise.name}</Text>
</View>
<Image source={arrow} />
</TouchableOpacity>
<View />
</View>
)
}
export default ExercisesList;
ExercisesList.propTypes = {
onPress: PropTypes.func,
exercises: PropTypes.arrayOf(PropTypes.object)
};

How to pass the props from parent to child to child of child?

I want to pass the props from App.js(parent) to CommentItem.js(child) to Button.js(child of child),
but the props is empty in Button,js(child of child)'s component.
App.js(parent) to CommentItem.js(child)
I pushed mainuser: this.state.head to FlatList, and the props(mainuser) passed by renderItem={({ item }) => <CommentItem {...item}
And, CommentItem.js(child) received mainuser by the code below.
const {
head,
text,
created,
mainuser,
subuser,
} =
CommentItem.js(child) to Button.js(child of child)
I thought props was pased to Button by ,
and Button(child of child) received the props by the code below.
const {
mainuser,
} = this.props;
However, props id empty in Button.js(child of child).
#
Are there any problems of my code?
Could you give some advice please?
App.js(parent)
export default class App extends Component<{}> {
constructor(props) {
super(props);
this.state = {
head: [],
list: [],
};
}
_onPress = (text) => {
const list = [].concat(this.state.list);
list.push({
key: Date.now(),
text: text,
done: false,
mainuser: this.state.head,
});
this.setState({
list,
});
}
render() {
const {
head,
list,
} = this.state;
var data = [["User1", "User2", "User3"],];
return (
<View style={styles.container}>
<View style={styles.dropdownHeader}>
<View style={styles.dropdown}>
<DropdownMenu
style={{flex: 1}}
bgColor={'white'}
tintColor={'#666666'}
activityTintColor={'green'}
handler={(selection, row) => this.setState({head: data[selection][row]})}
data={data}
>
</DropdownMenu>
</View>
</View>
<Text>To Do</Text>
<View style={styles.postinput}>
<CommentInput onPress={this._onPress} />
</View>
</View>
<View style={styles.CommentListContainer}>
<FlatList
/*inverted*/
style={styles.CommentList}
data={list}
renderItem={({ item }) => <CommentItem {...item} /> }
/>
</View>
);
}
}
CommentItem.js(child)
const CommentItem = (props) => {
const {
head,
text,
created,
mainuser,
subuser,
} = props;
return (
<View style={styles.container}>
<View style={styles.left}>
<Text style={styles.text}>{text}</Text>
</View>
<View style={styles.function}>
<Button {...mainuser} />
</View>
</View>
);
}
Button.js(child of child)
export default class ApplauseButton extends Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
const {
mainuser,
} = this.props;
return (
<View style={styles.container}>
<Text>{mainuser}</Text>
</View>
);
}
}
If you want to access mainuser as a prop, then you've to pass it as <Button mainuser={mainuser} />.
As it stands, you're spreading the contents of mainuser.
as per your code it seems that main user is array,
this.state = {
head: [],//array
list: [],
};
....
list.push({
key: Date.now(),
text: text,
done: false,
mainuser: this.state.head,// main user should be array
});
so you need to give prop name also in <Button />
like this
<Button mainuser={mainuser} />

When changing the parent's state from a child, why does the parent not render again?

I have a parent class that passes a function called editClassInfo to a child class. This function, is bound to the parent, and when called, immutably changes the state of the parent. However, the parent does not render again, instead having to set the state from within the parent.
Specifically, the problem is with a modal component that has a textInput that, when the text is changed, sets the state of the parent screen. However, when the modal is closed, the screen's state does not update immediately, even though the state is changed. Instead, another this.setState() has to be called in order for the render to happen again.
Here is a picture reference of the problem:
https://imgur.com/a/oCHRTIu
Here is the code:
This is the parent component.
export default class Classes extends Component {
static navigationOptions = {
title: 'Classes'
};
constructor() {
super();
this.state = {
numObjects: 3.,
classes: [
{
className: "Math",
teacherName: "Someone",
},
{
className: "Science",
teacherName: "Someone",
},
{
className: "Art",
teacherName: "Someone",
}
]
};
this.editClassInfo = this.editClassInfo.bind(this);
}
editClassInfo(index, infoType, value) {
let newClass = this.state.classes;
switch (infoType) {
case 'className':
newClass[index].className = value;
break;
case 'teacherName':
newClass[index].teacherName = value;
break;
}
this.setState({classes: newClass});
}
addClass(name, name2) {
let newClass = this.state.classes.concat({className: name, teacherName: name2});
this.setState({classes: newClass});
}
loadClasses = () => {
this.setState({
numObjects: this.state.numObjects * 2,
})
}
render(){
const classData = this.state.classes;
console.log(this.state.classes[0]);
return (
<View style={styles.container}>
<TopBar title='Classes'/>
<View style={styles.classHeader}>
<Text style={styles.currentClasses}> CURRENT CLASSES </Text>
<TouchableOpacity onPress={() => {this.addClass('World History', 'Someone')}}>
<Image
style = {styles.addClass}
source={require('../resources/addClass.png')}
/>
</TouchableOpacity>
</View>
<FlatList
data = { classData }
onEndReached = {this.loadClasses}
keyExtractor = {(item, index) => index.toString()}
initialNumtoRender = {3}
renderItem = {({item}) =>
<ClassBox
index={this.state.classes.indexOf(item)}
editClassInfo ={this.editClassInfo}
className={item.className}
teacherName={item.teacherName}
/>
}
/>
</View>
);
}
}
I pass editClassInfo onto a component called ClassBox:
export default class ClassBox extends Component {
static propTypes = {
className: PropTypes.string.isRequired,
teacherName: PropTypes.string.isRequired,
index: PropTypes.number.isRequired
};
constructor(props) {
super(props);
this.state = {
isVisible: false,
};
this.modalVisible = this.modalVisible.bind(this);
}
modalVisible(visible) {
this.setState({isVisible: visible});
}
render(){
return(
<View>
<ClassEdit
index={this.props.index}
editClassInfo={this.props.editClassInfo}
isVisible={this.state.isVisible}
modalClose={this.modalVisible}
className={this.props.className}
teacherName={this.props.teacherName}
/>
<TouchableOpacity onPress={() => {this.modalVisible(true)}}>
<View style={styles.container}>
<Image
source={{uri: 'http://via.placeholder.com/50x50'}}
style={styles.classImage}
/>
<View style={styles.classInfo}>
<Text style={styles.className}>
{this.props.className}
</Text>
<Text style={styles.teacherName}>
{this.props.teacherName}
</Text>
</View>
</View>
</TouchableOpacity>
</View>
)
}
}
This component contains the Child Modal ClassEdit:
export default class ClassEdit extends Component {
static propTypes = {
index: PropTypes.number.isRequired,
isVisible: PropTypes.bool.isRequired,
className: PropTypes.string.isRequired,
teacherName: PropTypes.string.isRequired
}
render() {
return(
<Modal
animationType="none"
transparent={false}
visible={this.props.isVisible}
>
<View style={styles.container}>
<View style={styles.closeTop}>
<TouchableOpacity onPress={() => {
this.props.modalClose(false);
}}>
<Image
style={styles.closeIcon}
source={require('../resources/close.png')}
/>
</TouchableOpacity>
</View>
<View style={styles.classInfo}>
<Image
source={{uri: 'http://via.placeholder.com/150x150'}}
style={styles.classImage}
/>
<TextInput
style={styles.className}
placeholder='Class Name'
value={this.props.className}
onChangeText = {(className) => {this.props.editClassInfo(this.props.index, 'className', className)}}
/>
<TextInput
style={styles.teacherName}
placeholder='Teacher Name'
value={this.props.teacherName}
onChangeText = {(teacherName) => {this.props.editClassInfo(this.props.index, 'teacherName', teacherName)}}
/>
</View>
</View>
</Modal>
);
}
}
It is in this last component called ClassEdit where the parent state gets changed, but when the modal is closed, the updated state is not seen, instead having to call addClass to trigger it.
I am a bit new to react-native so my code might not be the best, and the problem might be really simple, but any help would be appreciated.
let newClass = this.state.classes; creates a reference to the actual classes state, you're then mutating it.
To create a new array in an immutable way you can do this :
ES6 :
let newClass = [...this.state.classes];
ES5 :
let newClass = [].concat(this.state.classes);

React Native - Rendering after pushing to array

I'm mapping an array to be rendered in React Native. On an event (button press) I want to add and object to the array and it be rendered on the screen. I am getting the lifecycle functions to trigger, but not sure if they are needed for this or how to utilize them effectively. Any help would be greatly appreciated!
class Home extends Component {
constructor(props) {
super(props)
this.state = {
data: '',
text: '',
submitted: false,
count: 1,
arr: [
{
key: 1,
text: "text1"
},
],
}
buttonsListArr = this.state.arr.map(info => {
return (
<View style={styles.container}>
<Text key={info.key}>{info.text}</Text>
<Button title='Touch' onPress={() => {
this.setState({count: this.state.count + 1})
}}/>
</View> )
})
}
shouldComponentUpdate = () => {
return true;
}
componentWillUpdate = () => {
this.state.arr.push({key: this.state.count, text: 'text2'})
}
render() {
return (
<View style={styles.container}>
{buttonsListArr}
</View>
)
}
}
What you've written is not typical. A quick fix is to move the logic to a render function like
constructor(props) {
.
.
this.renderText = this.renderText.bind(this)
}
renderText() {
return this.state.arr.map(info => {
return (
<View style={styles.container}>
<Text key={info.key}>{info.text}</Text>
<Button title='Touch' onPress={() => {
this.setState({count: this.state.count + 1})
}}/>
</View> )
})
}
then call the function within the render()
render() {
return (
<View style={styles.container}>
{this.renderText()}
</View>
)
}
You shouldn't be using a lifecycle call to add an element to an array. Simply call setState and it will rerender itself!
Try this.
<View style={styles.container}>
<Text key={info.key}>{info.text}</Text>
<Button title='Touch' onPress={() => {
this.setState({
count: this.state.count + 1
arr: this.state.arr.push({key: this.state.count, text: 'text2'})
})
}}/>
</View> )
return this.arrayholder.map(image => {
return (
<View style={styles.ImageContainer}>
<Image style={styles.ImageContainer} source={image} />
</View>
)
})

Categories