I am trying to populate a Listview in my react-native app using data from Firebase and am currently getting this error:
"Objects are not valid as a React child (found object with keys {title}). If you meant to render a collection of children, use an array instead or wrap the object using createFragment(object) from the React addons. Check the render method of 'Text.'"
This is occurring on my call to clonewithrows. Currently my input to this function (after parsing response to Firebase) is: [ { title: 'New York' }, { title: 'Boston' } ]
Here is my relevant code; please let me know if you see anything that may be causing the problem.
const huntsRef = new Firebase(`${ config.FIREBASE_ROOT }/hunts`)
class Home extends Component {
constructor(props) {
super(props);
var conDataSource = new ListView.DataSource(
{rowHasChanged: (r1, r2) => r1.guid != r2.guid});
this.state = {
dataSource: conDataSource
};
}
listenForItems(huntsRef) {
huntsRef.on('value', (snap) => {
var hunts = [];
snap.forEach((child) => {
hunts.push({
title: child.val().title
});
});
this.setState({
dataSource: this.state.dataSource.cloneWithRows(hunts)
});
console.log('datasource' + this.state.dataSource);
});
}
componentDidMount() {
this.listenForItems(huntsRef);
}
renderRow(rowData, sectionID, rowID) {
console.log("ROWDATA" + rowData);
}
render() {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => <Text>{rowData}</Text>}
/>
);
}
}
module.exports = Home;
As the message says, rowData is javascript object and cannot be rendered. You can stringify your rowData. Ex:
<ListView
dataSource={this.state.dataSource}
renderRow={(rowData) => <Text>{JSON.stringify(rowData)}</Text>}
/>
Related
when I use the map method inside the showData function it works, but when I use it while rendering, it shows an error.
export class App extends Component {
constructor(props) {
super(props);
this.state = {
word: "",
data: [],
};
}
handelWordChange = (e) => {
this.setState({ word: e.target.value });
};
showData = async () => {
let url = `https://api.urbandictionary.com/v0/define?term=${this.state.word}`;
let rawData = await fetch(url);
let parsedData = await rawData.json();
this.setState({ data: parsedData });
this.state.data.list.map((e) => {
console.log(e.definition);
});
};
render() {
return (
<div>
<input
type="text"
value={this.state.value}
onChange={this.handelWordChange}
/>
<button onClick={this.showData}>Show</button>
{this.state.data.list.map((e) => {
return <h1>{e.definition}</h1>;
})}
</div>
);
}
}
I am learning to react and I came through this error can anyone please help me out.
This Error is appearing because when your component load on the first render then your data variable value is an empty array, Add the conditional check before applying the map on the array.
{this.state.data && this.state.data.list && this.state.data.list.map((e) => {
return <h1>{e.definition}</h1>;
})}
This error is when you are accessing properties/indices from something undefined.
Use optional chaining to make sure you are only accessing properties of objects which themselves are defined.
{this.state.data.list?.map((e) => {
return <h1>{e.definition}</h1>;
})}
In my React Native app I am trying to print data from my Firebase database which have specific key (passed from another component). I have used similiar code I had used to get all database data but I am getting blank space.
var data = []
var currentUser;
class FavPlant extends React.Component {
constructor(props){
super(props)
this.ds = new ListView.DataSource({rowHasChanged:(r1,r2) => r1 !==r2})
this.state = {
listViewData : data
}
}
componentDidMount(){
const { navigation } = this.props;
const keyPlant = navigation.getParam('keyPlant');
firebase.auth().onAuthStateChanged((user) => {
if (user != null) {
var that = this
firebase.database().ref(user.uid).child('plantList').equalTo('keyPlant').on('child_added',function(data){
var newData = [...that.state.listViewData]
newData.push(data)
that.setState({ listViewData: newData})
});
}
})
}
render(){ return(
<Container>
<Content>
<ListView
enableEmptySections
dataSource = {this.ds.cloneWithRows(this.state.listViewData)}
renderRow={data =>
<Text>{data.val().bloom}</Text>
}/>
</Content>
</Container>
)}
If you have a DatabaseReference to the plantList element in your JSON:
var plantListRef = ...
Then you can get the child node with a specific key with:
plantListRef.child("-Lg-l2GOOVC_Hni5a3ng").on('value',function(snapshot){
console.log(snapshot.key); // prints "-Lg-l2GOOVC_Hni5a3ng"
console.log(snapshot.val());
});
I'm running into an issue right now trying to render a list using react, where I'm saving my react elements into the state, but the problem I'm getting is that the console outputs this:
Uncaught Error: Objects are not valid as a React child (found: object with keys {}). If you meant to render a collection of children, use an array instead.
Here is what the state looks like which causes the error:
export default class UserData extends Component {
constructor() {
super();
this.state = {
resultsItems: {}
}
};
componentDidMount() {
fetch(url)
.then(results => {
return results.json();
}).then(data => {
console.log(data.items);
let items = data.items.map((item) => {
console.log(item.title);
return (
<li>
<h2>item.title</h2>
</li>
)
});
this.setState({resultsItems: items});
console.log("state", this.state.resultsItems);
})
.catch(error => console.log(error))
};
render() {
return (
<div>
<button onClick={() => this.props.updateLoginStatus(false)}>
Logout
</button>
<div>
ID: {this.props.user}
{this.state.resultsItems}
</div>
</div>
)
}
}
By way of demonstrating the sort of thing Hamms is talking about in their comment:
class UserData extends Component {
constructor () {
super()
this.state = {
resultsItems: []
}
}
componentDidMount () {
// Simulate API response
const resultsItems = [
{ title: 'foo' },
{ title: 'bar' },
{ title: 'wombat' }
]
this.setState({ resultsItems })
}
render () {
return (
<div>
{this.state.resultsItems.map(item => <ResultsItem item={item} />)}
</div>
)
}
}
function ResultsItem ({ item }) {
return <li>{item.title}</li>
}
However, Chris' answer is correct as to the cause of the error message: the first render tries to use an empty object and not an array, which fails.
It seems like you are correctly setting an array to your state on componentDidMount, however the initial state in your constructor is an object and not an array!
So change this:
this.state = {
resultsItems: {}
}
to this:
this.state = {
resultsItems: []
}
I have been searching for solutions for day, I learned a lot, but did not figure out what is wrong. Here what I do:
Calling the App constructor; initialising the state loading, dataSource and data
when the component mounts, the program calls the getData function with the requested URL
The getData function is an asynchronous fetch function
then the data is converted to JSON
then the JSON is cloned to became a datablob for the webview
then the setState function is called, changing the loading and the data.
This is where the setState does not fire. Not even the render (it should). Every tutorial, every forum shows it to be this way (and its also logical).
Here is the code:
import Exponent from 'exponent';
import React from 'react';
import {
StyleSheet,
Text,
View,
ListView,
ActivityIndicator
} from 'react-native';
import { Button, Card } from 'react-native-material-design';
import {UnitMenuCard} from './unitmenucard.js';
class App extends React.Component {
constructor(props) {
super(props);
const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.state = {
loading: true,
dataSource: ds,
data: null
}
}
componentDidMount() {
//console.log('componentDidMount');
this.getData('https://dl.dropboxusercontent.com/u/76599014/testDATA.json');
}
getData(url) {
console.log('loading data');
return fetch(url).then(
(rawData) => {
console.log('parsing data');
//console.table(rawData);
return rawData.json();
}
).then(
(jsonData) =>
{
console.log('parsing to datablobs');
let datablobs = this.state.dataSource.cloneWithRows(jsonData);
console.log('datablobs: ' + datablobs);
return datablobs;
}
).then(
(datablobs) => {
console.log('setting state');
this.setState = ({
loading: false,
data: datablobs
});
console.log('the loading is ' + this.state.loading);
console.log('the data is ' + this.state.data);
}
).catch((errormsg) =>
{console.error('Loading error: ' + errormsg);}
);
}
render() {
console.log('loading is ' + this.state.loading);
var dataToDisplay = '';
if(this.state.loading) {
dataToDisplay = <ActivityIndicator animated={true} size='large' />
} else {
//let jdt = this.state.dataSource.cloneWithRows(this.state.data);
dataToDisplay = <ListView
dataSource={this.state.ds}
renderRow={(unit) => <UnitMenuCard name={unit.name} image={unit.picture} menu={unit.menu}/>}
/>
}
return (
<View style={styles.container}>
{dataToDisplay}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
});
Exponent.registerRootComponent(App);
Did I missed something? Thank you forward for your answer mighty Stack Overflow.
I think you misunderstood how DatSource works.
getInitialState: function() {
var ds = new ListViewDataSource({rowHasChanged: this._rowHasChanged});
return {ds};
},
_onDataArrived(newData) {
this._data = this._data.concat(newData);
this.setState({
ds: this.state.ds.cloneWithRows(this._data)
});
}
This is taken from the docs here: https://facebook.github.io/react-native/docs/listviewdatasource.html
See how you need to clone your datasource object in the state. The key thing is that you call cloneWithRows passing the data you got back from the API (in your case jsonData). This creates a cloned datasource, containing the data you just fetched.
In your code instead you just create a clone of your data source, but never replace the actual one you list view is linked to. You should do it this way:
.then(
(datablobs) => {
console.log('setting state');
this.setState = ({
loading: false,
dataSource: datablobs
});
console.log('the loading is ' + this.state.loading);
console.log('the data is ' + this.state.data);
}
You don't need state.data for this, the list view reads from the datasource object. You can also avoid having two .then calls by simply doing everything in one.
You also have another problem. You have the list view linked to the wrong property in the state. You list view code in render should be:
dataToDisplay = <ListView
dataSource={this.state.dataSource}
renderRow={(unit) => <UnitMenuCard name={unit.name} image={unit.picture} menu={unit.menu}/>}
/>
this.state.dataSource is where you store the data source object.
dataToDisplay = <ListView
dataSource={this.state.ds} // <- you set the dataSource to this.state.ds instead of this.state.data
renderRow={(unit) => <UnitMenuCard name={unit.name} image={unit.picture} menu={unit.menu}/>}
/>
The data source should be the data you got from your fetch call. Therefore, dataSource should be set equal to this.state.data.
I have parent component and child(listView) component. My target is to send backend dispatched data from parent to child. I achieve that via button click. Problem is that child renders after second parent button click. Maybe my mistake is somewhere in componentWillReceiveProps?
Parent:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: '',
noRepos: false
};
}
componentWillReceiveProps(newProps) {
this.setState({
dataSource: newProps.data
});
this.validateData(newProps)
}
submitUsername() {
if(this.validateInput()){
this.props.dispatch({type: 'DUMMY', state: this.state.username});
} else {
this.setState({emptyInput: true});
}
}
render() {
return (
...
<View>
<ListViewComponent dataRecieved={this.state.dataSource} />
</View>
...
);
}
}
export default connect(state => {
return {
data: state.data,
};
})(Parent);
Child:
export default class ListViewComponent extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
}),
};
}
propTypes: {
dataRecieved: PropTypes.func.string
};
componentWillReceiveProps(newProps) {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(this.props.dataRecieved),
});
}
renderRow(rowData) {
return (
<View>
<Text>{rowData.name}</Text>
</View>
);
}
render() {
return (
<View>
<ListView dataSource={this.state.dataSource}
enableEmptySections={true}
renderRow={this.renderRow} />
</View>
);
}
}
Alright, a general advice is to always keep a single source of truth:
do not copy the data that you already have in props to your internal component state. Use the data in props.
try to create your components as stateless as possible (see above...use props, or have the component listen to a 'store'. See Redux or AltJs).
Specifically to try to solve your issue:
In parent replace:
<ListViewComponent dataRecieved={this.state.dataSource} />
with
<ListViewComponent dataRecieved={this.props.data} />
And in ListViewComponent, don't do:
this.setState({
dataSource: this.state.dataSource.cloneWithRows(this.props.dataRecieved),
});
but do:
render() {
var ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2,
})
, dataSource = ds.cloneWithRows(this.props.dataRecieved);
return (
<View>
<ListView dataSource={dataSource}
enableEmptySections={true}
renderRow={this.renderRow} />
</View>
);
}
The above is untested code, but should serve as a guide to what approach to follow.
The way you originally implemented ListViewComponent is fine, and you SHOULD be using componentWillReceiveProps when refreshing your ListView. Every best practice out there says to do this (just Google react native redux listview). You just have a slight error in your implementation that I mentioned in a comment above. Also, you should not be recreating the ListView.DataSource inside the render function, that is not good for performance and defeats the purpose of rowHasChanged in ListView.DataSource.
The error that I'm talking about is here:
componentWillReceiveProps(newProps) {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(this.props.dataRecieved),
});
}
it should be:
// ListViewComponent
componentWillReceiveProps(newProps) {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(newProps.dataRecieved),
});
}
Also, your Parent should not be holding a dataSource in its state, just pass data straight down to ListViewComponent because Redux is passing it as a prop already:
class Parent extends Component {
constructor(props) {
super(props);
this.state = {
// ...
};
}
componentWillReceiveProps(newProps) {
// ...
}
submitUsername() {
if (this.validateInput()) {
this.props.dispatch({ type: 'DUMMY', ... });
} else {
// ...
}
}
render() {
return (
...
<View>
{/* this.props.data automatically comes from the `connect` wrapper below */}
<ListViewComponent dataRecieved={this.props.data} />
</View>
...
);
}
}
export default connect(state => {
return {
data: state.data,
};
})(Parent);
You should also take a look at this gist. It is an example of how to use Redux alongside a ListView. His example uses cloneWithRowsAndSections, but because you don't have sections, you just adapt with cloneWithRows instead. This gist was written by a pretty active core React Native developer.