I'm trying to create a small react component, however, I am unable to set the state. Below is my code. In the _onChange function, I am trying to set an array of length 10 to State and console.log the same. I am getting an empty array in the console.
var Home = React.createClass({
getInitialState: function () {
return ({
reviewData: []
});
},
componentWillMount: function() {
ReviewStore.addChangeListener(this._onChange);
ReviewAction.getRatings();
console.log(this.state.reviewData);
},
_onChange: function() {
var res = ReviewStore.getRating();
console.log(res); //Here I am getting array of length 10
this.setState({reviewData: ReviewStore.getRating()});
console.log(this.state.reviewData); //Here I am getting array of length 0
},
componentWillUnmount: function () {
ReviewStore.removeChangeListener(this._onChange);
},
ratingChanged : function(newRating) {
console.log(newRating)
},
render: function() {
return(
<div>
<h2>Rating of Arlo Smart Home 1 HD Camera</h2>
<hr/>
<h4>Average Rating: </h4><ReactStars half={false} onChange={this.ratingChanged} size={24}/>
</div>
)
}
});
setState is asynchronous. The value will not be set immediately. You can pass a callback to setState which will be called when new state is set.
From react documentation https://facebook.github.io/react/docs/component-api.html
setState() does not immediately mutate this.state but creates a pending state transition. Accessing this.state after calling this method can potentially return the existing value.
You can change your code
this.setState({reviewData: ReviewStore.getRating()}, function () {
console.log(this.state.reviewData)
});
Related
When the click event takes place, the state is not set to the value given inside the CompOne. It still show the initial state and console logs the old state which is "hello".
var CompOne = React.createClass({
getInitialState: function() {
return {
edit: "hello"
}
},
editme: function () {
this.setState({
edit: "there"
})
console.log(this.state.edit)
},
render: function(){
return (
<div>
{this.props.name}
<button onClick={this.editme}>Edit</button>
</div>
)
}
})
var Pri = React.createClass({
render: function () {
return (
<div>
< CompOne name = "Anne"/>
< CompOne name = "Bob"/>
</div>
);
}
})
ReactDOM.render( <Pri /> , document.getElementById("root"));
Function setState is not synchronous. Here is a note about this from React documentation;
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls
may be batched for performance gains.
In human terms this means that if you call setState and try to read the state immediately, the state could be changed or it could be the same.
The solution that you can use is to pass a callback to setState method as a second parameter:
editme: function () {
this.setState({
edit: "there"
}, function(){
// this function would be invoked only when the state is changed
console.log(this.state.edit);
});
}
The purpose of the second parameter is described in the same documentation article:
The second parameter is an optional callback function that will be
executed once setState is completed and the component is
re-rendered.
You need to use the callback function in setState because setState takes time to mutate and you console.log gets executed before the state is mutated as statements are executed asynchronously.
editme: function () {
this.setState({
edit: "there"
}, function(){
console.log(this.state.edit)
})
},
var CompOne = React.createClass({
getInitialState: function() {
return {
edit: "hello"
}
},
editme: function () {
this.setState({
edit: "there"
}, function(){
console.log(this.state.edit)
})
},
render: function(){
return (
<div>
{this.props.name}
<button onClick={this.editme}>Edit</button>
</div>
)
}
})
var Pri = React.createClass({
render: function () {
return (
<div>
< CompOne name = "Anne"/>
< CompOne name = "Bob"/>
</div>
);
}
})
ReactDOM.render( <Pri /> , document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.8/react-dom.min.js"></script>
<div id="root"></div>
In the follow code, I thought the text should update to the new one after 3 seconds:
https://jsfiddle.net/smrfcr9x/1/
var Component = React.createClass({
render: function() {
return React.DOM.span(null, "hello " + this.props.text);
}
});
var aComponent = React.render(
React.createElement(Component, {
text: "abcd"
}),
document.getElementById("app")
);
setTimeout(function() {
console.log("now setting state");
aComponent.setState({
text: "lmno"
});
}, 3000);
How is it actually done?
You should use setProps to replace setState.
Please notice the warning in console from react.js:
Warning: setProps(...) and replaceProps(...) are deprecated. Instead, call render again at the top level.
You may follow that warning.
EDIT:
Sorry for haven't said it clearly just now.
Using setProps to replace setState can do correctly what you want. But the method setProps has been deprecated and will show the warning above. So I think you should follow the waring, once you want to change the props, rerender the component.
You read the text from props not state in the component, so it won't work if you set the state.
EDIT2:
var Component = React.createClass({
render: function() {
return React.DOM.span(null, "hello " + this.props.text);
}
});
var renderComponent = function(text){
React.render(
React.createElement(Component, {
text: text
}),
document.getElementById("app")
);
}
renderComponent('abcd')
setTimeout(function() {
console.log("now setting state");
renderComponent('lmno')
}, 3000);
I'm sorry for not knowing how to share code in jsfiddle. This code work fine in it.
Ok, I think I get it. The code to needs to set a state, and in render, use this.state instead of this.props:
https://jsfiddle.net/smrfcr9x/7/
var Component = React.createClass({
getInitialState: function() {
return {
text: this.props.text,
};
},
render: function() {
return React.DOM.span(null, "hello " + this.state.text);
}
});
My real application, is to use react to create a firefox addon, to maintain an object/state in a DOM-less scope, and then render an element to the currently active browser window. Multiple browser windows are present.
So as a small experiment, to see if react can do this, I was messing around with the Timer example on the reactjs main site.
var Timer = React.createClass({
getInitialState: function() {
return {secondsElapsed: 0};
},
tick: function() {
this.setState({secondsElapsed: this.state.secondsElapsed + 1});
},
componentDidMount: function() {
this.interval = setInterval(this.tick, 1000);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
render: function() {
return (
<div>Seconds Elapsed: {this.state.secondsElapsed}</div>
);
}
});
var rendered = ReactDOM.render(<Timer />, mountNode);
// i hope to do something here to render it to another node
Is it possible to render the same timer to multiple nodes? The state should be same, and if i modify state in that single object it should affect both. Is this possible?
Thanks!
I actually tried a different method. I used a singleton object with an array of callbacks as a property. This array of callbacks stores innerMethods of all elements mounted which correspond to a particular event.
Ideally, you should do have a key value pair
event => [All functions to fire]
But for this example, I went for a rather primitive scenario where there is only one event. So it's just an array of call callbacks for that event(doSomething).
http://codepen.io/bhargav175/pen/rxNRxO
MyStore
let MyStore = {
doSomething : function(v){
MyStore.callbacks.forEach((callback)=>{
callback(v);
});
},
callbacks :[],
addSomethingListener : function(callback){
MyStore.callbacks.push(callback);
}
}
Everytime a component is mounted I do this,
componentDidMount(props){
MyStore.addSomethingListener(this.doStuff.bind(this));
}
which adds callbacks from each element to the Store and each of them gets fired when the event occurs.
Full code.
let MyStore = {
doSomething : function(v){
MyStore.callbacks.forEach((callback)=>{
callback(v);
});
},
callbacks :[],
addSomethingListener : function(callback){
MyStore.callbacks.push(callback);
}
}
/*
* A simple React component
*/
class Application extends React.Component {
constructor(props){
super(props);
this.state = {'some_prop':0};
}
doStuff(val){
this.setState({
'some_prop':val
});
}
componentDidMount(props){
MyStore.addSomethingListener(this.doStuff.bind(this));
}
render() {
return <div>
<h1>Hello, ES6 and React 0.13!</h1>
<p>
{this.state.some_prop}
</p>
</div>;
}
}
/*
* Render the above component into the div#app
*/
React.render(<Application />, document.getElementById('app'));
React.render(<Application />, document.getElementById('yetanotherapp'));
MyStore.doSomething(5);
MyStore.doSomething(15);
MyStore.doSomething(25);
So, if you see, I fire the doSomething event and all the components are updated at different mount nodes. In your case, you could move your tick logic into doSomething. Just a start. Hope this has helped.
I'm new to react and flux and I am having a hard time trying to figure out how to load data from a server. I am able to load the same data from a local file with no issues.
So first up I have this controller view (controller-view.js) that passes down initial state to a view (view.js)
controller-view.js
var viewBill = React.createClass({
getInitialState: function(){
return {
bill: BillStore.getAllBill()
};
},
render: function(){
return (
<div>
<SubscriptionDetails subscription={this.state.bill.statement} />
</div>
);
}
});
module.exports = viewBill;
view.js
var subscriptionsList = React.createClass({
propTypes: {
subscription: React.PropTypes.array.isRequired
},
render: function(){
return (
<div >
<h1>Statement</h1>
From: {this.props.subscription.period.from} - To {this.props.subscription.period.to} <br />
Due: {this.props.subscription.due}<br />
Issued:{this.props.subscription.generated}
</div>
);
}
});
module.exports = subscriptionsList;
I have an actions file that loads the INITAL data for my app. So this is data that is not called by as user action, but called from getInitialState in the controller view
InitialActions.js
var InitialiseActions = {
initApp: function(){
Dispatcher.dispatch({
actionType: ActionTypes.INITIALISE,
initialData: {
bill: BillApi.getBillLocal() // I switch to getBillServer for date from server
}
});
}
};
module.exports = InitialiseActions;
And then my data API looks like this
api.js
var BillApi = {
getBillLocal: function() {
return billed;
},
getBillServer: function() {
return $.getJSON('https://theurl.com/stuff.json').then(function(data) {
return data;
});
}
};
module.exports = BillApi;
And this is the store
store.js
var _bill = [];
var BillStore = assign({}, EventEmitter.prototype, {
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
removeChangeListener: function(callback) {
this.removeListener(CHANGE_EVENT, callback);
},
emitChange: function() {
this.emit(CHANGE_EVENT);
},
getAllBill: function() {
return _bill;
}
});
Dispatcher.register(function(action){
switch(action.actionType){
case ActionTypes.INITIALISE:
_bill = action.initialData.bill;
BillStore.emitChange();
break;
default:
// do nothing
}
});
module.exports = BillStore;
So as I mentioned earlier, when I load data locally using BillApi.getBillLocal() in actions everything works fine. But when I change to BillApi.getBillServer() I get the followind errors in the console...
Warning: Failed propType: Required prop `subscription` was not specified in `subscriptionsList`. Check the render method of `viewBill`.
Uncaught TypeError: Cannot read property 'period' of undefined
I also added a console.log(data) to BillApi.getBillServer() and I can see that the data is returned from the server. But it is displayed AFTER I get the warnings in the console which I believe may be the issue. Can anyone offer some advice or help me to fix it? Sorry for such a long post.
UPDATE
I made some changes to the api.js file (check here for change and DOM errors plnkr.co/edit/HoXszori3HUAwUOHzPLG ) as it was suggested that the issue is due to how I handle the promise. But it still seems to be the same issue as you can see in the DOM errors.
This is an async issue. Using $.getJSON().then() is not enough. Since it returns a promise object, you have to handle the promise at invocation by doing something like api.getBill().then(function(data) { /*do stuff with data*/ });
I made a CodePen example with the following code:
function searchSpotify(query) {
return $.getJSON('http://ws.spotify.com/search/1/track.json?q=' + query)
.then(function(data) {
return data.tracks;
});
}
searchSpotify('donald trump')
.then(function(tracks) {
tracks.forEach(function(track) {
console.log(track.name);
});
});
It looks like from your code that the intended flow is something like:
some component fires initialize action,
initialize action calls API
which waits for results from server (I think here is where things break down: your component render starts before results from server are back),
then passes the result to the store,
which emits change and
triggers a re-render.
In a typical flux setup, I would advise to structure this somewhat different:
some component calls API (but does not fire action to dispatcher yet)
API does getJSON and waits for server results
only after results are received, API triggers the INITIALIZE action with received data
store responds to action, and updates itself with results
then emits change
which triggers re-render
I am not so familiar with jquery, promises and chaining, but I think this would roughly translate into the following changes in your code:
controller-view needs a change listener to the store: add a componentDidMount() function that adds an event listener to flux store changes.
in controller-view, the event listener triggers a setState() function, which fetches the most recent _bill from the store.
move the dispatcher.dispatch() from your actions.js to your api.js (replacing return data);
That way, your component initially should render some 'loading' message, and update as soon as data from server is in.
An alternative method would be to check if the prop of subscription exists before you play with the data.
Try modifying your code to look a bit like this:
render: function(){
var subscriptionPeriod = '';
var subscriptionDue = ['',''];
var subscriptionGenerated = '';
if(this.props.subscription !== undefined){
subscriptionPeriod = this.props.subscription.period;
subscriptionDue = [this.props.subscription.due.to,this.props.subscription.due.from];
subscriptionGenerated = this.props.subscription.generated;
}
return (
<div >
<h1>Statement</h1>
From: {subscriptionPeriod[0]} - To {subscriptionPeriod[1]} <br />
Due: {subscriptionDue}<br />
Issued:{subscriptionGenerated}
</div>
);
}
In the render function before the return try adding the following:
if(this.props.subscription != undefined){
// do something here
}
Due your data changing the state of the top level component it will retrigger the render once it has the data with the subscription prop being defined.
If I understand correctly you could try with something like this
// InitialActions.js
var InitialiseActions = {
initApp: function(){
BillApi.getBill(function(result){
// result from getJson is available here
Dispatcher.dispatch({
actionType: ActionTypes.INITIALISE,
initialData: {
bill: result
}
});
});
}
};
module.exports = InitialiseActions;
//api.js
var BillApi = {
getBillLocal: function() {
console.log(biller);
return biller;
},
getBill: function(callback) {
$.getJSON('https://theurl.com/stuff.json', callback);
}
};
$.getJSON does not return the value from the http request. It makes it available to the callback.
The logic behind this is explained in detail here: How to return the response from an asynchronous call?
I'll separate my Actions, Stores and Views (React components).
First of all, I'd implement my Action like this:
import keyMirror from 'keymirror';
import ApiService from '../../lib/api';
import Dispatcher from '../dispatcher/dispatcher';
import config from '../env/config';
export let ActionTypes = keyMirror({
GetAllBillPending: null,
GetAllBillSuccess: null,
GetAllBillError: null
}, 'Bill:');
export default {
fetchBills () {
Dispatcher.dispatch(ActionTypes.GetAllBillPending);
YOUR_API_CALL
.then(response => {
//fetchs your API/service call to fetch all Bills
Dispatcher.dispatch(ActionTypes.GetAllBillSuccess, response);
})
.catch(err => {
//catches error if you want to
Dispatcher.dispatch(ActionTypes.GetAllBillError, err);
});
}
};
The next is my Store, so I can keep track of all changes that suddenly may occur during my api call:
class BillStore extends YourCustomStore {
constructor() {
super();
this.bindActions(
ActionTypes.GetAllBillPending, this.onGetAllBillPending,
ActionTypes.GetAllBillSuccess, this.onGetAllBillSuccess,
ActionTypes.GetAllBillError , this.onGetAllBillError
);
}
getInitialState () {
return {
bills : []
status: Status.Pending
};
}
onGetAllBillPending () {
this.setState({
bills : []
status: Status.Pending
});
}
onGetAllBillSuccess (payload) {
this.setState({
bills : payload
status: Status.Ok
});
}
onGetAllBillError (error) {
this.setState({
bills : [],
status: Status.Errors
});
}
}
export default new BillStore();
Finally, your component:
import React from 'react';
import BillStore from '../stores/bill';
import BillActions from '../actions/bill';
export default React.createClass({
statics: {
storeListeners: {
'onBillStoreChange': BillStore
},
},
getInitialState () {
return BillStore.getInitialState();
},
onBillStoreChange () {
const state = BillStore.getState();
this.setState({
bills : state.bills,
pending: state.status === Status.Pending
});
},
componentDidMount () {
BillActions.fetchBills();
},
render () {
if (this.state.pending) {
return (
<div>
{/* your loader, or pending structure */}
</div>
);
}
return (
<div>
{/* your Bills */}
</div>
);
}
});
Assuming you are actually getting the data from your API, but are getting it too late and errors are thrown first, try this:
In your controller-view.js, add the following:
componentWillMount: function () {
BillStore.addChangeListener(this._handleChangedBills);
},
componentWillUnmount: function () {
BillStore.removeChangeListener(this._handleChangedBills);
},
_handleChangedBills = () => {
this.setState({bill: BillStore.getAllBill()});
}
And in your getInitialState function, give an empty object with the structure that your code expects (specifically, have a 'statement' object inside it). Something like this:
getInitialState: function(){
return {
bill: { statement: [] }
};
},
What is happening is that when you are getting your initial state, it isn't fetching from the store properly, and so will return an undefined object. When you then ask for this.state.bill.statement, bill is initialized but undefined, and so it cannot find anything called statement, hence why you need to add it in. After the component has had a bit more time (this is an async problem like the other posters said), it should fetch properly from the store. This is why we wait for the store to emit the change for us, and then we grab the data from the store.
I have this doubt that I haven't been able to google out yet but I have this react component that I want to update it's state using a reflux store using componentWillMount() method.
I am able to update the state in the store but using this.trigger to update it's state from the store didn't give me the updated state of the data which got me confused. How can I get the updated state of the data.
Here is what my component is like at the moment
var Challenges = React.createClass({
contextTypes: {
router: React.PropTypes.func
},
mixins: [Reflux.connect(ChallengeStore,'challenges')],
getInitialState: function() {
return {
challenges: []
}
}
componentDidMount: function() {
var trackId = this.props.params.trackId; // the url
ChallengeActions.GetChallenges(trackId);
console.log(this.state);
},
render: function () {
return(
<div>
<h1>{ this.state.challenges.title }</h1> <List challenges={ this.state.challenges } />
</div>
);
}
});
And my store here
var ChallengeStore = Reflux.createStore({
listenables: ChallengeActions,
onGetChallenges: function(url) {
var items = ChallengeService.getChallenges(url);
this.trigger({
challenges: items
});
}
});
Ran into this while figuring out Reflux this week.
The issue is Reflux.connect only connects a getInitialState() in the store which your store seems is missing.
As per the docs:
The Reflux.connect() mixin will check the store for a getInitialState
method. If found it will set the components getInitialState
Unless your store's initial state is consistent across all it's listeners, I find it's better to just use Reflux.listenTo():
var Status = React.createClass({
mixins: [Reflux.listenTo(statusStore,"onStatusChange")],
onStatusChange: function(status) {
this.setState({
currentStatus: status
});
},
render: function() {
// render using `this.state.currentStatus`
}
});