I have simple example with JSON that has array in it.
It is loaded with ajax:
{somekey: "value1", somekey2: "value2", belongsto: ["me","you"]}
I can render it by for example:
<div>
belongsto: {this.state.data.belongsto}
</div>
But I would love to render it as list in subcomponent, so I am trying:
<Uli data={this.state.data.belongsto}/>
as:
var Uli = React.createClass({
render: function() {
return (
<ul className="Uli">
{
this.props.data.map(function(value) {
return <li key={value}>{value}</li>
})
}
</ul>
)
}
});
And that gives me:
Uncaught TypeError: Cannot read property 'map' of undefined
How this should be achieved?
You are loading your json data through AJAX asynchronously, and hence belongsto is undefined until you'll got the response from the server.
There are several solutions to solve this:
Add getDefaultProps method to your ULi component.
var Uli = React.createClass({
getDefaultProps() {
return {
data: []
}
},
render: function() {
return (
<ul className="Uli">
{this.props.data.map(function(value) {
return <li key={value}>{value}</li>
})}
</ul>
)
}
});
Use || operator:
<ul className="Uli">
{(this.props.data || []).map(function(value) {
return <li key={value}>{value}</li>
})}
</ul>
Prevent ULi rendering, if does not belongsto is undefined:
{this.state.data.belongsto && <Uli data={this.state.data.belongsto}/>}
Related
Here is the problematic component in question.
const UserList = React.createClass({
render: function(){
let theList;
if(this.props.data){
theList=this.props.data.map(function(user, pos){
return (
<div className="row user">
<div className="col-xs-1">{pos}</div>
<div className="col-xs-5">{user.username}</div>
<div className="col-xs-3">{user.recent}</div>
<div className="col-xs-3">{user.alltime}</div>
</div>
);
}, this);
} else {
theList = <div>I don't know anymore</div>;
}
console.log(theList);
return (
theList
);
}
});
Whenever I attempt to return {theList}, I receive a Cannot read property '__reactInternalInstance$mincana79xce0t6kk1s5g66r' of null error. However, if I replace {theList} with static html, console.log prints out the correct array of objects that i want. As per the answers, I have tried to return both {theList} and theList but that didn't help.
In both cases, console.log first prints out [] which I assume is because componentDidMount contains my ajax call to get json from the server and has not fired yet before the first render(). I have tried to check against
this.props.data being null but it does not help.
Here is the parent component if it helps:
const Leaderboard = React.createClass({
getInitialState: function(){
return ({data: [], mode: 0});
},
componentDidMount: function(){
$.ajax({
url: 'https://someurlthatreturnsjson',
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error('https://someurlthatreturnsjson', status, err.toString());
}.bind(this)
});
},
render: function(){
return (
<div className="leaderboard">
<div className="row titleBar">
<img src="http://someimage.jpg"></img>Leaderboard
</div>
<HeaderBar />
<UserList data={this.state.data}/>
</div>
);
}
});
Ah OK, there were some interesting problems in here, but you were so close. The big one, with react you must always return a single top-level element (e.g. a div). So, your variable theList was actually an array of divs. You can't return that directly. But you can return it if it's wrapped in a single parent div.
const mockData = [
{
username: 'bob',
recent: 'seven',
alltime: 123,
},
{
username: 'sally mae',
recent: 'seven',
alltime: 133999,
},
];
var $ = {
ajax(opt) {
setTimeout(() => {
opt.success(mockData);
}, 200);
}
}
const UserList = React.createClass({
render: function(){
let theList;
if (this.props.data && this.props.data.length) {
theList = this.props.data.map(function(user, pos){
return (
<div key={user.username} className="row user">
<div className="col">{pos}</div>
<div className="col">{user.username}</div>
<div className="col">{user.recent}</div>
<div className="col">{user.alltime}</div>
</div>
);
});
} else {
theList = <div>There is NO data</div>;
}
return <div>{theList}</div>;
}
});
const Leaderboard = React.createClass({
getInitialState: function(){
return ({data: [], mode: 0});
},
componentDidMount: function(){
$.ajax({
url: 'https://someurlthatreturnsjson',
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error('https://someurlthatreturnsjson', status, err.toString());
}.bind(this)
});
},
render: function(){
return (
<div className="leaderboard">
<UserList data={this.state.data}/>
</div>
);
}
});
ReactDOM.render(
<Leaderboard/>,
document.getElementById('container')
);
.col {
width: 200px;
float: left;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
To explain the fiddle a little bit. Don't worry about the weird looking var $ stuff, I'm just stubbing out jQuery's ajax method so I can return some fake data after 200ms.
Also, for me jsfiddle gives me a 'bad config' message when I run it, but I close the message and the result is there. Don't know what that's about.
return (
{theList}
)
should just be
return theList
because you are not inside JSX at that point. What you're doing there will be interpreted as
return {
theList: theList
}
That's ES6 shorthand properties syntax.
Error can also arise from accessing nested state that doesn't exist:
I lack the reputation to comment, so adding an answer for future assistance -- I ran into this same issue for a different reason. Apparently, the error is triggered from an earlier error throwing off react's internal state, but the error is getting caught somehow. github issue #8091
In my case, I was trying access a property of state that didn't exist after moving the property to redux store:
// original state
state: {
files: [],
user: {},
}
// ... within render function:
<h1> Welcome {this.state.user.username} </h1>
I subsequently moved user to redux store and deleted line from state
// modified state
state: {
files: [],
}
// ... within render function (forgot to modify):
<h1> Welcome {this.state.user.username} </h1>
And this threw the cryptic error. Everything was cleared up by modifying render to call on this.props.user.username.
There is a small problem with the if statement:
if(this.props.data !== []){
should be:
if(this.props.data){
this.props.data is null, if the ajax call returns null. alternatively the code could be more elaborate.
const data = this.props.data;
if(data && data.constructor === Array && data.length > 0) {
Not sure if this is how you want to do it, but it works for me.
edit:
const UserList = React.createClass({
render: function() {
if(this.props.data){
return this.props.data.map(function(user, pos){
return (
<li> className="row user">
<span>{pos}</span>
<span>{user.username}</span>
<span>{user.recent}</span>
<span>{user.alltime}</span>
</li>
);
});
} else {
return <li>I don't know anymore</li>;
}
}
});
I encountered this error when I rendered a stateless component and decided to remove the react-root-element (wrapping div) after rendering it with basic dom manipulation.
Conclusion: be aware of this, better don't do it.
I've built a basic restuarant recommendation app that filters by location using the YELP api. The api was responding to my requests with the response object and everything was appending to my divs perfectly, but I realized that for my project, I needed to make a new layer for the data listing. Here are the relevant portions of my two components as they are now:
display-recs:
var DisplayRecs = React.createClass({
render: function() {
var recsLoop = [];
if (this.props.recommendations) {
for (var i=0; i < this.props.recommendations.length; i++) {
recsLoop.push(<Recommendations item={this.props.recommendations[i]} />)
}
}
console.log(this.props.recommendations);
return (
<div className="DisplayRecs">
{recsLoop}
</div>
);
}
});
var mapStateToProps = function(state, props) {
return {
recommendations: state.recommendations
};
};
recommendations:
var Recommendations = React.createClass({
render: function() {
<div id="bizData">
<div id='nameList'>{this.props.item.name}</div>
<div id='phoneList'>{this.props.item.phone}</div>
<div id='ratingList'>{this.props.item.rating}</div>
</div>
}
});
var mapStateToProps = function(state, props) {
return {
recommendations: state.recommendations
};
};
I cannot figure out why the nameList, phoneList, and ratingList will not print onto the dom. When I view the elements tab in my devtools, all i see is an empty displayrecs div. I've tried to just change things by guessing, but it's not been fruitful. Can any of you see an obvious problem with the current code?
Thanks
Your Recommendations react component's render function doesn't have any return statement. Try doing this:
var Recommendations = React.createClass({
render: function() {
return ( <div id="bizData">
<div id='nameList'>{this.props.item.name}</div>
<div id='phoneList'>{this.props.item.phone}</div>
<div id='ratingList'>{this.props.item.rating}</div>
</div>);
}
});
Also add a key to the Recommendations components as #Vikramaditya recommends:
recsLoop.push(<Recommendations key={i} item={this.props.recommendations[i]} />)
I have been having problems rendering a template function call from a meteor/angular template. I am trying to use the Moment.js package to format my time stamp. I have seen examples of this done with Blaze however, I can't replicate it in Meteor with Angular. My issue is with the formatTimestamp() function call. It isn't rendering anything and there are no errors in my console. Where am I going wrong?
My template
<li ng-repeat="task in $ctrl.tasks" ng-class="{'private' : task.private}">
<details>
<summary>{{task.administration.inventoryNumber}}</summary>
<ul class="bxslider">
<li><h3>Adminstration</h3>
<table>
<td>created at: </td><td>{{formatTimestamp(task.administration.createdAt)}}</td>
My controller
class TodosListCtrl {
constructor($scope) {
$scope.viewModel(this);
this.subscribe('tasks');
this.helpers({
tasks() {
const selector = {};
return Artefacts.find(selector, {
sort: {
createdAt: -1
}
});
},
currentUser() {
return Meteor.user();
},
formatTimestamp: function (timestamp) {
alert("timestamp");
console.log(timestamp);
return moment(timestamp).calendar();
}
})
}
Insert Mongo function
Artefacts.insert({
administration: {
inventoryNumber: invNum,
boxNumber: boxNum,
createdAt: new Date(),
Your custom function goes outside of the constructor, like this
class TodosListCtrl {
constructor($scope) {
$scope.viewModel(this);
this.subscribe('tasks');
this.helpers({
tasks() {
const selector = {};
return Artefacts.find(selector, {
sort: {
createdAt: -1
}
});
},
currentUser() {
return Meteor.user();
}
})
}
formatTimestamp(timestamp) {
alert("timestamp");
console.log(timestamp);
return moment(timestamp).calendar();
}
}
But also, remember in the view to call formatTimeStamp with $ctrl as prefix because it looks like you are using the ControllerAs syntax.
Your code:
<li ng-repeat="task in $ctrl.tasks" ng-class="{'private' : task.private}">
<details>
<summary>{{task.administration.inventoryNumber}}</summary>
<ul class="bxslider">
<li><h3>Adminstration</h3>
<table>
<td>created at: </td><td>{{formatTimestamp(task.administration.createdAt)}}</td>
How it should be:
<li ng-repeat="task in $ctrl.tasks" ng-class="{'private' : task.private}">
<details>
<summary>{{task.administration.inventoryNumber}}</summary>
<ul class="bxslider">
<li><h3>Adminstration</h3>
<table>
<td>created at: </td><td>{{$ctrl.formatTimestamp(task.administration.createdAt)}}</td>
don't need to create a function for date format. It is bad practice to call a function with angular expressions {{}}. You can format your date in Meteor helper.
this.helpers({
tasks() {
const selector = {};
this.value = Artefacts.find(selector, {
sort: {
createdAt: -1
}
// add filter here on createdAt
$filter('filter')(this.value.cretedAt);
});
},
I am building a recipe box in reactJS. My objective is hide ingredients within the button, listing the recipe title. Thus when a person clicks on a button titled "cheesecake" they will see its respective ingredients. The booleans in communicating when and when not to do this makes sense as it relates to "Onclick". However, I'm unsure of how to coordinate this action when fetching data given that my ingredients data (this.props.ingredients) is implicated within recipetitlebutton component. I tried re-initializing the ingredients component within the title button thinking that I can just define it within the recipeTitleButton when I mapped over the data. However, this didn't work and it didn't feel clean. Anyway, I hope this makes sense. Any help is greatly appreciated.
Thanks
var recipes = [{
recipe_title: "Cheesecake",
ingredients: "cream cheese, graham crackers, butter, eggs"
}, {
recipe_title: "Lasagna",
ingredients: " ricotta cheese, ground beef, pasta shells, parsely"
}, {
recipe_title: "Spaghetti",
ingredients: "noodles, pasta sauce, ground beef"
}]
var RecipeTitleButton = React.createClass({
getInitialState: function() {
return {
showIngredients: false
}
},
onClick: function() {
this.setState({
showIngredients: true
})
},
render: function() {
<Ingredients ingredients={this.props.ingredients}/>
return (
<div>
<button type="button" className="recipe_title_button" class="btn btn=primary btn-lg">{this.props.recipe_title}</button>
{this.state.showIngredients ? <Ingredients/>: null}
</div>
)
}
})
var Ingredients = React.createClass({
render: function() {
return (
<div id="ingredients" className="recipe_title_ingredients">
{this.props.ingredients}
</div>
)
}
})
var MainRecipeDisplay = React.createClass({
getInitialState: function() {
return {
recipeDataObject: recipes
}
},
render: function() {
var Pages = this.state.recipeDataObject.map(function(recipeContents) {
<RecipeTitleButton recipe_title={recipeContents.recipe_title} ingredients={recipeContents.ingredients}/>
})
return (
<div>
{Pages}
</div>
)
}
})
ReactDOM.render(<MainRecipeDisplay/>, document.getElementById('content'))
It appears that you're not actually passing the click handler to any of the elements. So you've defined the onClick method in RecipeTitleButton, but you're not passing this to anything. The solution is probably as simple as passing the <button> element a property onClick={/* The function that you want to fire on click */}.
First of all, change the name of the click handler to something like onClickHandler, for sanity. Click handlers are called with an event argument, which you probably don't need for your purposes but is important to know about (for example, if you need to prevent event propagation or have the click handler figure out which button was clicked).
Then the render function for RecipeTitleButton should look like:
render: function() {
<Ingredients ingredients={this.props.ingredients}/>
return (
<div>
<button type="button" className="recipe_title_button" onClick={this.onClickHandler.bind(this)} class="btn btn=primary btn-lg">{this.props.recipe_title}</button>
{this.state.showIngredients ? <Ingredients/>: null}
</div>
)
}
In case you're wondering, we bind the function to this so that the context remains the RecipeTitleButton component, instead of the window.
Oh, as an aside, it'd be easier to test this if you put it in a JSFiddle
Actually I figured it out. Was very simple. First, React doesn't recognize changing the setState value with a mere {setState({showingredients: true}). I had to use a function to make this explicit {setState(function(){return showIngredients:true)}. Second,in order to render the ingredients component within in my recipeTitle component, I only had to indicate props within {this.showIngredients ? :"null"/>. This way, I was allowed to define the ingredients prop within my recipeTitleButton. Code is Below:
var recipes = [{
title: "Cheesecake",
ingredients: "cream cheese, graham crackers, butter, eggs"
}, {
title: "Lasagna",
ingredients: " ricotta cheese, ground beef, pasta shells, parsely"
}, {
title: "Spaghetti",
ingredients: "noodles, pasta sauce, ground beef"
}]
var Layout = React.createClass({
getInitialState: function() {
return {
recipeDataObject: recipes,
showIngredients: false
}
},
render: function() {
var recipeContents = this.state.recipeDataObject.map(function(currentRecipe) {
return (
<RecipeTitleButton title={currentRecipe.title} ingredients={currentRecipe.ingredients}/>
)
})
return (
<div>
{recipeContents}
</div>
)
}
})
var RecipeTitleButton = React.createClass({
getInitialState: function() {
return {
showIngredients: false
}
},
handleRecipeButtonClick: function() {
**this.setState(function() {
return {
showIngredients: true
}**
});
},
render: function() {
return (
<div>
<button type="button" onClick={this.handleRecipeButtonClick.bind(this)} className="recipe_button">{this.props.title}</button>
**{this.state.showIngredients && <Ingredients ingredients={this.props.ingredients}/>}**
</div>
)
}
})
var Ingredients = React.createClass({
render: function(){
return(
<div className= "ingredients_list">
{this.props.ingredients}
</div>
)
}
})
this is what I have in my model
// The contents of individual model .js files will be concatenated into dist/models.js
(function() {
// Protects views where angular is not loaded from errors
if ( typeof angular == 'undefined' ) {
return;
};
var module = angular.module('myModel', ['restangular']);
module.factory('myRestangular', function(Restangular) {
return Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('http://localhost/data');
RestangularConfigurer.setRequestSuffix('.json');
RestangularConfigurer.setRestangularFields({
id: "my_id"
});
});
});
})();
this is fine. but now I have another json that I need to grab data from. How could I change this model to look for that other json as well. I am very very new to angular and still learning how model data binding works!
*This is what I have tired *
my model
var module = angular.module('FloorModel', ['restangular']);
module.factory('FloorRestangular', function(Restangular) {
return Restangular.withConfig(function(RestangularConfigurer) {
RestangularConfigurer.setBaseUrl('http://localhost/data/floor');
RestangularConfigurer.setRequestSuffix('.json');
RestangularConfigurer.setRestangularFields({
id: "floor_id"
});
});
});
**my controller**
myApp.controller('FloorCtrl', function ($scope, $filter, FloorRestangular) {
// Fetch all objects from the local JSON (see app/models/mdpocket.js)
FloorRestangular.all('floor').getList().then( function(floors) {
// Then select the one based on the view's id query parameter
$scope.floor = $filter('filter')(floors, {floor_id: steroids.view.params['id']})[0];
});
// -- Native navigation
steroids.view.navigationBar.show("Floor: " + steroids.view.params.id );
});
*my view *
<div ng-app="myApp">
<div ng-controller="FloorCtrl">
<div class="topcoat-list__container">
<ul class="topcoat-list">
<li class="topcoat-list__item" hm-tap="open(floor.floor_id)" ng-repeat="floor in floors">
Floor Name: {{ floor.name }}
</li>
</ul>
</div>
</div>
</div>