I have an VueJS component that list the contents of the array to the page. runner.availableResources.cores and runner.availableResources.memory come from bus creates usingbusmq npm package. They take awhile to become available, about 15s depending on IO buffer and thus not immediately available when the page renders.
The error is: [Vue warn]: Error in render: "TypeError: Cannot read property 'cores' of undefined"
How can I make Vue keep checking for values to become available?
<template>
<b-col>
<b-table striped hover :items="formatRunners"></b-table>
</b-col>
</template>
<script>
const fileSize = require("filesize");
export default {
name: "RunnersList",
props: {
runners: Array
},
computed: {
formatRunners() {
const runnerItems = [];
for (const runner of this.runners) {
const newItem = {};
newItem.id = runner.id;
newItem.isPublic = runner.marathon.isPublic;
newItem.AvailableCpu = runner.availableResources.cores;
newItem.AvailableMemory = fileSize(runner.availableResources.memory);
runnerItems.push(newItem);
}
return runnerItems;
}
},
data() {
return {};
}
};
</script>
This is not a really aesthetic solution, but here is a quick workaround:
in your template, add this v-if condition:
<b-table v-if="haveResourcesLoaded" striped hover :items="formatRunners"></b-table>
then in your computed properties, add the corresponding one:
haveResourcesLoaded() {
if (this.runners.length > 0) {
return this.runners[0].availableResources !== undefined
}
return false
}
If you need to do it in a better and more controlled way, you should take a look at the documentation, the bus.isOnline() method might be what you're looking for.
It wasn't so much issue with the listing, as it was update function only getting called once in a minute. The final code is for listing runner is bellow.
<template>
<b-col>
<b-table v-if="runnersTable.length > 0" striped hover :items="runnersTable"></b-table>
</b-col>
</template>
<script>
const fileSize = require("filesize");
export default {
name: "RunnersList",
props: {
runners: Array
},
data() {
return {
haveResourcesLoaded: false
};
},
mounted() {},
computed: {
runnersTable() {
const runnerItems = [];
for (const runner of this.runners) {
const newItem = {
id: runner.id,
isPublic: runner.marathon.isPublic,
AvailableCpu: runner.availableResources.cores,
AvailableMemory: fileSize(runner.availableResources.memory)
};
runnerItems.push(newItem);
}
return runnerItems;
}
}
};
</script>
Related
I need to push object in ngFor but I got error Property 'push' does not exist on type 'Observable<MyDataType[]>'.
Stackblitz demo code
HTML
<div *ngFor='let item of myObservableArray | async'>
{{item.value}}
</div>
Component
getData() {
if (!this.myObservableArray) {
this.myObservableArray = this.myService.getData();
this.myObservableArray.push({"id":3, "value":"value_4"});
}
}
Service
mydata: MyDataType[] = [
{"id":1, "value":"value_1"},
{"id":2, "value":"value_2"},
{"id":3, "value":"value_3"}
];
getData():Observable<MyDataType[]>
{
let data = new Observable<MyDataType[]>(observer => {
setTimeout(() => {
observer.next(this.mydata);
}, 4000);
});
return data;
}
}
Demo I added one more method to service to add or update.
addOrUpdate(item:MyDataType){
var elem=this.mydata.find(element=> element.id==item.id);
if(elem){
elem.value=item.value;
}
else{
this.mydata.push(item);
}
}
and call it inside component
this.myService.addOrUpdate({"id":3, "value":"value_4"});
My HTML code is like below
<ul>
<li v-for="item in values">{{ item }}</li>
</ul>
My vue.js code is like below
export default {
data() {
return {
values: {}
}
},
props: ['applicants', 'pageinfo'],
watch: {
applicants (val) {
EventBus.$on('click', function (skillName) {
this.values = val[0];
console.log(this.values); // I am getting output here.
});
},
},
}
I am trying to iterate over the values of val[0]. But I am not getting any output.
Either use arrow function like
applicants (val) {
EventBus.$on('click', (skillName) => {
this.values = val[0];
console.log(this.values); // I am getting output here.
});
},
or save the reference of this before the event handler
applicants (val) {
var _self = this;
EventBus.$on('click', function (skillName) {
_self.values = val[0];
console.log(this.values); // I am getting output here.
});
},
I believe I have two basic problems, which are probably connected. I'm trying to place an event handler with a callback function on a nested component. It didn't seem to be doing anything, so I replaced the callback function with an alert of JSON.stringify(this.props) to see if that would shed any light. It illuminated two problems: 1) my callback function was not in the props. 2) the alert popped up 2 times on page load, but did not pop up on click, like it was supposed to. I'm working through this React tutorial. Here are the relevant components:
var App = React.createClass({
mixins: [Catalyst.LinkedStateMixin],
getInitialState: function(){
return {
fishes: {},
order: {}
}
},
componentDidMount: function(){
base.syncState(this.props.params.storeId + '/fishes', {
context: this,
state: 'fishes'
});
var localStorageRef = localStorage.getItem('order-' + this.props.params.storeId);
if(localStorageRef){
this.setState({
order: JSON.parse(localStorageRef)
});
}
},
componentWillUpdate: function(nextProps, nextState){
localStorage.setItem('order-' + this.props.params.storeId, JSON.stringify(nextState.order));
},
loadSamples: function(){
this.setState({
fishes: require('./sample-fishes.js')
});
},
addFish: function(fish){
var timestamp = (new Date()).getTime();
this.state.fishes['fish-' + timestamp] = fish;
this.setState({ fishes: this.state.fishes });
},
removeFish: function(key){
if(confirm("Are you sure you want to remove this fish?")){
this.state.fishes[key] = null;
this.setState({ fishes: this.state.fishes });
}
},
addToOrder: function(key){
this.state.order[key] = this.state.order[key] + 1 || 1;
this.setState({ order: this.state.order });
},
// <<<<<<<< the function I'm having trouble with >>>>>>>>
removeFromOrder: function(key){
alert('hi');
delete this.state.order[key];
this.setState({ order: this.state.order });
},
renderFish(key){
return <Fish key={key} index={key} details={this.state.fishes[key]} addToOrder={this.addToOrder}/>
},
render: function(){
return (
<div className="catch-of-the-day">
<div className="menu">
<Header tagline="Fresh Seafood Market"/>
<ul className="list-of-fish">
{/*{ Object.keys(this.state.fishes).map(this.renderFish) }*/}
{ Object.keys(this.state.fishes).length > 0 ? Object.keys(this.state.fishes).map(this.renderFish) : <li>No Fishes!</li> }
</ul>
</div>
// <<<<<<<< I pass the function through to the Order component >>>>>>>>
<Order fishes={this.state.fishes} order={this.state.order} removeFromOrder={this.removeFromOrder}/>
<Inventory fishes={this.state.fishes} addFish={this.addFish} removeFish={this.removeFish} loadSamples={this.loadSamples} linkState={this.linkState}/>
</div>
)
}
});
var Order = React.createClass({
renderOrder: function(key){
var fish = this.props.fishes[key];
var count = this.props.order[key];
// <<<<<<<< the onClick I'm having trouble with >>>>>>>>
var removeButton = <button onCLick={this.props.removeFromOrder.bind(null, key)}>×</button>
// var removeButton = <button onCLick={alert(JSON.stringify(this.props))}>×</button>
if(!fish) {
return <li key={key}>Sorry, that fish is no longer available! {removeButton}</li>
// return <li key={key}>Sorry, that fish is no longer available!</li>
}
return (
<li key={key}>
{count}lbs
{" " + fish.name}
<span className="price">{helpers.formatPrice(count * fish.price)} {removeButton}</span>
{/*<span className="price">{helpers.formatPrice(count * fish.price)}</span>*/}
</li>
)
},
render: function(){
var orderIds = Object.keys(this.props.order);
var total = orderIds.reduce((prevTotal, key)=>{
var fish = this.props.fishes[key];
var count = this.props.order[key];
var isAvailable = fish && fish.status === 'available';
if(isAvailable) {
return prevTotal + (count * parseInt(fish.price) || 0);
}
return prevTotal;
}, 0);
return (
<div className="order-wrap">
<h2 className="order-title">Your Order</h2>
<ul className="order">
{ orderIds.length > 0 ? orderIds.map(this.renderOrder) : ""}
<li className="total">
<strong>Total:</strong>
{helpers.formatPrice(total)}
</li>
</ul>
</div>
)
}
});
The props for Order should include: the available fishes with all of their details, the current order with a fish id and quantity, and the removeFromOrder callback. When I explore the component in React dev tools, it has all of these things.
When I replace the removeFromOrder callback with an alert of the props, what happens is:
- on click, nothing
- on page refresh, two alerts pop up: the props in the first include the current order and an empty fishes array, the props in the second include the current order and the populated fishes array. Neither show the removeFromOrder callback function, which appears to be undefined from the perspective of the event listener.
On a potentially related note, when I explore the component in React dev tools and hover over a list item in the Order, I get the following error: TypeError: node.getBoundingClientRect is not a function. I'm not sure if this is part of my problem; if it's not, I'm not too concerned about it, since it only seems to pop up when I hover over the element in dev tools.
Thank you for reading this long thing, and any help would be much appreciated!
As #azium pointed out, the problem was a simple typo: onCLick={alert()} should instead be onClick={() => alert()}. Facepalm.
I'm new to ReactJS and was trying my hands on a simple project. Basically, the snippet create a list of friends from an array and displays the total number of friends.
For some reason, I realized the incrementFriendsCount function throws a "Maximum call stack exceeded error" when I add a new friend
The code snippet below is also available on JSFiddle.
var HelloUser = React.createClass({
getInitialState: function() {
return {
name: "Toyosi",
friends: ["Susanna", "Jibola", "Worreva"],
friendsCount: 0
}
},
addFriends: function(friend) {
this.setState({
friends: this.state.friends.concat([friend])
});
},
componentWillMount: function() {
this.setState({
friendsCount: this.state.friends.length
});
},
incrementFriendsCount: function() {
this.setState({
friendsCount: this.state.friends.length
});
},
render: function() {
return ( < div >
Villain: {
this.state.name
}, No of friends: {
this.state.friendsCount
} < br / >
< AddingTheFriend addNew = {
this.addFriends
}
incCount = {
this.incrementFriendsCount
}
/>
<ListFriends enemies={this.state.friends} / >
< /div>
);
}
});
var ListFriends = React.createClass({
propTypes: {
enemies: React.PropTypes.array.isRequired
},
render: function() {
var allFriends = this.props.enemies.map(function(friend){
return <li>{friend}</li > ;
});
return ( < div > Her evil friends:
< ul > {
allFriends
} < /ul>
</div >
)
}
});
var AddingTheFriend = React.createClass({
getInitialState: function() {
return {
newFriend: ''
}
},
propTypes: {
addNew: React.PropTypes.func.isRequired
},
updateNewFriend: function(change) {
this.setState({
newFriend: change.target.value
});
},
addTheFriend: function() {
this.props.addNew(this.state.newFriend);
this.setState({
newFriend: ''
})
},
componentWillReceiveProps: function() {
this.props.incCount();
},
render: function() {
return ( < div >
< input type = "text"
value = {
this.state.newFriend
}
onChange = {
this.updateNewFriend
}
/>
<button type="button" onClick={this.addTheFriend}>Add Friend</button >
< /div>
)
}
});
React.render(<HelloUser / > , document.getElementById('app'));
<script src="http://fb.me/react-js-fiddle-integration.js"></script>
<div id="app"></div>
I will appreciate if anyone could throw more light on why this error is thrown.
You are calling this.props.incCount in componentWillReceiveProps which sets the state of the parent component and the effect will be that AddingTheFriend is rendered again, and this.props.incCount is called again. Hence the stack overflow.
Another advice would be that generally you want to be careful and use setState as little as possible, in as few components as possible. Simply increment the friends count at the same time you concat the new friend to parent component's state.
Here's the codepen --much better than JSFiddle in my opinion.
As a future reference for people with the same error output when using react:
This error will also occur of you run your app with both
webpack-dev-server --hot
new webpack.HotModuleReplacementPlugin()
So: When you run your server with --hot and also have the hotModuleReplacementPlugin enabled in your webpack config then you will get into the situation that your components will recursively update, thus generating exactly the error message the OP mentioned.
Simple solution: Omit one of the two things, e.g. omit the "--hot" since you already use the plugin for that.
In my case it was an infinite loop, or if you like an obvious logical error.
I started to learn React and I hit first wall.
I have a list component which should display a list of rows + button for adding a new row.
All is in those 2 gists:
https://gist.github.com/matiit/7b361dee3f878502e10a
https://gist.github.com/matiit/8bac28c4d5c6ce3993c7
The addRow method is executed on click, because I can see the console.log, but no InputRows are added.
Can't really see why.
This is a little updated (dirty) code which doesn't work either.
Now it's only one file:
var InputList = React.createClass({
getInitialState: function () {
return {
rowCount: 1
}
},
getClassNames: function () {
if (this.props.type === 'incomes') {
return 'col-md-4 ' + this.props.type;
} else if (this.props.type === 'expenses') {
return 'col-md-4 col-md-offset-1 ' + this.props.type;
}
},
addRow: function () {
this.state.rowCount = this.state.rowCount + 1;
this.render();
},
render: function () {
var inputs = [];
for (var i=0;i<this.state.rowCount; i++) {
inputs.push(i);
}
console.log(inputs);
return (
<div className={ this.getClassNames() }>
{inputs.map(function (result) {
return <InputRow key={result} />;
})}
<div className="row">
<button onClick={this.addRow} className="btn btn-success">Add more</button>
</div>
</div>
);
}
});
this.render() doesn't do anything. If you look at the function, it simply does some calculations and returns some data (the virtual dom nodes).
You should be using setState instead of directly modifying it. This is cleaner, and allows react to know something's changed.
addRow: function () {
this.setState({rowCount: this.state.rowCount + 1});
},
Don't store list of components in state.
Instead store the income row count and expense row count in state.
Use click handler to increment these counts.
Use render method to generate required rows based on count.