Populating React-Native ListView from JSON Data - ios

React-Native amateur here, I've written a php api to retrieve data from a MySQL server, and I need to put this into a ListView to display on an app.
The code for showing this is:
constructor(props) {
super(props);
var ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.state = {
dataSource: ds.cloneWithRows(this.props.response)
};
}
renderRow(rowData) {
return (
<View style={styles.row}>
<Text>{response}</Text>
</View>
)
}
render() {
return {
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}/>
);
}
}
And the API json return is as follows:
[{"barcode":"50201600","name":"Cadbury's Creme
Egg","tescoPrice":"1","sainsburysPrice":"1","asdaPrice":"1"},
{"barcode":"5034660520191","name":"Cadburys Twirl 4
Pack","tescoPrice":"1","sainsburysPrice":"1","asdaPrice":"1"}]
The error I get is:Objects are not valid as a React child (found: object with keys {barcode, name, tescoPrice, sainsburysPrice, asdaPrice}). If you meant to render a collection of children, use an array instead. Check the render method of 'Text'
Any ideas?
Cheers

From the error i see, you need to change from:
renderRow(rowData) {
return (
<View style={styles.row}>
<Text>{response}</Text>
</View>
)
}
into something like:
renderRow(rowData) {
return (
<View style={styles.row}>
<Text>{rowData.name}</Text>
</View>
)
}

Related

Unable to display fetched data in react native firebase using this.state.data.map()

I have been battling with the display of database item on my page using this.state.noteArray.map(val, key). I intend to display each value with a delete button to remove it from the page.
import React, { Component } from 'react';
import {
View,
Text,
StyleSheet,
TextInput,
ScrollView,
TouchableOpacity
} from 'react-native';
import firebase from 'firebase';
// Initialize Firebase
const config = {
apiKey: "XXXXXXXXXXXXXXX",
authDomain: "XXXXXXXXXXXXXXXXXXXXXX",
databaseURL: "XXXXXXXXXXXXXXXXXXXXXXXX",
projectId: "XXXXXXXXXXXXXXXXXXXXXX",
storageBucket: "",
messagingSenderId: "XXXXXXXXXXXXXXXXXXXX"
};
firebase.initializeApp(config);
export default class Main extends Component {
constructor(props){
super(props);
this.state = {
noteArray: [],
noteText: '',
};
this.addNote = this.addNote.bind(this);
}
componentDidMount(){
firebase.database()
.ref()
.child("todo")
.once("value", snapshot => {
const data = snapshot.val()
if (snapshot.val()){
const initNoteArray = [];
Object
.keys(data)
.forEach(noteText => initNoteArray.push(data[noteText]));
this.setState({
noteArray: initNoteArray
});
}
});
firebase.database()
.ref()
.child("todo")
.on("child_added", snapshot => {
const data = snapshot.val();
if (data){
this.setState(prevState => ({
noteArray: [data, ...prevState.noteArray]
}))
console.log(this.state.noteArray);
}
})
}
addNote(){
// firebase function here to send to the database
if (!this.state.noteText) return;
var d = new Date();
const newNote = firebase.database().ref()
.child("todo")
.push ({
'date':d.getFullYear()+
"/"+(d.getMonth()+1) +
"/"+ d.getDate(),
'note': this.state.noteText
});
newNote.set(this.state.noteText, () => this.setState({noteText: ''}))
}
render() {
let notes = this.state.noteArray.map((val, key)=>{
return
(<View key={key} keyval={key} val={val} style={styles.note}>
<Text style={styles.noteText}>{this.state.val.date}</Text>
<Text style={styles.noteText}>{this.state.val.note}</Text>
<TouchableOpacity onPress={this.state.deleteMethod} style={styles.noteDelete}>
<Text deleteMethod={()=>this.deleteNote(key)} style={styles.noteDeleteText}>D</Text>
</TouchableOpacity>
</View>)
});
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerText}>Todo App</Text>
</View>
<ScrollView style={styles.scrollContainer}>
{notes}
</ScrollView>
<View style={styles.footer}>
<TextInput
style={styles.textInput}
placeholder='>note'
onChangeText={(noteText)=> this.setState({noteText})}
value={this.state.noteText}
placeholderTextColor='white'
underlineColorAndroid='transparent'>
</TextInput>
</View>
<TouchableOpacity onPress={ this.addNote } style={styles.addButton}>
<Text style={styles.addButtonText}>+</Text>
</TouchableOpacity>
</View>
);
}
deleteNote(key){
this.state.noteArray.splice(key, 1);
this.setState({noteArray: this.state.noteArray});
}
}
There is no warning or error, but it is not displaying anything. I will appreciate if there is any help and inline comment to understand the process for the next time, I am a newbie, trying to master the code for future similar projects. All I care to know is the basic understanding of the CRUD and search using React native firebase. Thank you so much
If I correctly understand you need a proper way to display your data. Since noteArray is an array, there's nothing easier than a FlatList, which is scrollable by itself.
So, in your render method:
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.noteArray} // Here is where you pass your array of data
renderItem={this.renderItem} // Here is how to display each item of your data array
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
/>
</View>
);
}
Where:
renderHeader = () => {
return (
<View style={styles.header}>
<Text style={styles.headerText}>Todo App</Text>
</View>
)
}
renderFooter = () => {
return (
<View>
<View style={styles.footer}>
<TextInput
style={styles.textInput}
placeholder='>note'
onChangeText={(noteText)=> this.setState({noteText})}
value={this.state.noteText}
placeholderTextColor='white'
underlineColorAndroid='transparent'>
</TextInput>
</View>
<TouchableOpacity onPress={ this.addNote } style={styles.addButton}>
<Text style={styles.addButtonText}>+</Text>
</TouchableOpacity>
</View>
)
}
renderItem = ({ item, index }) => {
return (
<View key={index} style={styles.note}>
<Text style={styles.noteText}>{item.date}</Text>
<Text style={styles.noteText}>{item.note}</Text>
<TouchableOpacity onPress={this.state.deleteMethod} style={styles.noteDelete}>
<Text deleteMethod={()=>this.deleteNote(index)} style={styles.noteDeleteText}>D</Text>
</TouchableOpacity>
</View>
)
}
Thanks for your support. I have reviewed my code and it is working perfectly as I want. I will love to post it here in case someone else needs to work or learn about it. I used array.map() function to iterate over the items.
import React, { Component } from 'react';
import {
View,
Text,
StyleSheet,
TextInput,
ScrollView,
TouchableOpacity
} from 'react-native';
import Note from './Note';
import firebase from 'firebase';
// Initialize Firebase
const config = {
apiKey: "XXXXXXXXXXXXXXXXXXXXXXXXXX",
authDomain: "XXXXXXXXXXXXXXXXXXXXXX",
databaseURL: "XXXXXXXXXXXXXXXXXXXXXXXX",
projectId: "XXXXXXXXXXXXXXXXXXXXXXXXX",
storageBucket: "",
messagingSenderId: "XXXXXXXXXXXXXXXX"
};
firebase.initializeApp(config);
export default class Main extends Component {
constructor(props){
super(props);
this.state = {
noteArray: [],
noteText: '',
};
this.addNote = this.addNote.bind(this);
}
componentDidMount(){
firebase.database()
.ref()
.child("todo")
.once("value", snapshot => {
const data = snapshot.val()
if (snapshot.val()){
const initNoteArray = [];
Object
.keys(data)
.forEach(noteText => initNoteArray.push(data[noteText]));
this.setState({
noteArray: initNoteArray
});
}
});
firebase.database()
.ref()
.child("todo")
.on("child_added", snapshot => {
const data = snapshot.val();
if (data){
this.setState(prevState => ({
noteArray: [data, ...prevState.noteArray]
}))
console.log(this.state.noteArray);
}
})
}
addNote(){
// firebase function here to send to the database
if (!this.state.noteText) return;
var d = new Date();
const newNote = firebase.database().ref()
.child("todo")
.push ();
newNote.set({
'date':d.getFullYear()+
"/"+(d.getMonth()+1) +
"/"+ d.getDate(),
'note': this.state.noteText
});
this.setState({noteText:''});
}
render() {
let notes = this.state.noteArray.map((val, key)=>{
return <Note key={key} keyval={key} val={val}
deleteMethod={()=>this.deleteNote(key)}/>
});
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerText}>Todo App</Text>
</View>
<ScrollView style={styles.scrollContainer}>
{notes}
</ScrollView>
<View style={styles.footer}>
<TextInput
style={styles.textInput}
placeholder='>note'
onChangeText={(noteText)=> this.setState({noteText})}
value={this.state.noteText}
placeholderTextColor='white'
underlineColorAndroid='transparent'>
</TextInput>
</View>
<TouchableOpacity onPress={ this.addNote } style={styles.addButton}>
<Text style={styles.addButtonText}>+</Text>
</TouchableOpacity>
</View>
);
}
deleteNote(key){
this.state.noteArray.splice(key, 1);
this.setState({noteArray: this.state.noteArray});
}
}
I have another .js component named Note.js for display template. This was included in the Main.js and was reference just after the render.
import React, { Component } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
} from 'react-native';
export default class Note extends Component {
render() {
return (
<View key={this.props.keyval} style={styles.note}>
<Text style={styles.noteText}>{this.props.val.date}</Text>
<Text style={styles.noteText}>{this.props.val.note}</Text>
<TouchableOpacity onPress={this.props.deleteMethod} style={styles.noteDelete}>
<Text style={styles.noteDeleteText}>D</Text>
</TouchableOpacity>
</View>
);
}
}

Want to pass data to other component - ListView

I have added and imported the sample data. I want to list out data from this file in a list view and I'm passing the data to the Row Component for RenderRow. But getting error saying
Row(...): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.
import React, { Component } from 'react';
import { AppRegistry, View, ListView, Text, StyleSheet } from 'react-native';
import Row from './app/Row';
import data from './app/Data';
export default class ListViewDemo extends Component {
constructor(props) {
super(props);
const rowHasChanged = (r1, r2) => r1 !== r2
const ds = new ListView.DataSource({rowHasChanged});
this.state = {
dataSource: ds.cloneWithRows(data),
};
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={(data) => <Row {...data} />} // Getting error here
/>
);
}
}
AppRegistry.registerComponent('DemoApp',() => ListViewDemo)
These my sample Data.js You can check the data here.
export default data = [
{...}, {...}
];
Row.js:
const Row = (props) => {
<View Style={styles.container}>
<Image source={{ uri: props.picture.large}} />
<Text >
{`${props.name.first} ${props.name.last}`}
</Text>
</View>
}
What would be the problem?
ES6 only returns when there is no explicit blocks:
const cb = (param) => param * 2;
You should explicitly return:
const Row = (props) => {
return (
<View Style={styles.container}>
<Image source={{ uri: props.picture.large}} />
<Text >
{`${props.name.first} ${props.name.last}`}
</Text>
</View>
);
}
Check this answer for further explanation.
Change this Line to
renderRow={(data) => <Row {...data} />}
To This
renderRow={(data) =><Row {...this.props} />}
This may help you to get props in the row Component

React-Native/Expo Receiving ERROR 'null is not an object (evaluating 'match.localteam_name')

Here I am only running two API requests. The first one in the componentDidMount function works fine, but the second one labeled handleMatchFacts does not work. In short, Using React-Native I'm retrieving information from the API, mounting it to the page and then once the Touchablehighlight is clicked it is suppose to retrieve additional information from the API according to the 'id' that is passed in 'onPress'. I am able to console.log the json of the data in the second request, but for some reason when I setState with the new data and render it to the page in ListView, I get an error.
import React from 'react'
import { View, Text, StyleSheet, ListView, TouchableHighlight } from 'react-native'
export default class Main extends React.Component {
constructor() {
super();
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
matches: ds.cloneWithRows([]),
matchFacts: ds.cloneWithRows([])
};
this.handleShowMatchFacts.bind(this)
}
componentDidMount(){
fetch("http://api.football-api.com/2.0/matches?match_date=27.04.2017&to_date=27.04.2017&Authorization=565ec012251f932ea4000001fa542ae9d994470e73fdb314a8a56d76")
.then(res => res.json())
.then(matches => {
this.setState({
matches : this.state.matches.cloneWithRows(matches)
})
})
}
handleShowMatchFacts = id => {
console.log('match', id)
return fetch(`http://api.football-api.com/2.0/matches/${id}?Authorization=565ec012251f932ea4000001fa542ae9d994470e73fdb314a8a56d76`)
.then(res => res.json())
.then(matchFacts => {
console.log('match facts', matchFacts)
let selectedMatch = matchFacts;
this.setState({
matches : this.state.matches.cloneWithRows([]),
matchFacts : this.state.matchFacts.cloneWithRows(selectedMatch)
})
})
}
render() {
return (
<View style={styles.mainContainer}>
<Text
style={styles.header}>
Todays Matches</Text>
<ListView
style={styles.matches}
dataSource={this.state.matches}
renderRow={(matches) =>
<TouchableHighlight
onPress={() => this.handleShowMatchFacts(matches.id)}
underlayColor="green"
><Text style={styles.item}> {matches.localteam_name} {matches.localteam_score} - {matches.visitorteam_score} {matches.visitorteam_name} </Text>
</TouchableHighlight>
}
/>
<ListView
style={styles.matches}
dataSource={this.state.matchFacts}
renderRow={(match) =>
<Text style={styles.item}> {match.localteam_name} {match.localteam_score} - {match.visitorteam_score} {match.visitorteam_name} </Text>
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
mainContainer : {
flex: 1,
padding: 20
},
header : {
textAlign: 'center'
},
matches : {
marginTop: 20
},
item : {
borderRadius: 4,
borderWidth: 0.5,
borderColor: 'green',
marginBottom: 5,
padding: 20,
textAlign: 'center',
},
});
You're probably seeing an issue because the second API request doesn't return an array, it returns an object. The cloneWithRows expects an array. Replacing this line
matchFacts : this.state.matchFacts.cloneWithRows(selectedMatch)
with
matchFacts : this.state.matchFacts.cloneWithRows([selectedMatch])
may help, depending on how you render this new data.
That's just a guess since I don't know what error you're receiving.

React Native Element Type is invalid Error While using ActivityIndicatorIOS

I have begun to develop a book-searching application using React and Google Books API. However, I have run into an error where my simulator reads:
Element Type is invalid: expected a string (for built-in components) or
a class/function (for composite components) but got: undefined. Check
the render method of 'BookList'.
Given that I am fairly new to React, I was hoping someone might be able to point out the error(s) in my code below. I have noted the place where I think there may be an error. Thanks!
class BookList extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
})
};
}
componentDidMount() {
this.fetchData();
}
fetchData() {
fetch(REQUEST_URL[0])
.then((response) => response.json())
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.items),
isLoading: false
});
})
.done();
}
render() {
if (this.state.isLoading) {
return this.renderLoadingView();
}
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderBook.bind(this)}
style={styles.listView}
/>
);
}
// *** adding this function (and using it) began to cause issues ****
renderLoadingView() {
return (
<View style={styles.loading}>
<ActivityIndicatorIOS
size='large'/>
<Text>
Loading books...
</Text>
</View>
);
}
renderBook(book) {
return (
<TouchableHighlight>
<View>
<View style={styles.container}>
<Image
source={{uri: book.volumeInfo.imageLinks.thumbnail}}
style={styles.thumbnail} />
<View style={styles.rightContainer}>
<Text style={styles.title}>{book.volumeInfo.title}</Text>
<Text style={styles.author}>{book.volumeInfo.authors}</Text>
<Text style={styles.price}>{'Lowest Available Price: ' + book.volumeInfo.price}</Text>
</View>
</View>
<View style={styles.separator} />
</View>
</TouchableHighlight>
);
}
}
var REQUEST_URL = ['https://www.googleapis.com/books/v1/volumes?q=subject:fiction'];
ActivityIndicatorIOS is depreciated use ActivityIndicator instead.

React-Native with Redux: API objects appear in my console.log but not my view

I’m building a Simple Sports App using react, react-native, react-native-router-flux, react-redux, with redux and trying to pull an api of top news object to my view. The object that I need displays on my console log but cant get it to show up on my view.
The error that I am getting is:
Cannot read property 'cloneWithRowsAndSections' of undefined
TypeError: Cannot read property 'cloneWithRowsAndSections' of
undefined
at NewsList.componentWillMount
Is there something that I am missing?
NewsList:
export default class NewsList extends Component {
constructor(props){
super(props);
const ds = new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 != row2,
sectionHeaderHasChanged: (s1, s2) => s1 !== s2
});
this.state = {
dataSource: ds.cloneWithRowsAndSections(props.ListData)
}
}
componentWillReceiveProps(nextProps){
const dataSource = this.state.dataSource.cloneWithRowsAndSections(nextProps.ListData);
this.setState({dataSource});
}
componentWillMount(){
// console.log("whats This: ",this.props.ListData);
let data = this.props.ListData;
this.setState({
dataSource: this.ds.cloneWithRowsAndSections(data)
});
}
News_View:
render(){
console.log('TopNews', this.state.TopNews);
if(this.state.TopNews.length == 0){
return(
<View style={styles.container}>
<View style={styles.LiveStyle}>
<Live style={styles.LiveStyle} />
</View>
<View style={styles.container}>
<Text style={[styles.NotFollowTeamMsg, styles.centerTeamMsg]}>
Your not connected to the api
</Text>
</View>
</View>
)
} else {
return (
<View style={styles.container}>
<NewsList
ListData={this.state.TopNews}
/>
</View>
)
}
}
In the constructor, I tried changing const ds = ..., as this.ds = ... but that only lead to the error:
ds is not defined
ReferenceError: ds is not defined
I tried taking out the constructor function:
const ds = new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 != row2,
sectionHeaderHasChanged: (s1, s2) => s1 !== s2
});
then locate it outside of NewsList class, and I setState,like this:
this.setState({
dataSource: ds.cloneWithRowsAndSections(data)
});
but that only lead to the error:
Cannot read property 'bind' of undefined TypeError: Cannot read
property 'bind' of undefined
2 Options:
1.In your constructor make the const ds = ..., as this.ds = ...
2.Take this out of the constructor function:
const ds = new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 != row2,
sectionHeaderHasChanged: (s1, s2) => s1 !== s2
});
and locate it outside of NewsList class.
Now, when you setState, do it like this:
this.setState({
dataSource: ds.cloneWithRowsAndSections(data)
});

Resources