I'm trying to render out a redux state by mapping through an array of objects but I'm getting map is not a function. I can console.log my props to see it is receiving but it looks as though it's trying to map through it before the props have been passed into the component. As you can see I've tried also using the && method as others have suggested but all I get back is:
TypeError: myItems.map is not a function
Here's the code I have
import React, {Component} from 'react';
import { connect } from 'react-redux';
class RandomComponent extends Component {
state = {
myItems: this.props.myItems
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('Styles: ', this.props.myItems); // Returns object array
}
render() {
const {myItems} = this.props; // also tried this.state
return (
<ul>
{myItems && myItems.map((item) => {
return <span>Hello.</span>
})}
</ul>
);
}
}
const mapStateToProps = state => ({
myItems: state.getmyItems.myItems
});
export default connect(mapStateToProps)(RandomComponent);
Your initialState is an object, set it to an empty array []. In your catch return an empty array and not an empty object. The && does not work because an empty object "exists". If u still want to use the && then set initialState to undefined
map is a function for arrays your data type might be an object. To iterate over an object you can use for ... in
Related
I have the following code:
import React, { Component } from 'react';
import axios from 'axios';
class Dashboard extends Component {
state = {
name : 'randomname',
apiData: {}
};
componentDidMount() {
axios.get('https://api_url/getdata)
.then(res => {
const apiData = res.data
this.setState({apiData});
});
}
render() {
const { name, apiData} = this.state;
//THIS WORKS
var objTest = [{game_id: 2}]; //This is returned from apical
console.log(objTest[0].game_id);
console.log(apiData); //logs the following: [{game_id: 2}]
console.log(apiData[0]); //logs the following: {game_id: 2}
console.log(apiData[0].game_id); //Error occurs See error below
return (
<div className="wrap">
TESTING
</div>
);
}
}
export default Dashboard;
While testing and trying to get game_id using: console.log(apiData[0].game_id); I get the following error:
TypeError: undefined is not an object (evaluating
'apiData[0].game_id')
I would like to know why this works when I declare a variable and assign it the same values as the api call returns. But it does not work then I'm assigning the api call to apiData. It can only access apiData[0] which returns {game_id:2} , but cannot access apiData[0].game_id.
Thanks for all the help!
The main issue here is the order of life cycle methods. During mounting phase the constructor and then the render method is called. ComponentDidMount is not called yet and hence your state is empty. The reason you are not getting error when you log apiData or apiData[0] is it simply loggs empty array or object during initial render call (mounting phase) and then the actual object during the second render after componentDidMount(updateing phase). But when you try to call the property(game_id), you get an error(undefined) during the mounting phase since you are calling it on an empty array/object.
The solution is check for the existance of the parent object before calling the property on it , for example , usine optional chaining (JS2020 new future) which checks apiData[0], the error should be fixed just py appending "?" after the object. you can also use other methods for older JS.
console.log(apiData[0]?.game_id)
ComponentDidMount is triggered after the render method has loaded. Which means that console.log(apiData[0]) is calling the default state first before componentDidMount method is called.
Default state is an empty object here and not an array. So the index 0 of apiData is nothing. Changing the default state to apiData: [{game_id: null}] will give you the result and state will change once the componentDidMount is triggered and the api is successfully called.
This however is not the best approach. It's just to make things clear and understandable.
Simply defined one flag in the state and check whether your data is available or not once you get data change that flag and and load your component element accordingly.
see below solution for your problem statement.
import React, { Component } from 'react';
import axios from 'axios';
class Dashboard extends Component {
state = {
loading:true,
name : 'randomname',
apiData: {}
};
componentDidMount() {
axios.get('https://api_url/getdata').then(res => {
this.setState({apiData:res.data, loading:false});
});
}
render() {
const { name, apiData, loading} = this.state;
//THIS WORKS
var objTest = [{game_id: 2}]; //This is returned from apical
console.log(objTest[0].game_id);
console.log(apiData); //logs the following: [{game_id: 2}]
console.log(apiData[0]); //logs the following: {game_id: 2}
console.log(apiData[0].game_id); //Error occurs See error below
return (
<div className="wrap">
{loading ? <div>Loading ...</div> : <div className="wrap">TESTING</div>}
</div>
);
}
}
export default Dashboard;
I have a redux state which is working fine in that my data is in the redux store. I can console.log it out with console.log(this.props) but I can't seem to render it out. It returns this error:
TypeError: Cannot read property 'somevalue' of undefined
Ususally I would map over props with a static block of html/jsx but I need this to be different html per loop so I'm trying to insert the values directly into the markup. Can anyone point me in the right direction please?
import React, {Component} from 'react';
import { connect } from 'react-redux';
class UnitClass extends Component {
state = {
unit: {}
}
render() {
console.log('Props is:', this.props); // shows correct values
return (
<ul>
<li>{this.props.unit.somevalue.value1}</li>
<li>{this.props.unit.somevalue.value2}</li>
<li>{this.props.unit.somevalue.value3}</li>
<li>{this.props.unit.somevalue.value4.someothervalue}</li>
</ul>
);
}
}
const mapStateToProps = state => ({
unit: state.setUnit.unit,
});
export default connect(mapStateToProps)(UnitClass);
try something like this
render() {
console.log('Props is:', this.props); // shows correct values
const {unit} = this.props;
return (
<ul>
{unit.somevalue && unit.somevalue.map((value)=>{
return value.someothervalue? <li>{value.someothervalue}</li>: <li>{value}</li>})
}
</ul>
);
}
note: for conditional rendering, you can use ternary operator and if you have to deal with nested conditional rendering, then I would recommend Higher Order Components, and then probably this
I'm trying to render out a mapped array supplied by props. The trouble is that the array is not always available depending on the parent's state. If I try to apply a map to a non-existent array, I get an error (naturally!). So I've tried to set a default empty array to get past the error.
If I set the default REACT thinks I'm trying to map over an object. The following code has been simplified for example purposes, but the end result is the same:
export class Parent extends Component {
constructor(props) {
super(props);
this.state = {
// This array is not always available
// It's actually a deep property in an object
// done here for simplicity
myArray: [{name: 'foo'},{name: 'bar'}],
};
}
render() {
return (
<Child myArray={this.state.myArray} />
);
}
}
import React, { Component } from 'react';
export class Child extends Component {
render() {
console.log(this.props.myArray); //=> [{...},{...}] Array
console.log(typeof this.props.myArray); //=> Object (wuh???)
this.props.myArray.map((item) => console.log(item.name)); //=> 'foo' then 'bar'
// Supply a default empty array here to avoid mapping an undefined
const list = this.props.myArray || [];
list.map((item) => {
return (
<li key={item.name}>{item.name}</li>
);
});
return (
<ul> {list} </ul>
);
}
}
Uncaught Error: Objects are not valid as a React child (found: object with keys {name}). If you meant to render a collection of children, use an array instead.
If I hard code the array and leave out the default empty array, I don't get an error:
const list = this.props.myArray.map((item) => {
return (
<li key={item.name}>{item.name}</li>
);
});
Any ideas on how I can apply a map to a conditional array?
Use default props for your Child component as an empty array:
import React, { Component } from 'react';
export class Child extends Component {
render() {
const list = this.props.myArray.map((item) =>(
<li key={item.name}>{item.name}</li>
));
return (
<ul> {list} </ul>
);
}
}
Child.defaultProps = {
myArray: []
};
.map returns a new array rather than modifying the existing array (which you shouldn't anyway, since props are meant to be read-only).
You can assign .map to a new variable, or do the mapping directly in JSX:
<ul>{list.map(item => <li key={item.name}>{item.name}</li>)}</ul>
You can also just do {(this.props.myArray || []).map(...)} or use default props, or use [] (instead of null etc) for your initial state in parent component corresponding to props.myArray.
It's my understanding that the most common use care for iterating over a list of data is map, which is an array method that iterates over an array, but when I tried to apply it here:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import axios from 'axios';
class QuestionList extends Component {
state = { questions: [] };
componentWillMount() {
axios
.get('https://opentdb.com/api.php?amount=10&difficulty=hard&type=boolean')
.then(response => this.setState({ questions: response.data }));
}
// renderQuestions() {
// return this.state.questions.map(question => <Text>{}</Text>);
// }
render() {
console.log(this.state);
return (
<View>
<Text>{}</Text>
</View>
);
}
}
export default QuestionList;
I ended up getting an error in the Simulator saying that this.state.questions.map() is not a function. I have searched for similar errors online, but they do not apply to my use case.
Keep in mind I commented out the code and erased what I had inside of <Text> because my machine was about to take off.
I don't know what this error means short of not being able to use the map() array helper method, does that mean I need to be applying a different helper method to iterate through this list of questions?
I did a console log of the response object like so:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import axios from 'axios';
class QuestionList extends Component {
state = { questions: [] };
componentWillMount() {
axios
.get('https://opentdb.com/api.php?amount=10&difficulty=hard&type=boolean')
.then(response => console.log(response));
}
render() {
console.log(this.state);
return (
<View>
<Text>{}</Text>
</View>
);
}
}
export default QuestionList;
and I got back the response object in the console:
from axios with a status of 200 which means the request was successful. You will notice I also go the data property and inside that is the results property and then the category with questions is inside of it:
So I am wondering if its that results property that I need to also implmement, but when I tried it I would get map() undefined.
Your API returns an object, which has no map method.
response.data.results is an array so change it to that if you intend to map over it:
this.setState({ questions: response.data.results }))
It's advisable to use componentDidMount instead of componentWillMount for async update.
I have looked at other questions that seemingly had a similar issue, but none of the accepted answers have solved my issue. I am attempting to fetch new names and load them into child component when redux is updated with new IDs.
When I use only redux and no state (as I would prefer), the new IDs do not get passed along to the child component and the names do not load at all
Alternatively, I have tried using state for the names in the child component (as you can see in the commented text below). However ... Oddly enough, every time the IDs are changed, the component loads the names based on the previous IDs rather than the current IDs.
Redux
const initialState = {
objectOfIds: {"someID":"someID", "aDifferentID":"aDifferentID"}, // I know this format may seem redundant and odd, but I have to keep it this way
arrayOfNames: ["John Doe", "Jenny Smith"]
}
Parent Compoenent
// React
import React from 'react';
import firebase from 'firebase';
// Components
import ListOfNames from './ListOfNames';
// Redux
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {set} from './../actions/index.js';
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.changeIDs = this.changeIDs.bind(this);
}
changeIDs() {
this.props.set("objectOfIds",{"aNewID":"aNewID","someOtherID":"someOtherID","anotherID":"anotherID"});
}
render (
return (
<div>
<h2>Parent Component</h2>
<button onClick={this.changeIDs}>Change Data</button>
<ListOfNames objectOfIds={this.props.reduxData.objectOfIds}/>
</div>
)
)
}
function mapStateToProps(state) {
return {
reduxData: state.reduxData
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
set: set
}, dispatch)
}
export default connect(mapStateToProps, matchDispatchToProps)(ParentComponent);
Child Compoenent
// React
import React from 'react';
import firebase from 'firebase';
// Redux
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {set} from './../actions/index.js';
// Firebase Database
var databaseRef;
class ListOfNames extends React.Component {
constructor(props) {
super(props);
this.state= {
arrayOfNames: []
}
this.fetchNamesForIds = this.fetchNamesForIds.bind(this);
this.add = this.add.bind(this);
}
componentDidMount() {
console.log("componentDidMount triggering...");
firebase.auth().onAuthStateChanged(function (user) {
if (!user) {
console.log("no user authenticated");
}
databaseRef = firebase.database().ref('/people/' + user.uid);
this.fetchNamesForIds(this.props.reduxData.objectOfIds);
})
}
// I have tried making the fetch in componentWillReceiveProps so that the function would run anytime the IDs were updated in redux, but "this.props.objectOfIds" and "this.props.reduxData.objectOfIds"
componentWillReceiveProps() {
console.log("componentWillReceiveProps triggering...");
console.log("this.props.objectOfIds");
console.log(this.props.objectOfIds);
console.log("this.props.reduxData.objectOfIds");
console.log(this.props.reduxData.objectOfIds);
this.fetchNamesForIds(this.props.reduxData.objectOfIds);
// Note: I have also tried: this.fetchNamesForIds(this.props.objectOfIds); so that the data is passed in from the parent
}
// fetched the names for the associated IDs
fetchNamesForIds(personIds) {
if (personIds === [] || personIds === undefined || personIds === null) {
ALTERNATIVE TO LINE ABOVE
I would prefer to store the data in redux so that it is accessible to other components, but doing this did allow the data to load, but it loads with a lag (i.e. when I change the IDs, it loads the names associated to the previous IDs)
// this.setState({
// arrayOfNames: []
// });
this.props.set("arrayOfNames", []);
return
}
var arrayOfNames = [];
// loop through person and set each value into the arrayOfNames array
Object.keys(IDs).map(function(person, index) {
console.log("person = " + person);
console.log("index = " + index);
// get names associated with the ids obtained
var name = ''
databaseRef.child('people').child(person).limitToFirst(1).on("value", function(snapshot) {
var firstName = snapshot.child('firstName').val()
var lastName = snapshot.child('firstName').val()
name = firstName + " " + lastName
console.log("name = " + name);
arrayOfNames.push(name);
console.log("arrayOfNames = " + arrayOfNames);
this.props.set("arrayOfNames", arrayOfNames);
ALTERNATIVE TO LINE ABOVE
I would prefer to store the data in redux so that it is accessible to other components, but doing this did allow the data to load, but it loads with a lag (i.e. when I change the IDs, it loads the names associated to the previous IDs)
// this.setState({
// arrayOfNames: arrayOfNames
// });
}.bind(this));
}.bind(this));
}
render() {
return(
(this.props.user.arrayOfNames === [] || this.props.user.arrayOfNames === undefined || this.props.user.arrayOfNames === null || this.props.user.arrayOfNames.length < 1)
? <span>no people selected</span>
: <div>
<h5>List of People</h5>
{this.props.user.arrayOfNames.map((name, index) => {
return (
<h5>{name}</h5>
)
})}
</div>
)
}
}
ListOfNames.propsTypes = {
objectOfIds: React.PropTypes.Object
};
function mapStateToProps(state) {
return {
reduxData: state.reduxData
};
}
function matchDispatchToProps(dispatch) {
return bindActionCreators({
set: set
}, dispatch)
}
export default connect(mapStateToProps, matchDispatchToProps)(ListOfNames);
Similar Questions:
https://github.com/gaearon/redux-thunk/issues/80
React Native Child Component Not Updated when redux state changes
update child component when parent component changes
Does anyone understand how I can get my component to load the data based on the current IDs in redux?
Probably because the object keys are changed but not the object reference.
A hacky solution would be to call this.forceUpdate() to update the component after the change:)
I had a similar issue where I was loading a child component multiple times on one page and despite passing in what I thought was a unique ID it would only reference the first ID. I know this isn't exactly the situation you have but this will allow you to have a unique object key AND a unique object reference which will hopefully fix your issue.
This is the package I used for this: https://www.npmjs.com/package/react-html-id. When you import the package you need to have curly brackets.
import { enableUniqueIds } from 'react-html-id'
The rest is explained on npm.
Tip: you don't need to bind your functions if you use the new javascript syntax.
this.add = this.add.bind(this); will be solved by writting the add method like:
add = () => {
};
The issue is the Child Component componentWillReceiveProps. You are not using the new props that are propagated to this component. componentWillReceiveProps is called with nextProps, which contains the updated props.
Use this in your child component
componentWillReceiveProps(nextProps) {
console.log("componentWillReceiveProps triggering...");
console.log("nextProps.objectOfIds ", nextProps.objectOfIds);
console.log("nextProps.reduxData.objectOfIds ", nextProps.reduxData.objectOfIds);
this.fetchNamesForIds(nextProps.reduxData.objectOfIds);
}