Hello Im creating a simple React Component with just a label that change its content when a SignalR method is fired. My react component is like this one:
var PersonalityStatusApp = React.createClass({
getInitialState: function () {
return { data: dataInit };
},
componentWillMount(){
var self = this;
this.setState({ data:this.props.status});
Votinghub.on("UpdateStatusLabel", function (data) {
var obj = $.parseJSON(data);
self.setState({ data: obj });
});
},
render: function () {
return (
<div className="PersonalityStatusApp">
<label>{this.props.status}</label>
</div>
);
}
});
When te component receives a UpdateStatusLabel signalR message it change the State of the component with the value that gets from the signalR message.
The method UpdateStatusLabel gets the correct value.
This fires the render method, but when I check the properties in the render method I see thnat the values are still the ones from the initial state.
Can somebody help me?
Reason is, you are updating the state variable and printing the props value. Initially state variable data will have the value of this.props and after you get the signalR you are updating the state by data: obj, so print the value of this.stat.data.status it will print the updated value.
use this:
return (
<div className="PersonalityStatusApp">
<label>{this.state.data.status}</label>
</div>
);
Note: Initially you need to set the value of data: this.props
Full part:
var PersonalityStatusApp = React.createClass({
getInitialState: function () {
return { data: dataInit };
},
componentWillMount(){
var self = this;
this.setState({ data: this.props}); //changed this
Votinghub.on("UpdateStatusLabel", function (data) {
var obj = $.parseJSON(data);
self.setState({ data: obj });
});
},
render: function () {
return (
<div className="PersonalityStatusApp">
<label>{this.state.data.status}</label>
</div>
);
}
});
I'm trying to get this list in the view, but this doesn't display any items
render: function() {
var list = this.state.list;
console.log('Re-rendered');
return(
<ul>
{list.map(function(object, i){
<li key='{i}'>{object}</li>
})}
</ul>
)
}
list is first set to null, but then I reload it with AJAX. This on the other hand works
<ul>
{list.map(setting => (
<li>{setting}</li>
))}
</ul>
This is my whole component as it stands:
var Setting = React.createClass({
getInitialState: function(){
return {
'list': []
}
},
getData: function(){
var that = this;
var myHeaders = new Headers();
var myInit = { method: 'GET',
headers: myHeaders,
mode: 'cors',
cache: 'default' };
fetch('/list/',myInit)
.then(function(response){
var contentType = response.headers.get("content-type");
if(contentType && contentType.indexOf("application/json") !== -1) {
return response.json().then(function(json) {
that.setState({'list':json.settings});
});
} else {
console.log("Oops, we haven't got JSON!");
}
})
.catch(function(error) {
console.log('There has been a problem with your fetch operation: ' + error.message);
});;
},
componentWillMount: function(){
this.getData();
},
render: function() {
var list = this.state.list;
return(
<ul>
{list.map(function(object, i){
<li key={i}>{object}</li>
})}
</ul>
)
}
});
You are missing your return statement
{list.map(function(object, i){
return <li key={i}>{object}</li>
})}
this works
<ul>
{list.map(setting => (
<li>{setting}</li>
))}
</ul>
because anything within () is returned automatically when using an arrow function but the previous example was using {} which requires a return statement.
When should I use `return` in es6 Arrow Functions? this will give you more context around when and when not to use a return statement with arrow functions.
I am trying to create a Component whose content changes when the screen orientation(portrait/landscape) changes. This is what I am doing:
var Greeting = React.createClass({
getInitialState: function() {
return {orientation: true}
},
handleChange: function() {
if ('onorientationchange' in window) {
window.addEventListener("orientationchange", function() {
this.setState({
orientation: !this.state.orientation
})
console.log("onorientationchange");
}, false);
} else if ('onresize' in window) {
window.addEventListener("resize", function() {
this.setState({
orientation: !this.state.orientation
})
console.log("resize");
}, false);
}
},
render: function() {
var message = this.state.orientation ? "Hello" : "Good bye"
return <p>{message}</p>;
}
});
ReactDOM.render(
<Greeting/>, document.getElementById('container'));
How to make sure the state is mutated when the orientation change event is fired .
Your calling to this.setState is wrong. Need to change it to be like:
handleChange: function() {
var self = this; // Store `this` component outside the callback
if ('onorientationchange' in window) {
window.addEventListener("orientationchange", function() {
// `this` is now pointing to `window`, not the component. So use `self`.
self.setState({
orientation: !self.state.orientation
})
console.log("onorientationchange");
}, false);
}
Apologies, I probably have all of my terminology wrong here, but I am chunking up a React app into modules and am trying to combine two modules which do the same thing and come into an issue.
Specifically, I am trying to sum up totals of both income and expenditure, and have been calling two different react classes via:
<ExpenditureTotal type={this.state.cashbook.expenditure} addTotal={this.addTotal} /> and another <IncomeTotal... which does the same).
The Class is as follows (expenditure shown, but income is the same:
/* Expenditure Running Total */
var ExpenditureTotal = React.createClass({
render: function() {
var expIds = Object.keys(this.props.expenditure);
var total = expIds.reduce((prevTotal, key) => {
var expenditure = this.props.expenditure[key];
return prevTotal + (parseFloat(expenditure.amount));
}, 0);
this.props.addTotal('expenditure', total);
return(
<h2 className="total">Total: {h.formatPrice(total)}</h2>
);
}
});
I wanted to combine the two by making it more generic, so I made the following:
/* Cashflow Running Total */
var TotalCashflow = React.createClass({
render: function() {
var expIds = Object.keys(this.props.type);
var total = expIds.reduce((prevTotal, key) => {
var type = this.props.type[key];
return prevTotal + (parseFloat(type.amount));
}, 0);
this.props.addTotal('type', total);
return(
<h2 className="total">Total: {h.formatPrice(total)}</h2>
);
}
});
The only reason it doesn't work properly is when I add the summed total to the relevant state object which for this is income or expenditure in totals: (totals: { income: val, expenditure: val}).
Where I was previously explicitly specifying 'expenditure' or 'income' in the module, (seen above in ExpenditureTotal as this.props.addTotal('expenditure', total);, I am not sure in React how to pass the total value to the relevant place - it needs to read either 'expenditure' or 'income' to populate the correct state totals key.
Apologies if this is a bit garbled, struggling to explain it clearly.
Thank you :)
Update: App component in full:
import React from 'react';
// Firebase
import Rebase from 're-base';
var base = Rebase.createClass("FBURL")
import h from '../helpers';
import Expenditure from './Expenditure';
import Income from './Income';
import TotalCashflow from './TotalCashflow';
import AddForm from './AddForm';
import Available from './Available';
var App = React.createClass({
// Part of React lifecycle
getInitialState: function() {
return {
cashbook: {
expenditure: {},
income: {}
},
totals: {},
available: {}
}
},
componentDidMount: function() {
// Two way data binding
base.syncState('cashbook', {
context: this,
state: 'cashbook'
});
},
addExpenditure: function(expenditure) {
var timestamp = (new Date()).getTime();
// update state object
this.state.cashbook.expenditure['expenditure-' + timestamp] = expenditure;
// set state
this.setState({
cashbook: { expenditure: this.state.cashbook.expenditure }
});
},
addIncome: function(income) {
var timestamp = (new Date()).getTime();
// update state object
this.state.cashbook.income['income-' + timestamp] = income;
// set state
this.setState({
cashbook: { income: this.state.cashbook.income }
});
},
removeExpenditure: function(key) {
this.state.cashbook.expenditure[key] = null;
this.setState({
cashbook: { expenditure: this.state.cashbook.expenditure }
});
},
renderExpenditure: function(key) {
var details = this.state.cashbook.expenditure[key];
return(
<tr className="item" key={key}>
<td><strong>{details.name}</strong></td>
<td><strong>{h.formatPrice(details.amount)}</strong></td>
<td>{details.category}</td>
<td>{details.type}</td>
<td>{details.date}</td>
<td><button className="remove-item" onClick={this.removeExpenditure.bind(null, key)}>Remove</button></td>
</tr>
);
},
removeIncome: function(key) {
this.state.cashbook.income[key] = null;
this.setState({
cashbook: { income: this.state.cashbook.income }
});
},
renderIncome: function(key) {
var details = this.state.cashbook.income[key];
return(
<tr className="item" key={key}>
<td><strong>{details.name}</strong></td>
<td><strong>{h.formatPrice(details.amount)}</strong></td>
<td>{details.category}</td>
<td>{details.type}</td>
<td>{details.date}</td>
<td><button className="remove-item" onClick={this.removeIncome.bind(null, key)}>Remove</button></td>
</tr>
);
},
listInventory: function() {
return
},
addTotal: function(type, total) {
this.state.totals[type] = total;
},
render: function() {
return(
<div className="cashbook">
<Expenditure
cashbook={this.state.cashbook.expenditure}
renderExpenditure={this.renderExpenditure} />
<TotalCashflow
type={this.state.cashbook.expenditure}
addTotal={this.addTotal}
identifier='expenditure'
/>
<AddForm addCashflow={this.addExpenditure} />
<Income
cashbook={this.state.cashbook.income}
renderIncome={this.renderIncome} />
<TotalCashflow
type={this.state.cashbook.income}
addTotal={this.addTotal}
identifier='income'
/>
<AddForm addCashflow={this.addIncome} />
<Available totals={this.state.totals} />
</div>
);
}
});
export default App;
If I got you correctly, you want to have a component, that renders the totals of 'expenditure' and/or 'income'. Notice, you should not modify the state within the render function, as these will trigger over and over again, with each state change it is invoking itself.
Instead, try to compute the necessary data from within the parent components componentDidMount/componentDidUpdate, so that the TotalCashflow can be served the data, without it needing to do any more logic on it.
This might be the parent component:
/* Cashflow-Wrapper */
var TotalCashflowWrapper = React.createClass({
componentDidMount: function(){
this.setState({
income: this.addTotal(/*x1*/)
expenditure: this.addTotal(/*x1*/)
});
/*x1: hand over the relevant 'type' data that you use in the child components in your example*/
}
addTotal: function(type) {
var expIds = Object.keys(type);
return expIds.reduce((prevTotal, key) => {
var x = type[key];
return prevTotal + (parseFloat(x.amount));
}, 0);
}
render: function() {
return(
<div>
<TotalCashflow total={this.state.expenditure}/>
<TotalCashflow total={this.state.income}/>
</div>
);
}
});
This would be your total component:
/* Cashflow Running Total */
var TotalCashflow = React.createClass({
render: function() {
return(
<h2 className="total">Total: {h.formatPrice(this.props.total)}</h2>
);
}
});
Edit
There are a few issues with your App component, but first of all, here is how you could redesign/simplify it. You should also move the markup from renderExpenditure and renderIncome to your Income/Expenditure components. I've been using ES6, as I've noticed you're already using arrow functions.
var App = React.createClass({
getInitialState: function () {
return {
cashbook: {
expenditure: {},
income: {}
},
totals: {},
available: {}
}
},
componentDidMount: function () {
// Two way data binding
base.syncState('cashbook', {
context: this,
state: 'cashbook'
}).then(()=> {
// after syncing cashbook, calculate totals
this.setState({
totals: {
income: this.getTotal(this.state.cashbook.income),
expenditure: this.getTotal(this.state.cashbook.expenditure),
}
});
});
},
// Get total of obj income/expenditure
getTotal: function (obj) {
var expIds = Object.keys(obj);
return expIds.reduce((prevTotal, key) => {
var type = obj[key];
return prevTotal + (parseFloat(type.amount));
}, 0);
},
addCashflow: function (identifier, amount) {
var timestamp = (new Date()).getTime();
// clone cashbook and clone cashbook[identifier] and set cashbook[identifier][identifier + '-' + timestamp] to amount
var cashbook = {
...this.state.cashbook,
[identifier]: {
...this.state.cashbook[identifier],
[identifier + '-' + timestamp]: amount
}
};
// set state
this.setState({
cashbook: cashbook,
// Update totals
totals: {
...this.state.totals,
[identifier]: this.getTotal(cashbook[identifier])
}
});
},
removeCashflow: function (identifier, key) {
// clone cashbook and clone cashbook[identifier]
var cashbook = {...this.state.cashbook, [identifier]: {...this.state.cashbook[identifier]}};
delete cashbook[identifier][key];
this.setState({
cashbook: cashbook,
totals: {
...this.state.totals,
// Update totals
[identifier]: this.getTotal(cashbook[identifier])
}
});
},
render: function () {
return (
<div className="cashbook">
<Expenditure cashbook={this.state.cashbook.expenditure} removeCashflow={(key)=>this.removeCashflow('expenditure', key)}/>
<TotalCashflow total={this.state.cashbook.expenditure} identifier='expenditure'/>
{/*or drop TotalCashflow and do <h2 className="total">Total: {h.formatPrice(this.state.totals.expandature)}</h2>*/}
<AddForm addCashflow={(amount)=>this.addCashflow('expenditure', amount)}/>
<Income cashbook={this.state.cashbook.income} removeCashflow={(key)=>this.removeCashflow('income', key)}/>
<TotalCashflow type={this.state.cashbook.income} identifier='income' />
<AddForm addCashflow={(amount)=>this.addCashflow('income', amount)}/>
<Available totals={this.state.totals}/>
</div>
);
}
});
export default App;
Issues with your current App component
Issue: You are not removing the key, you're just setting it to null; this could lead to exceptions when iterating Object.keys() and you're expecting the values to be numbers, like when calculating the totals
removeIncome: function(key) {
// this.state.cashbook.income[key] = null;
delete this.state.cashbook.income[key]
this.setState({
cashbook: { income: this.state.cashbook.income }
});
},
Bad design: You're defining the markup of a child component within your parent component, although it seems unnecessary
renderExpenditure: function(key) {
var details = this.state.cashbook.expenditure[key];
return(
<tr className="item" key={key}>
<td><strong>{details.name}</strong></td>
<td><strong>{h.formatPrice(details.amount)}</strong></td>
<td>{details.category}</td>
<td>{details.type}</td>
<td>{details.date}</td>
<td><button className="remove-item" onClick={this.removeExpenditure.bind(null, key)}>Remove</button></td>
</tr>
);
},
This could easily be moved to your Expenditure/Income component.
Issues: Mutating state, Overwriting cashbook in state and loosing expenditure/income
addExpenditure: function(expenditure) {
var timestamp = (new Date()).getTime();
// You are mutating the state here, better clone the object and change the clone, then assign the cloned
this.state.cashbook.expenditure['expenditure-' + timestamp] = expenditure;
// You are overwriting cashbook with an object, that only contains expenditure, thus loosing other properties like income
this.setState({
cashbook: { expenditure: this.state.cashbook.expenditure }
});
}
// Fixed
addExpenditure: function(expenditure) {
var timestamp = (new Date()).getTime();
// clone object
var cashbook = Object.assign({}, this.state.cashbook);
cashbook.expenditure = Object.assign({}, cashbook.expenditure);
// define latest expenditure
cashbook.expenditure['expenditure-' + timestamp] = expenditure;
// set state
this.setState({
cashbook: cashbook
});
}
// ES6
addExpenditure: function(expenditure) {
var timestamp = (new Date()).getTime();
this.setState({
cashbook: {
...this.state.cashbook,
expenditure: {
...this.state.cashbook.expenditure,
['expenditure-' + timestamp]: expenditure
}
}
});
}
You would be better off, if you'd flatten your state and cashbook object
so instead of
this.state = {
cashbook: {
expenditure: {},
income: {}
},
totals: {},
available: {}
}
having
this.state = {
expenditure: {},
income: {}
totals: {},
available: {}
}
so you could just
addExpenditure: function(expenditure) {
var timestamp = (new Date()).getTime();
var exp = Object.assign({}, this.state.cashbook.expenditure);
exp['expenditure-' + timestamp] = expenditure;
this.setState({
expenditure: exp
});
}
or es6
addExpenditure: function(expenditure) {
var timestamp = (new Date()).getTime();
this.setState({
expenditure: {
...this.state.cashbook.expenditure,
['expenditure-' + timestamp]: expenditure
}
});
}
of course you'd need to update the rebase binding and your models, dunno if this is something you want
componentDidMount: function() {
base.syncState('expenditure', {
context: this,
state: 'expenditure'
});
base.syncState('income', {
context: this,
state: 'income'
});
}
I have developed the Component it's look like,
var ManageViewPage = React.createClass({
// Get initial state from stores
getInitialState: function() {
return setViewData();
},
componentDidMount: function() {
SystemMetaItemStore.CallSystemMetaItem("Views");
ItemStore.addChangeListener(this._onChange);
SystemMetaItemStore.addChangeListener(this._onChange);
alert("Did mount");
},
// Remove change listers from stores
componentWillUnmount: function() {
ItemStore.removeChangeListener(this._onChange);
SystemMetaItemStore.removeChangeListener(this._onChange);
alert("Did Unmount");
},
preventDefault: function (event) {
event.preventDefault();
},
DeleteViewclick:function(event)
{
},
render: function() {
return (
<div className="row">
</div>
)
},
_onChange: function() {
this.setState(setViewData());
}
});
module.exports =ManageViewPage;
When I will call this page first time using routing then it will call the alert "did mount" but when I press F5 to refresh the browser it will not called the alert "did mount" so can anyone guide me what I did wrong here ?