can somebody please help me with "ref" in this code. In the last "view" component, it seems that "this.multiSelect" is undefined. I dont understand why and where do I make mistake. Thas it have something with context or?
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import MultiSelect from 'react-native-multiple-select';
export default class MultiSelectComponent extends Component {
constructor() {
super();
this.state = {
selectedItems : []
};
this.items = [{
id: '92iijs7yta',
name: 'Ondo',
}, {
id: 'suudydjsjd',
name: 'Abuja',
}];
}
onSelectedItemsChange = selectedItems => {
this.setState({ selectedItems });
};
render() {
const { selectedItems } = this.state;
return (
<View style={{ flex: 1 }}>
<MultiSelect
hideTags
items={this.items}
uniqueKey="id"
ref={(component) => { this.multiSelect = component; }}
onSelectedItemsChange = {this.onSelectedItemsChange}
selectedItems={selectedItems}
selectText="Pick Items"
searchInputPlaceholderText="Search Items..."
submitButtonText="Submit"
/>
<View>
{this.multiselect
?
this.multiselect.getSelectedItemsExt(selectedItems)
:
null}
</View>
</View>
);
}
}
You have a typo in your code, the error is maybe there :
you are using this.multiselect and declaring
ref={(component) => { this.multiSelect = component; }}
so maybe with thi :
ref={(component) => { this.multiselect = component; }}
Related
I am using the Checkbox component for my code and I have a list of elements and for each element I have a checkbox. However, my checkbox returns the following error when I check it on the phone simulator :
TypeError: this.props.onClick is not a function. (In 'this.props.onClick()', 'this.props.onClick' is undefined)
I don't understand because a checkbox does not require the onClick function ...
Please see my code below if needed:
import CheckBox from 'react-native-check-box'
export default class List extends Component {
constructor(props) {
super(props)
this.state = {
names: [
{
key: 0,
name: 'Element1',
isChecked: false
},
{
key: 1,
name: 'Element2',
isChecked: false,
}
]
}
}
checkboxTest(key){
let names = this.state.names.reduce((acc,name)=>{
if(name.key === key){
return {...name, isChecked: !name.isChecked}
}
return name
}, [])
this.setState({names})
console.table(names)
}
render(){
return (
<ScrollView>
<View>
{
this.state.names.map((item) => (
<View style={styles.background}>
<Text style = {styles.title}>{item.name}</Text>
<CheckBox value={false} onChange={()=>this.checkboxTest(item.key)}/>
</View>
))
}
</View>
</ScrollView>
)
}
}
If you read the documentation carefully, you will noticed that this checkbox component requires a onClick function, which you did not provide. Hence, instead of writing onChange, you should be using onClick instead
<CheckBox
style={{flex: 1, padding: 10}}
onClick={()=>{
this.setState({
isChecked:!this.state.isChecked
})
}}
isChecked={this.state.isChecked}
leftText={"CheckBox"}
/>
According to react-native-check-box documentation prop onClick is isRequired. But you are passing onChange not onClick.
Change your onChange to onClick in order to fix the error. Also change value to isChecked.
<CheckBox isChecked={false} onClick={()=>this.checkboxTest(item.key)}/>
Edit
Check below complete example for information
import * as React from "react";
import { Text, View } from "react-native";
import CheckBox from "react-native-check-box";
export default class List extends React.Component {
constructor(props) {
super(props);
this.state = {
names: [
{
key: 0,
name: "Element1",
isChecked: false
},
{
key: 1,
name: "Element2",
isChecked: false
}
]
};
}
checkboxTest = key => {
let names = this.state.names.reduce((acc, name) => {
if (name.key === key) {
acc.push({ ...name, isChecked: !name.isChecked });
} else {
acc.push(name);
}
return acc;
}, []);
this.setState({ names });
};
render() {
return (
<View>
{this.state.names.map((item, index) => (
<View style={{ flexDirection: " row" }}>
<Text>{item.name}</Text>
<CheckBox
isChecked={
this.state.names.find(item => item.key == index).isChecked
}
onClick={() => this.checkboxTest(item.key)}
/>
</View>
))}
</View>
);
}
}
Hope this helps you. Feel free for doubts.
I am new to react native. I am doing a simple app where I add name and age of a person to firebase and then showing it in the list, I am using flatList in this project but it asks to import all the attributes of the flatList. if I add only 2 attributes like data, renderItem it gives an error, please help
here my code
import React from "react";
import {StyleSheet, View, Button, Text, FlatList, TextInput, ListView} from "react-native";
import firebase from './firebase'
let db = firebase.firestore();
class TextInputExample extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
userName: '',
userAge: '',
input1Background: 'red',
textColor1: 'white',
input2Background: 'red',
textColor2: 'white'
};
}
componentDidMount(): void {
db.collection('users')
.onSnapshot((snapshot) => {
snapshot.docChanges().forEach(change => {
if (change.type === 'added') {
this.state.data.push({
name: change.doc.get('name'),
age: change.doc.get('age')
});
console.log(this.state.data);
}
})
}, (error => {
console.log(error.message);
}))
}
addToDatabase = () => {
let data = {
name: this.state.userName,
age: this.state.userAge
};
console.log(data);
db.collection('users').add(data)
.then(ref => {
}).catch(msg => {
console.log(msg);
});
};
renderItem = ({item}) => {
return(
<View>
<Text>{item.name}</Text>
<Text>{item.age}</Text>
</View>
);
};
render(): React.Node {
return (
<View style={styles.container}>
<TextInput
placeHolder={'Enter name'}
onChangeText={(text) => this.setState( {userName: text} )}
/>
<TextInput
placeHolder={'Enter Age'}
onChangeText={(text) => this.setState( {userAge: text} )}
/>
<Button title={'Add'} onPress={() => this.addToDatabase()}/>
<View>
<FlatList
data={this.state.data}
renderItem={this.renderItem}
/>
</View>
</View>
);
}
}
export default TextInputExample;
const styles = StyleSheet.create({
container: {
flex: 1, alignSelf: 'center', alignItems: 'center'
}
});
I think your error is because you're updating your state in the wrong way, if you want to add an element to an array in your state you must do it using the setState method and not directly accessing to the array and push it.
Do this
const newItem = {
name: change.doc.get('name'),
age: change.doc.get('age')
}
this.setState((prevState) => ({
...prevState,
data: [...prevState, newItem]
}))
I'm making an app in react-native and I would like to navigate to different page by clicking on a button using stack navigation:
Here is my code :
app.js
import React from 'react';
import { AppRegistry } from 'react-native';
import { StackNavigator } from 'react-navigation';
import Home from './Screens/Home';
import VideoListItems from './Screens/VideoListItems';
import TrackPlayer from './Screens/TrackPlayer';
const reactNavigationSample = props => {
return <VideoListItems navigation={props.navigation} />;
};
reactNavigationSample.navigationOptions = {
title: "VideoListItems"
};
const AppNavigator = StackNavigator({
Home: { screen: Home, navigationOptions: { header: null }},
VideoListItems: { screen: VideoListItems, navigationOptions: { header: null }},
TrackPlayer: { screen: TrackPlayer, navigationOptions: { header: null }},
}
);
export default class App extends React.Component {
render() {
return (
<AppNavigator />
);
}
}
VideoListItems where the button to navigate is :
import {StackNavigator} from 'react-navigation';
const VideoListItems = ({ video, props }) => {
const { navigate } = props.navigation;
const {
cardStyle,
imageStyle,
contentStyle,
titleStyle,
channelTitleStyle,
descriptionStyle
} = styles;
const {
title,
channelTitle,
description,
thumbnails: { medium: { url } }
} = video.snippet;
const videoId = video.id.videoId;
return(
<View>
<Card title={null} containerStyle={cardStyle}>
<Image
style={imageStyle}
source= {{ uri: url}}
/>
<View style={contentStyle}>
<Text style={titleStyle}>
{title}
</Text>
<Text style={channelTitleStyle}>
{channelTitle}
</Text>
<Text style={descriptionStyle}>
{description}
</Text>
<Button
raised
title="Save And Play"
icon={{ name: 'play-arrow' }}
containerViewStyle={{ marginTop: 10 }}
backgroundColor="#E62117"
onPress={() => {
navigate('TrackPlayer')
}}
/>
</View>
</Card>
</View>
);
};
export default VideoListItems;
But I'm getting this error :
TypeError: undefined is not an object (evaluating 'props.navigation')
I don't know how to pass the props navigation and make it able to navigate when clicking on the button, I don't know where is my error, any ideas ?
[EDIT]
My new VideoItemList :
const VideoListItems = props => {
const {
cardStyle,
imageStyle,
contentStyle,
titleStyle,
channelTitleStyle,
descriptionStyle
} = styles;
const {
title,
channelTitle,
description,
thumbnails: { medium: { url } }
} = props.video.snippet;
const videoId = props.video.id.videoId;
const { navigate } = props.navigation;
return(
<View>
<Card title={null} containerStyle={cardStyle}>
<Image
style={imageStyle}
source= {{ uri: url}}
/>
<View style={contentStyle}>
<Text style={titleStyle}>
{title}
</Text>
<Text style={channelTitleStyle}>
{channelTitle}
</Text>
<Text style={descriptionStyle}>
{description}
</Text>
<Button
raised
title="Save And Play"
icon={{ name: 'play-arrow' }}
containerViewStyle={{ marginTop: 10 }}
backgroundColor="#E62117"
onPress={() => {
navigate.navigate('TrackPlayer')
}}
/>
</View>
</Card>
</View>
);
};
This is the file where I display all my components :
import React, { Component } from 'react';
import { View } from 'react-native';
import YTSearch from 'youtube-api-search';
import AppHeader from './AppHeader';
import SearchBar from './SearchBar';
import VideoList from './VideoList';
const API_KEY = 'ApiKey';
export default class Home extends Component {
state = {
loading: false,
videos: []
}
componentWillMount(){
this.searchYT('');
}
onPressSearch = term => {
this.searchYT(term);
}
searchYT = term => {
this.setState({ loading: true });
YTSearch({key: API_KEY, term }, videos => {
console.log(videos);
this.setState({
loading: false,
videos
});
});
}
render() {
const { loading, videos } = this.state;
return (
<View style={{ flex: 1, backgroundColor: '#ddd' }}>
<AppHeader headerText="Project Master Sound Control" />
<SearchBar
loading={loading}
onPressSearch={this.onPressSearch} />
<VideoList videos={videos} />
</View>
);
}
}
And my VideoList where I use VideoListItems :
import React from 'react';
import { ScrollView, View } from 'react-native';
import VideoListItems from './VideoListItems';
const VideoList = ({ videos }) => {
const videoItems = videos.map(video =>(
<VideoListItems
key={video.etag}
video={video}
/>
));
return(
<ScrollView>
<View style={styles.containerStyle}>
{videoItems}
</View>
</ScrollView>
);
};
const styles = {
containerStyle: {
marginBottom: 10,
marginLeft: 10,
marginRight: 10
}
}
export default VideoList;
That's because you try to extract navigation from a prop named props (that doesn't exist), you have many ways to solve this problem :
Use the rest operator to group all props except video inside a props variable
const VideoListItems = ({ video, ...props }) => {
Don't destructure you props object
const VideoListItems = props => {
// don't forget to refactor this line
const videoId = props.video.id.videoId;
Extract navigation from props
const VideoListItems = ({ video, navigation }) => {
const { navigate } = navigation;
I am trying to update ListView.DataSource after fetching data from server but its not happening. I have two components, one is imported in other.
From base component I am trying to update ListView.DataSource in other component. Here is my code.
index.android.js
import React, { Component } from "react";
import { ListView, View, Button, AppRegistry } from "react-native";
import OtherComponent from "./Components/OtherComponent";
class MyApp extends Component {
constructor(props) {
super(props);
this.state = {
movies: [{ title: "ABCD" }, { title: "EFGH" }]
};
}
getTopics = () => {
fetch("https://facebook.github.io/react-native/movies.json")
.then(response => response.json())
.then(responseText => {
console.log(responseText.movies);
this.setState({
movies: responseText.movies
});
})
.catch(error => {
console.warn(error);
});
};
render() {
return (
<View>
<Button
onPress={this.getTopics}
title="Get Topics"
color="#841584"
accessibilityLabel="Learn more about this purple button"
/>
<OtherComponent movies={this.state.movies} />
</View>
);
}
}
AppRegistry.registerComponent("MyApp", () => MyApp);
OtheComponent.js
import React, { Component } from "react";
import { View, ListView, Text, StyleSheet, Button } from "react-native";
export default class FormativeRevisionList extends Component {
constructor(props) {
super(props);
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.state = {
dataSource: ds.cloneWithRows(props.movies)
};
}
render() {
return (
<View>
<ListView
style={styles.listContainer}
dataSource={this.state.dataSource}
renderRow={rowData => (
<View>
<Text style={styles.listItem}>{rowData.title}</Text>
</View>
)}
/>
</View>
);
}
}
const styles = StyleSheet.create({
listContainer: {
paddingTop: 22
},
listItem: {
fontSize: 30,
fontWeight: "bold",
textAlign: "center"
}
});
In your code, you only set your dataSource in contructor of FormativeRevisionList, this mean FormativeRevisionList will only render the given movies when your first render it.
To render new list after you press Get Topics button , you need to set the dataSource again when it receive new props, this can be achieve by setting it in FormativeRevisionList componentWillReceiveProps
componentWillReceiveProps(nextProps) {
if (nextProps.movies !== this.props.movies) {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(nextProps.movies)
})
}
}
You can read more about componentWillReceiveProps from here
Is there any way to set only one toolbarAndroid to be used on every screen of the application in conjunction with a navigator.
I set up a navigator in index.android.js :
import React, { Component } from 'react';
import {
AppRegistry,
Navigator,
} from 'react-native';
import ContactList from './src/containers/ContactList.js';
class MyIndex extends Component {
render() {
return (
<Navigator
initialRoute={{ name: 'index', component: ContactList }}
renderScene={(route, navigator) => {
if (route.component) {
return React.createElement(route.component, { navigator, ...route.props });
}
return undefined;
}}
/>
);
}
}
AppRegistry.registerComponent('reactest', () => MyIndex);
The first screen displays a contact list :
import React, { Component, PropTypes } from 'react';
import {
Text,
View,
TouchableOpacity,
TouchableHighlight,
ListView,
Image,
ActivityIndicator,
ToolbarAndroid,
} from 'react-native';
import styles from '../../styles';
import ContactDetails from './ContactDetails';
import logo from '../images/ic_launcher.png';
const url = 'http://api.randomuser.me/?results=15&seed=azer';
export default class ContactList extends Component {
static propTypes = {
navigator: PropTypes.object.isRequired,
}
constructor(props) {
super(props);
const datasource = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
this.state = {
animating: false,
animatingSize: 0,
jsonData: datasource.cloneWithRows([]),
ds: datasource,
appTitle: 'Test',
appLogo: logo,
};
}
_handlePress() {
this.setState({
animating: true,
animatingSize: 80,
});
return fetch(url)
// convert to json
.then((response) => response.json())
// do some string manipulation on json
.then(({ results }) => {
const newResults = results.map((user) => {
const newUser = {
...user,
name: {
title: `${user.name.title.charAt(0).toUpperCase()}${user.name.title.slice(1)}`,
first: `${user.name.first.charAt(0).toUpperCase()}${user.name.first.slice(1)}`,
last: `${user.name.last.charAt(0).toUpperCase()}${user.name.last.slice(1)}`,
},
};
return newUser;
});
return newResults;
})
// set state
.then((results) => {
this.setState({
appSubTitle: 'Contacts list',
animating: false,
animatingSize: 0,
jsonData: this.state.ds.cloneWithRows(results),
});
});
}
renderRow(rowData: string) {
return (
<TouchableHighlight
onPress={() => {
this.props.navigator.push({
first: rowData.name.first,
component: ContactDetails,
props: {
title: rowData.name.title,
first: rowData.name.first,
last: rowData.name.last,
picture: rowData.picture.large,
thumbnail: rowData.picture.thumbnail,
},
});
}}
>
<View style={styles.listview_row}>
<Image
source={{ uri: rowData.picture.thumbnail }}
style={{ height: 48, width: 48 }}
/>
<Text>
{rowData.name.title} {rowData.name.first} {rowData.name.last}
</Text>
</View>
</TouchableHighlight>
);
}
render() {
const view = (
<View style={styles.container}>
<ToolbarAndroid
logo={this.state.appLogo}
title={this.state.appTitle}
subtitle={this.state.appSubTitle}
style={[{ backgroundColor: '#e9eaed', height: 56 }]}
/>
<ActivityIndicator
animating={this.state.animating}
style={[styles.centering, { height: this.state.animatingSize }]}
/>
<TouchableOpacity
onPress={() => this._handlePress()}
style={styles.button}
size="large"
>
<Text>Fetch results?</Text>
</TouchableOpacity>
<ListView
enableEmptySections
dataSource={this.state.jsonData}
renderRow={(rowData) => this.renderRow(rowData)}
onPress={() => this._handleRowClick()}
/>
</View>
);
return view;
}
}
and the second one displays a contact details :
import React, {
Component,
PropTypes,
} from 'react';
import {
Text,
View,
Image,
ToolbarAndroid,
} from 'react-native';
import styles from '../../styles';
export default class ContactDetails extends Component {
constructor(props) {
super(props);
this.state = {
animating: false,
animatingSize: 0,
appTitle: 'Test',
appLogo: { uri: this.props.thumbnail, height: 56 },
appSubTitle: `Contact Details - ${this.props.first} ${this.props.last}`,
};
}
render() {
return (
<View style={styles.container}>
<ToolbarAndroid
logo={this.state.appLogo}
title={this.state.appTitle}
subtitle={this.state.appSubTitle}
style={[{ backgroundColor: '#e9eaed', height: 56 }]}
/>
<Image
source={{ uri: this.props.picture }}
style={{ height: 128, width: 128 }}
/>
<Text>{this.props.title} {this.props.first} {this.props.last}</Text>
</View>
);
}
}
ContactDetails.propTypes = {
title: PropTypes.string.isRequired,
first: PropTypes.string.isRequired,
last: PropTypes.string.isRequired,
picture: PropTypes.string.isRequired,
thumbnail: PropTypes.string.isRequired,
};
I set up an toolbarAndroid in my first screen and another in my second screen, it's working well, but I have a feeling that it would be better to define only one toolbarAndroid and update it calling setState.
Is it possible, if so how ?
Wrap your Navigator class with your ToolbarAndroid. This way, everything that is rendered on the Navigator will be wrapped by the Toolbar. Actually, everything that is common between those scenes should be put on a single component wrapping the rest.
class MyIndex extends Component {
render() {
return (
<ToolbarAndroid>
<Navigator
initialRoute={{ name: 'index', component: ContactList }}
renderScene={(route, navigator) => {
if (route.component) {
return React.createElement(route.component, { navigator, ...route.props });
}
return undefined;
}}
/>
</ToolbarAndroid>
);
}
}
I managed to do that by wrapping the toolbarAndroid and the navigator in a view :
class MyIndex extends Component {
constructor(props) {
super(props);
this.state = {
appTitle: 'Test',
appLogo: logo,
};
}
render() {
return (
<View
style={styles.container}
>
<ToolbarAndroid
logo={this.state.appLogo}
title={this.state.appTitle}
subtitle={this.state.appSubTitle}
style={[{ backgroundColor: '#e9eaed', height: 56 }]}
/>
<Navigator
initialRoute={{ name: 'index', component: ContactList }}
renderScene={(route, navigator) => {
if (route.component) {
return React.createElement(route.component, { navigator, ...route.props });
}
return undefined;
}}
/>
</View>
);
}
}