I'm working with reactjs and cannot seem to prevent this error when trying to display JSON data (either from file or server):
Uncaught TypeError: this.props.data.map is not a function
I've looked at:
React code throwing “TypeError: this.props.data.map is not a function”
React.js this.props.data.map() is not a function
Neither of these has helped me fix the problem. After my page loads, I can verify that this.data.props is not undefined (and does have a value equivalent to the JSON object - can call with window.foo), so it seems like it isn't loading in time when it is called by ConversationList. How do I make sure that the map method is working on the JSON data and not an undefined variable?
var converter = new Showdown.converter();
var Conversation = React.createClass({
render: function() {
var rawMarkup = converter.makeHtml(this.props.children.toString());
return (
<div className="conversation panel panel-default">
<div className="panel-heading">
<h3 className="panel-title">
{this.props.id}
{this.props.last_message_snippet}
{this.props.other_user_id}
</h3>
</div>
<div className="panel-body">
<span dangerouslySetInnerHTML={{__html: rawMarkup}} />
</div>
</div>
);
}
});
var ConversationList = React.createClass({
render: function() {
window.foo = this.props.data;
var conversationNodes = this.props.data.map(function(conversation, index) {
return (
<Conversation id={conversation.id} key={index}>
last_message_snippet={conversation.last_message_snippet}
other_user_id={conversation.other_user_id}
</Conversation>
);
});
return (
<div className="conversationList">
{conversationNodes}
</div>
);
}
});
var ConversationBox = React.createClass({
loadConversationsFromServer: function() {
return $.ajax({
url: this.props.url,
dataType: 'json',
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadConversationsFromServer();
setInterval(this.loadConversationsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="conversationBox">
<h1>Conversations</h1>
<ConversationList data={this.state.data} />
</div>
);
}
});
$(document).on("page:change", function() {
var $content = $("#content");
if ($content.length > 0) {
React.render(
<ConversationBox url="/conversations.json" pollInterval={20000} />,
document.getElementById('content')
);
}
})
EDIT: adding sample conversations.json
Note - calling this.props.data.conversations also returns an error:
var conversationNodes = this.props.data.conversations.map...
returns the following error:
Uncaught TypeError: Cannot read property 'map' of undefined
Here is conversations.json:
{"user_has_unread_messages":false,"unread_messages_count":0,"conversations":[{"id":18768,"last_message_snippet":"Lorem ipsum","other_user_id":10193}]}
The .map function is only available on array.
It looks like data isn't in the format you are expecting it to be (it is {} but you are expecting []).
this.setState({data: data});
should be
this.setState({data: data.conversations});
Check what type "data" is being set to, and make sure that it is an array.
Modified code with a few recommendations (propType validation and clearInterval):
var converter = new Showdown.converter();
var Conversation = React.createClass({
render: function() {
var rawMarkup = converter.makeHtml(this.props.children.toString());
return (
<div className="conversation panel panel-default">
<div className="panel-heading">
<h3 className="panel-title">
{this.props.id}
{this.props.last_message_snippet}
{this.props.other_user_id}
</h3>
</div>
<div className="panel-body">
<span dangerouslySetInnerHTML={{__html: rawMarkup}} />
</div>
</div>
);
}
});
var ConversationList = React.createClass({
// Make sure this.props.data is an array
propTypes: {
data: React.PropTypes.array.isRequired
},
render: function() {
window.foo = this.props.data;
var conversationNodes = this.props.data.map(function(conversation, index) {
return (
<Conversation id={conversation.id} key={index}>
last_message_snippet={conversation.last_message_snippet}
other_user_id={conversation.other_user_id}
</Conversation>
);
});
return (
<div className="conversationList">
{conversationNodes}
</div>
);
}
});
var ConversationBox = React.createClass({
loadConversationsFromServer: function() {
return $.ajax({
url: this.props.url,
dataType: 'json',
success: function(data) {
this.setState({data: data.conversations});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
getInitialState: function() {
return {data: []};
},
/* Taken from
https://facebook.github.io/react/docs/reusable-components.html#mixins
clears all intervals after component is unmounted
*/
componentWillMount: function() {
this.intervals = [];
},
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() {
this.intervals.map(clearInterval);
},
componentDidMount: function() {
this.loadConversationsFromServer();
this.setInterval(this.loadConversationsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="conversationBox">
<h1>Conversations</h1>
<ConversationList data={this.state.data} />
</div>
);
}
});
$(document).on("page:change", function() {
var $content = $("#content");
if ($content.length > 0) {
React.render(
<ConversationBox url="/conversations.json" pollInterval={20000} />,
document.getElementById('content')
);
}
})
You need to create an array out of props.data, like so:
data = Array.from(props.data);
then will be able to use data.map() function
More generally, you can also convert the new data into an array and use something like concat:
var newData = this.state.data.concat([data]);
this.setState({data: newData})
This pattern is actually used in Facebook's ToDo demo app (see the section "An Application") at https://facebook.github.io/react/.
It happens because the component is rendered before the async data arrived, you should control before to render.
I resolved it in this way:
render() {
let partners = this.props && this.props.partners.length > 0 ?
this.props.partners.map(p=>
<li className = "partners" key={p.id}>
<img src={p.img} alt={p.name}/> {p.name} </li>
) : <span></span>;
return (
<div>
<ul>{partners}</ul>
</div>
);
}
Map can not resolve when the property is null/undefined, so I did a control first
this.props && this.props.partners.length > 0 ?
I had the same problem. The solution was to change the useState initial state value from string to array.
In App.js, previous useState was
const [favoriteFilms, setFavoriteFilms] = useState('');
I changed it to
const [favoriteFilms, setFavoriteFilms] = useState([]);
and the component that uses those values stopped throwing error with .map function.
Sometimes you just have to check if api call has data returned yet,
{this.props.data && (this.props.data).map(e => /* render data */)}
Create an array from props data.
let data = Array.from(props.data)
Then you can use it like this:
{ data.map((itm, index) => {
return (<span key={index}>{itm}</span>)
}}
If you're using react hooks you have to make sure that data was initialized as an array. Here's is how it must look like:
const[data, setData] = useState([])
You don't need an array to do it.
var ItemNode = this.state.data.map(function(itemData) {
return (
<ComponentName title={itemData.title} key={itemData.id} number={itemData.id}/>
);
});
You need to convert the object into an array to use the map function:
const mad = Object.values(this.props.location.state);
where this.props.location.state is the passed object into another component.
As mentioned in the accepted answer, this error is usually caused when the API returns data in a format, say object, instead of in an array.
If no existing answer here cuts it for you, you might want to convert the data you are dealing with into an array with something like:
let madeArr = Object.entries(initialApiResponse)
The resulting madeArr with will be an array of arrays.
This works fine for me whenever I encounter this error.
I had a similar error, but I was using Redux for state management.
My Error:
Uncaught TypeError: this.props.user.map is not a function
What Fixed My Error:
I wrapped my response data in an array. Therefore, I can then map through the array. Below is my solution.
const ProfileAction = () => dispatch => {
dispatch({type: STARTFETCHING})
AxiosWithAuth()
.get(`http://localhost:3333/api/users/${id here}`)
.then((res) => {
// wrapping res.data in an array like below is what solved the error
dispatch({type: FETCHEDPROFILE, payload: [res.data]})
}) .catch((error) => {
dispatch({type: FAILDFETCH, error: error})
})
}
export default ProfileAction
You should try this:
const updateNews = async()=>{
const res= await fetch('https://newsapi.org/v2/everything?q=tesla&from=2021-12-30&sortBy=publishedAt&apiKey=3453452345')
const data =await res.json();
setArticles(data)
}
Add this line.
var conversationNodes =this.props.data.map.length>0 && this.props.data.map(function(conversation, index){.......}
Here we are just checking the length of the array. If the length is more than 0, Then go for it.
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': '2',
I delete that code line in setting it to fix it
You might need to convert the objects into an array to use the map function since you are getting the JSON data in array form, I changed it to:
const [data, setData] = useState([]);
from:
const [data, setData] = useState(0);
I Hope, it might be helpful for you.
Thanks
try componentDidMount() lifecycle when fetching data
Here I create TodoItems component, and I try to store array as a state, but console says that my entries is not defined:
var TodoItems = React.createClass({
getInitialState: function() {
return {
entries: this.props.entries
};
},
removeItem: function(key){
var itemArray = this.state.entries;
for (var i = 0; i < itemArray.length; i++)
if (itemArray[i.key] === key) {
itemArray.splice(i, 1);
break;
}
this.setState({
entries: entries
})
},
This is the prop I'm trying to store:
</div>
<TodoItems entries={this.state.items}/>
</div>
You are doing a mistake, setting state as entries in removeItem function whereby you should be setting it to itemArray, also , assign itemArray as var itemArray = [...this.state.entries]; because in your style of declaration it will refer to the state entries itself and will mutate state directly, which is not advisable, and getInitialState is called only on the first render of TodoItems so if you are assigning a state with props make sure to do that in the componentWillReceiveProps function as well, since it will be called everytime the parent renders and will have the updated props
var TodoItems = React.createClass({
getInitialState: function() {
return {
entries: this.props.entries
};
},
componentWillReceiveProps(nextProps) {
this.setState({entries: nextProps.entries})
}
removeItem: function(key){
var itemArray = [...this.state.entries];
for (var i = 0; i < itemArray.length; i++)
if (itemArray[i.key] === key) {
itemArray.splice(i, 1);
break;
}
this.setState({
entries: itemArray
})
},
I have a parent class and a child class. The child class will perform an initial load operation based on a property passed from the parent. The parent class will access that data using a static method from the child. In essence, I'm trying to use the child class as a service. The code below illustrates the scenario (note that it's just pseudo code).
var Parent = React.createClass({
componentDidMount: function() {
console.log("parent component was mounted");
},
render: function() {
return (
<div>
<Child param={this.props.param} />
<p>Child.fetch('key')</p>
</div>
);
}
});
var Child = React.createClass({
getInitialState: function() {
return {
data: {}
};
},
componentDidMount: function() {
console.log("child component was mounted");
$.ajax({
url: "server/api.php",
data: {
param: param
}
}).done(function(response) {
this.setState({data: response});
}.bind(this));
},
statics: {
get: function(key) {
console.log('get requested for key: ' + key);
var value = null; // default value
if(this.data == null) { return value; }
//get the value from the data based on the key
return value;
}
},
render: function() {
return (
<h2>{this.props.param}</h2>
);
}
});
The problem here is that the parent's render function does not update after the data was loaded from the child class. The order of the console.log results is:
Parent render: get requested for key: key
parent component was mounted
child component was mounted
=> should trigger an update to re-render the parent
I'm not sure if its possible to trigger the parent's render function only once after the child component has been loaded. I'm guessing its not, so triggering an update for the parent's render method would suffice. Any suggestions, improvements are welcome as this is fairly new to me.
You should add a callback prop to your Child component that the child can trigger once the data has been loaded.
On the Parent side you just need to call this.forceUpdate().
What you are experiencing is expected since the lifecycle of your Child component is different than the one for your Parent class.
Still, you might need to analyze the possibility to move your load logic into the Parent and delegate the rendering of parts of the response to each child. This will also help performance since only one HTTP request will be needed to load all the data.
** Code **
var Parent = React.createClass({
componentDidMount: function() {
console.log("parent component was mounted");
},
render: function() {
return (
<div>
<Child param={this.props.param} onDataLoaded={this.forceUpdate} />
<p>Child.fetch('key')</p>
</div>
);
}
});
var Child = React.createClass({
getInitialState: function() {
return {
data: {}
};
},
componentDidMount: function() {
console.log("child component was mounted");
$.ajax({
url: "server/api.php",
data: {
param: param
}
}).done(function(response) {
if (typeof this.onDataLoaded === 'function')
this.onDataLoaded();
this.setState({data: response});
}.bind(this));
},
statics: {
get: function(key) {
console.log('get requested for key: ' + key);
var value = null; // default value
if(this.data == null) { return value; }
//get the value from the data based on the key
return value;
}
},
render: function() {
return (
<h2>{this.props.param}</h2>
);
}
});
Is there an established pattern used to manage user interactions with individual components, such as displaying loader spinners, disabling input fields while a form is saving/loading, etc.?
I'm finding myself doing the following in my stores in order to keep components somewhat decoupled from any implied state:
function CampaignStore() {
EventEmitter.call(this);
AppDispatcher.register(payload => {
switch (payload.type) {
// [#1] ---------------v (main action)
case CampaignContants.SAVE:
// [#2] ------------------------v (prepare for the main action)
this.emit(CampaignContants.WILL_SAVE);
const data = payload.data;
if (data.id) {
// [#3] ---v (perform main action in store)
updateCampaign(payload.data).then(_ => {
// [#4] ------------------------v (after main action)
this.emit(CampaignContants.DID_SAVE, 0)
});
} else {
insertCampaign(payload.data).then(campaignId => this.emit(CampaignContants.DID_SAVE, campaignId));
}
break;
// ...
}
}
}
Basically, I just fire an event saying that some action is about to take place, then I perform the action (make API call, etc.), then emit another event when the action completes.
Inside a component, I can just subscribe to a WILL_<action> event, render all the spinners and such, then clear up the screen when the DID_<action> is fired. While this seems to work, it does feel pretty boilerplattie and repetitive, as well as super messy (way too much state that only exists to tweak the UI based on where an action is (between WILL_<action> and *DID_<action>.
// some component
var MyComponent = React.createClass({
getInitialState: function () {
return {
items: [],
loading: false,
saving: false,
checkingPasswordStrength: fase,
// ...
};
},
render: function(){
return (
<div>
{this.state.loading && (
<p>Loading...</p>
)}
{!this.state.loading && (
// Display component in not-loading state
)}
</div>
);
}
});
I think you would be better off using the lifecycle methods such as componentWillMount, componentDidMount, componentWillUpdate, and componentWillUnmount. Using those methods you can inspect the previous/current/next props/state (depending on the method) and respond to that. That way your store only handles your state, and your components become more pure.
we have found a simple loading container component helps here.
so something like this:
const LoadingContainer = React.createClass({
getDefaultProps: function() {
return {isLoadedCheck:(res) => res.data!=null }
},
getInitialState: function() {
return {isLoaded:false, errors:[]}
},
componentDidMount: function() {
if(this.props.initialLoad) { this.props.initialLoad(); }
if(this.props.changeListener) { this.props.changeListener(this.onChange); }
},
onChange: function() {
let res = this.props.loadData();
this.setState({errors: res.errors, isLoaded: this.props.isLoadedCheck(res)});
},
render: function() {
if(!this.state.isLoaded) {
let errors = this.state.errors && (<div>{this.state.errors.length} errors</div>)
return (<div>{errors}<LoadingGraphic /> </div>)
}
return <div>{this.props.children}</div>
}
});
const Wrapper = React.createClass({
getDefaultProps: function() {
return {id:23}
},
render: function() {
let initialLoad = () => someActionCreator.getData(this.props.id);
let loadData = () => someStore.getData(this.props.id);
let changeListener = (fn) => someStore.onChange(fn);
return (<div><LoadingContainer initialLoad={initialLoad}
changeListener={changeListener}
loadData={loadData}
isLoadedCheck={(res) => res.someData != null}><SomeComponent id={this.props.id} /></LoadingContainer></div>)
}
});
while it adds another stateless wrapper, it gives a clean way to make sure your components dont just load on mount and a common place to show api feedback etc.
and with react 14 these sort of pure stateless wrappers are getting a bit of a push, with perf improvements to come, so we've found it scales nicely
This is the pattern which will help you in getting your individual components to manage user interactions
var MyComponent = React.createClass({
getInitialState: function() {
return {
item: [],
loading: true,
};
},
componentDidMount: function() {
//Make your API calls here
var self = this;
$.ajax({
method: 'GET',
url: 'http://jsonplaceholder.typicode.com/posts/1',
success: function(data) {
if (self.isMounted()) {
self.setState({
item: data,
loading: false
});
}
}
});
},
render: function() {
var componentContent = null;
if (this.state.loading) {
componentContent = (<div className="loader"></div>);
} else {
componentContent = (
<div>
<h4>{this.state.item.title}</h4>
<p>{this.state.item.body}</p>
</div>
);
}
return componentContent;
}});
I have the following GoogleMaps React component:
var GoogleMaps = React.createClass({
getDefaultProps: function () {
return {
initialZoom: 6,
mapCenterLat: 53.5333,
mapCenterLng: -113.4073126
};
},
componentDidMount: function (rootNode) {
var mapOptions = {
center: this.mapCenterLatLng(),
zoom: this.props.initialZoom
},
map = new google.maps.Map(this.getDOMNode(), mapOptions);
var marker = new google.maps.Marker({position: this.mapCenterLatLng(), title: 'Hi', map: map});
this.setState({map: map});
},
mapCenterLatLng: function () {
var props = this.props;
return new google.maps.LatLng(props.mapCenterLat, props.mapCenterLng);
},
render: function () {
return (
<div className='map-gic'></div>
);
}
});
And then use it via the following declaration:
var myPage = React.createClass({
render: function() {
return (
<div>
<GoogleMaps mlat="55.0000" mlong="-113.0000" />
</div>
)
}
});
How do I access the passed values (mlat and mlong) so that the location would be marked on the rendered map? At the moment, the values in the getDefaultProps function are the ones being marked on the map.
Rephrase the question:
How do I replace the value of the variables mapCenterLat and mapCenterLng in the getDefaultProps function with the values I pass through the component rendering?
When I check the value passed in the render function of the GoogleMaps component by adding some console.logs like this:
render: function () {
console.log(this.props.mlat);
console.log(this.props.mlong);
return (
<div className='map-gic'></div>
)
}
This is what registers in the browser console:
undefined
undefined
undefined
undefined
55.0000
113.000
The source code for this application is in this GitHub repo.
Here is a basic working example.
I'm not sure what you're code is doing wrong as it does not entirely match what it would need to be to work or demonstrate the problem.
I've not attempted to clean up the code further. It doesn't work if the props change (as componentDidMount only executes once), and doesn't properly remove the Map when React executes an unmount on the control.
var GoogleMap = React.createClass({
getDefaultProps: function () {
return {
initialZoom: 8,
mapCenterLat: 41.8, /* Chicago by default */
mapCenterLng: -87.6,
};
},
componentDidMount: function (rootNode) {
var mapOptions = {
center: this.mapCenterLatLng(),
zoom: this.props.initialZoom
};
var map = new google.maps.Map(this.getDOMNode(), mapOptions);
var marker = new google.maps.Marker({position: this.mapCenterLatLng(), map: map});
this.setState({map: map});
},
mapCenterLatLng: function () {
var props = this.props;
return new google.maps.LatLng(
props.mapCenterLat, props.mapCenterLng);
},
render: function () {
return (
<div className="gmap"></div>
);
}
});
// Bay area override
React.renderComponent(
<GoogleMap mapCenterLat='37' mapCenterLng='-122' />,
document.body);