Reactjs mounting state before initial render or keep as is? - javascript

State on the initial render here is the initial state. Am using componentWillMount() however it won't work without first checking whether the component is mounted. The result when using rawMarkup() is an empty initial state object. Everything functions properly on the re-render but wondering why the post object within state is still empty on the initial render. Is there a better way of accomplishing this? Thank you.
var Post = React.createClass({
propTypes: {
source: React.PropTypes.string.isRequired,
},
getInitialState: function() {
return {
post: {}
};
},
getDefaultProps: function() {
return {
source: 'http://localhost:3000/api/posts/' + postSlug
};
},
componentWillMount: function() {
$.get(this.props.source, function(result) {
if (this.isMounted()) {
this.setState({post: result});
}
}.bind(this));
},
rawMarkup: function() {
console.log(this.state.post);
var rawMarkup = marked(this.state.post.body || '', {sanitize: true});
return { __html: rawMarkup };
},
render: function() {
var post = this.state.post;
var date = moment(post.date).format('MM[/]DD[/]YYYY');
return (
React.createElement('div', {style: {marginLeft: '115px'}},
React.createElement('h1', {style: {marginTop: '50px'}}, post.title),
React.createElement('h5', {style: {marginBottom: '25px'}}, date),
React.createElement('span', {dangerouslySetInnerHTML: this.rawMarkup()})
)
);
}
});

By default, the jQuery get request is asynchronous, so the initial render happens before the request completes and this.setState({post: result}) is called. You could make the get request synchronous (How can I get jQuery to perform a synchronous, rather than asynchronous, Ajax request?), or figure out a way to gracefully deal with no data on the initial render (recommended).

Related

Meteor React renders twice after subscription are ready

I'm using Meteor with react and FlowRouter to handle subscriptions. I find that when my component renders it will render twice after a few seconds, but only when I have the meteor mixin subscribed to a subscription.
For example:
PeoplePage = React.createClass({
displayName:"People",
mixins: [ReactMeteorData],
getMeteorData() {
const subHandles = [
Meteor.subscribe("allPeople"),
];
const subsReady = _.all(subHandles, function (handle) {
return handle.ready();
});
return {
subsReady: subsReady,
people: People.find({}).fetch(),
};
},
render(){
if(this.data.subsReady == false){
return (<Loading/>);
} else {
console.log(this.data);
........
}
The same information is shown twice. Is this due to fast render that FlowRouter uses, or is it something that I am doing incorrectly?
Hmm, I guess the problem is that you are triggering the subscription every time, when the component re-renders.. I haven't tried it, but you could check if this will solve the problem
getMeteorData() {
const subsReady = _.all(this.subs || [{}], function (handle) {
if (typeof handle.ready == 'function')
return handle.ready();
return false;
});
if (!subsReady) // you can extend it, to provide params to subscriptions
this.subs = [
Meteor.subscribe("allPeople")
];
return {
subsReady: subsReady,
people: People.find({}).fetch(),
}
}
It should not trigger the subs if they are already ready.
Be aware, that mustn't pass an empty array to _.all, because of this:
_.all([], function(a) {return a.b()}) // true
this is why I added an empty object to the array, so this way you can check for the ready member..
I would suggest doing to subscription within the componentWillMount() function. This way, you make sure that you only subscribe once before the initial render().
getMeteorData() {
var ready = _.all(this.subHandles, function (handle) {
return handle.ready();
});
return {
subsReady: ready,
people: People.find({}).fetch()
}
},
componentWillMount() {
this.subHandles = [];
this.subHandles.push(Meteor.subscribe('allPeople');
},
componentWillUnmount() {
this.subHandles.map(function(handle) {
handle.stop();
});
}
If it still renders twice, I would suggest trying to turn of fast render for the route and check if this problem still occurs.

Pass data to React component with kefirjs

I am new in ReactJS and "reactive programming". I tried to create a dispatcher, action and store according to this project, but I don't know how to pass data to component.
In this example it doesn't work.
var data = [1, 2, 3, 4, 5];
var AppDispatcher = Kefir.emitter();
function DataActions() {
this.getAllData = function () {
AppDispatcher.emit({
actionType: "GET_ALL"
});
};
}
var Actions = new DataActions();
var getAllDataActionsStream = AppDispatcher.filter(function (action) {
return action.actionType === "GET_ALL";
}).map(function (action) {
return function (data) {
return data;
};
});
var dataStream = Kefir.merge([getAllDataActionsStream]).scan(function (prevData, modificationFunc) {
return modificationFunc(prevData);
}, {});
var Content = React.createClass({
getInitialState: function() {
this.onDataChange = this.onDataChange.bind(this);
return {componentData: []};
},
componentDidMount: function() {
dataStream.onValue(this.onDataChange);
},
componentWillMount: function(){
dataStream.offValue(this.onDataChange);
console.log(Actions.getAllData());
},
onDataChange(newData) {
this.setState({componentData: newData});
},
render: function() {
console.log(this.state);
var list = this.state.componentData.map(function (item, i) {
return (
<li key={i}>{item}</li>
);
});
return <ul>{list}</ul>;
}
});
React.render(<Content />, document.getElementById('container'));
Before I begin to answer in length I want to answer this part up front:
but I don't know how to pass data to component.
In the example you linked the author passes in the Todos into the main component using React's props, not with an action. So that is the approach I take in my example as well.
Now here is my example. I highly reccommend looking at the example and reading along to what I've written below.
var data = [ 1, 2, 3, 4, 5 ];
// This will now log all events of the AppDispatcher in the console with the prefix 'Kefer: '
var AppDispatcher = Kefir.emitter().log("Kefir: ");
function DataActions() {
// Our application has an action of emitting a random number.
this.emitNumber = function() {
AppDispatcher.emit({
actionType: "EMIT_NUMBER"
})
};
}
var Actions = new DataActions();
var emitNumberActionStream = AppDispatcher
.filter(function(action) {
return action.actionType === "EMIT_NUMBER";
})
.map(function(action) {
console.log("EMIT_NUMBER ACTION OCCURRED!!");
return Math.floor(Math.random() * (10)) + 1;
});
// Only one stream, no need to merge right now.
//var dataStream = Kefir.merge([ getAllDataActionsStream ]);
var Content = React.createClass({
getInitialState: function() {
// Set initial componentData using the data passed into this component's via props
return { componentData: this.props.data };
},
componentDidMount: function() {
// On each emitted value run the this.onDataChange function
emitNumberActionStream.onValue(this.onDataChange);
// Every second emit a number using the Actions we created earlier
setInterval(function() {
Actions.emitNumber();
}, 1000);
},
onDataChange: function(emittedNumber) {
console.log('state on change:', this.state);
// Update the state by appending the emitted number to the current state's componentData
this.setState({ componentData: this.state.componentData.concat([emittedNumber])});
console.log('updated state: ', this.state);
console.log('-----------------');
},
render: function() {
console.log('RENDER AGAIN!');
var list = this.state.componentData.map(function(item, i) {
return (
<li key={i}>{item}</li>
);
});
return <ul>{list}</ul>;
}
})
;
// Pass in initial data using props 'data={data}'
React.render(<Content data={data}/>, document.getElementById('container'));
I modified the example you gave that wasn't working so that it works and makes a little more sense (hopefully).
The Actions and Stores work like this:
Actions:
Request a number be emitted
Stores
Listen for "EMIT_NUMBER" actions and emit a random number
And the actual component runs like this:
It gets the initial 5 numbers passed into the component via props.
Once mounted it begins listening to the store and creates a setInterval that calls the action dispatcher's emitNumber() action. The interval is to show the reactivity at work, you could imagine that there was a button to press that would call emitNumber() instead.
The store observes the action dispatcher emit "EMIT_NUMBER" and emits a number.
The component observes the store emitted a number and updates the component's state.
The component observes that its state has changed and it rerenders.
I believe the issue is that you're using ES6 syntax (which is what the example was written in... notice the Readme). You'll need to either use a transpiler like Babel or convert your method(param => console.log(param)) syntax into normal JS (ie, method(function(param) { console.log(param) });).

Set state after observe()

What's the best way to set state based on the data received from observe()?
It seems setting state via componentWillMount() won't work as observe() runs after this and the data isn't available to set state.
I'm using the observe function as advised when using Parse
E.g.:
var DragApp = React.createClass({
getInitialState: function () {
return {
activeCollection : ''
};
},
observe: function() {
return {
collections: (collectionsQuery.equalTo("createdBy", currentUser))
};
},
_setactiveCollection: function(collection) {
this.setState({
activeCollection : collection
});
},
componentWillMount: function () {
var collection = this.data.collections[0];
this._setActiveCollection(collection);
},
)}
I went the wrong way about this.
I shouldn't be storing this.data into state. I can pass it into components via render.
To get round this.data not being ready before rendering, I make use of the ParseReact function pendingQueries() inside render. E.g.
if (this.pendingQueries().length > 0) {
content = 'loading...'
} else {
content = 'hello world I am' + this.data.name
}
Try:
var DragApp = React.createClass({
observe: function() {
var collections = collectionsQuery.equalTo("createdBy", currentUser);
return {
collections: collections,
activeCollection: collections[0]
};
},
render: function () {
// do something with this.data.collections and/or this.data.activeCollection
},
)}

State null in component

I'm having an issue with creating a component using react and martyjs. I'm sure it is a typo or something but I just can't seem to find it. Although I have a state mixin in the component the state is not being populated and it doesn't look like getState is even being called in the mixin.
Mixin.es6
var StateMixin = Marty.createStateMixin({
listenTo: VideoStore,
getState: function() {
return {
items: VideoStore.list(),
currentItem: VideoStore.select(),
}
}
});
State.es6
var VideoStore = Marty.createStore({
displayName: "Store",
handlers: {
list: Events.List,
render: Events.Render
},
getInitialState: function(){
return { };
},
list: function(){
return this.fetch({
id: 'list',
locally: function(){
if(this.hasAlreadyFetched('list') )
return this.state.items;
},
remotely: function(){
return DissolveStateSource.list();
}
});
},
select: function(){},
render: function(){}
});
Component.es6
$( ()=>
React.render(
<VideosTable/>,
$("#container")[0]
));
var VideosTable = React.createClass(
{
mixins: StateMixin,
render: function() {
var body = this.state.list.when({ //state is null here
pending: function(){
return <span className="ball"></span>;
},
failed: function(error){
return <div className="error">error.message</div>;
},
done: function(videos){
return <div>Videos</div>;
}
});
return <h2>hello</h2>;
}
});
Any idea what I'm doing wrong?
Edit: I've added a js bin thing here
http://jsbin.com/lekegicumo/2/edit?html,js,console,output
Looks like a typo in Mixin.es6 to me.
Change getState to getInitialState.
Also, in Component.es6:
Change mixins: StateMixin to mixins: [StateMixin].
The problem ended up being that the order of inclusion of JavaScript files was incorrect. Swapping some around fixed the issue.
are u using react v0.1.13.0
this is new way to initial your state using 'construct'
constructor(props) {
super(props);
this.state = {count: props.initialCount};
}
https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html

React does not rerender after setState used in promise

Every time the props are changed, the component will call onTermChange and get the details for this component with a promise that returns an array of objects.
The problem is that when setState is called, nothing happens and the component is not re-rendered with fresh details.
module.exports = React.createClass({
displayName: 'TaxonomySelect',
getInitialState: function () {
return {
children: undefined
};
},
componentDidMount: function () {
this.onTermChange(this.props.term);
},
componentWillReceiveProps: function (nextProps) {
this.props.term = this.props.term || null;
if (this.props.term) {
this.onTermChange(nextProps.term.id);
} else {
this.onTermChange(nextProps.term);
}
},
onTermChange: function (term) {
this.setState({children: undefined});
TaxonomyStore.getTerm(this.props.term)
.bind(this)
.then(function (term) {
TaxonomyStore.merge(9999,{
description: 'No specific topic',
id: 9999
});
if (term.path && term.path.length === 3) {
term.children.unshift(TaxonomyStore.get(9999));
}
console.log(term.id, term.children);
this.setState({children: term.children});
this.forceUpdate();
this.render();
});
},
onSelect: function (id) {
if (this.props.onChange) {
this.props.onChange(TaxonomyStore.get(id));
}
},
render: function () {
if (!Array.isArray(this.state.children) || this.state.children.length < 1) {
return null;
};
var options = this.state.children.map(function (term) {
return {
value: term.id.toString(),
label: term.description
};
});
var value = this.props.value && this.props.value.toString();
return (
<div>
<Select name={this.props.name} value={value} options={options} onChange={this.onSelect} />
</div>
);
}
});
When you call this.setState({children: term.children}); this equals the function it was defined in, not the react component.
Probably an exception occurs, but your promise does not call .error to trap and log it.
You shouldn't need to call this.forceUpdate() if you are calling this.setState. Also, you should never call a component's render method.
It's hard to tell why it's not rerendering but I would a few debugger statements to see whether render is getting called. I'd guess that always calling this.onTermChange in componentWillReceiveProps may be a potential issue.
I came across the same problem, inspired by #z5h, I use a local viraible to refer to this outside of Promise, and it works!
In your case:
onTermChange: function (term) {
this.setState({children: undefined});
let _this = this; // change
TaxonomyStore.getTerm(this.props.term)
.bind(this)
.then(function (term) {
TaxonomyStore.merge(9999,{
description: 'No specific topic',
id: 9999
});
if (term.path && term.path.length === 3) {
term.children.unshift(TaxonomyStore.get(9999));
}
console.log(term.id, term.children);
_this.setState({children: term.children}); //change
});
}
More about this in js: How does the “this” keyword work?

Categories