Is there an established pattern used to manage user interactions with individual components, such as displaying loader spinners, disabling input fields while a form is saving/loading, etc.?
I'm finding myself doing the following in my stores in order to keep components somewhat decoupled from any implied state:
function CampaignStore() {
EventEmitter.call(this);
AppDispatcher.register(payload => {
switch (payload.type) {
// [#1] ---------------v (main action)
case CampaignContants.SAVE:
// [#2] ------------------------v (prepare for the main action)
this.emit(CampaignContants.WILL_SAVE);
const data = payload.data;
if (data.id) {
// [#3] ---v (perform main action in store)
updateCampaign(payload.data).then(_ => {
// [#4] ------------------------v (after main action)
this.emit(CampaignContants.DID_SAVE, 0)
});
} else {
insertCampaign(payload.data).then(campaignId => this.emit(CampaignContants.DID_SAVE, campaignId));
}
break;
// ...
}
}
}
Basically, I just fire an event saying that some action is about to take place, then I perform the action (make API call, etc.), then emit another event when the action completes.
Inside a component, I can just subscribe to a WILL_<action> event, render all the spinners and such, then clear up the screen when the DID_<action> is fired. While this seems to work, it does feel pretty boilerplattie and repetitive, as well as super messy (way too much state that only exists to tweak the UI based on where an action is (between WILL_<action> and *DID_<action>.
// some component
var MyComponent = React.createClass({
getInitialState: function () {
return {
items: [],
loading: false,
saving: false,
checkingPasswordStrength: fase,
// ...
};
},
render: function(){
return (
<div>
{this.state.loading && (
<p>Loading...</p>
)}
{!this.state.loading && (
// Display component in not-loading state
)}
</div>
);
}
});
I think you would be better off using the lifecycle methods such as componentWillMount, componentDidMount, componentWillUpdate, and componentWillUnmount. Using those methods you can inspect the previous/current/next props/state (depending on the method) and respond to that. That way your store only handles your state, and your components become more pure.
we have found a simple loading container component helps here.
so something like this:
const LoadingContainer = React.createClass({
getDefaultProps: function() {
return {isLoadedCheck:(res) => res.data!=null }
},
getInitialState: function() {
return {isLoaded:false, errors:[]}
},
componentDidMount: function() {
if(this.props.initialLoad) { this.props.initialLoad(); }
if(this.props.changeListener) { this.props.changeListener(this.onChange); }
},
onChange: function() {
let res = this.props.loadData();
this.setState({errors: res.errors, isLoaded: this.props.isLoadedCheck(res)});
},
render: function() {
if(!this.state.isLoaded) {
let errors = this.state.errors && (<div>{this.state.errors.length} errors</div>)
return (<div>{errors}<LoadingGraphic /> </div>)
}
return <div>{this.props.children}</div>
}
});
const Wrapper = React.createClass({
getDefaultProps: function() {
return {id:23}
},
render: function() {
let initialLoad = () => someActionCreator.getData(this.props.id);
let loadData = () => someStore.getData(this.props.id);
let changeListener = (fn) => someStore.onChange(fn);
return (<div><LoadingContainer initialLoad={initialLoad}
changeListener={changeListener}
loadData={loadData}
isLoadedCheck={(res) => res.someData != null}><SomeComponent id={this.props.id} /></LoadingContainer></div>)
}
});
while it adds another stateless wrapper, it gives a clean way to make sure your components dont just load on mount and a common place to show api feedback etc.
and with react 14 these sort of pure stateless wrappers are getting a bit of a push, with perf improvements to come, so we've found it scales nicely
This is the pattern which will help you in getting your individual components to manage user interactions
var MyComponent = React.createClass({
getInitialState: function() {
return {
item: [],
loading: true,
};
},
componentDidMount: function() {
//Make your API calls here
var self = this;
$.ajax({
method: 'GET',
url: 'http://jsonplaceholder.typicode.com/posts/1',
success: function(data) {
if (self.isMounted()) {
self.setState({
item: data,
loading: false
});
}
}
});
},
render: function() {
var componentContent = null;
if (this.state.loading) {
componentContent = (<div className="loader"></div>);
} else {
componentContent = (
<div>
<h4>{this.state.item.title}</h4>
<p>{this.state.item.body}</p>
</div>
);
}
return componentContent;
}});
Related
I'm now to react and I'm wondering if what I've done is a bad way of creating this component. What I wonder is:
Is this the correct way to do the callback in the setState? If not, where should this line $('#editor').data('kendoEditor').value(data) be placed?
componentDidUpdate(prevProps) {
if(this.props.id!== prevProps.id) {
$.get('/webapi/GetData?id=' + this.props.id, function (data) {
this.setState({ editorValue: data }, $('#editor').data('kendoEditor').value(data));
}.bind(this));
}
}
Why doesn't this work?
componentDidMount() {
this.initEditor();
$.get('/webapi/GetData', function (data) {
this.setState({ data: data });
}.bind(this));
}
initEditor = () => {
$("#editor").kendoEditor({
value: this.state.editorValue,
)}
}
but this works?
componentDidMount() {
$.get('/webapi/GetData', function (data) {
this.setState({ data: data });
this.initEditor();
}.bind(this));
}
To properly do a callback after setState follow this format:
this.setState( { foo: bar }, () => callbackFunction() )
EDIT
To answer the second part of your question, you shouldn't need to use those lines of code at all. Let React handle the rendering. Say you have a render like so
render() {
return(
<div>
<input type="text" name="someValue" data-kendoeditor={this.state.editorValue} />
</div>
)
}
Then call setState like:
componentDidUpdate(prevProps) {
if(this.props.id!== prevProps.id) {
$.get('/webapi/GetData?id=' + this.props.id, function (data) {
this.setState({ editorValue: data });
}.bind(this));
}
}
This will rerender the value from state to the DOM.
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 am a newbie in React.js. While trying to understand the lifecycles in React, i stumbled upon componentWillReceiveProps. Even though i got hold of other functions, i am still not able to figure out componentWillReceiveProps. I created a small snippet where on every button click, i am incrementing the variable 'val'. When val becomes a multiple of 5, i want to change the value of 'increasing', which i am not able to do.
My Code is:
var Increment = React.createClass({
getInitialState: function() {
return {val: 0, increasing: false};
},
componentWillMount: function() {
console.log("componentWillMount");
},
componentDidMount: function() {
console.log("componentDidMount");
},
handleClick: function() {
console.log("inHandleClick");
console.log(this.state.val);
this.setState({val: (this.state.val+1)});
},
componentWillReceiveProps : function(nextProps) {
this.setState({
increasing: (nextProps.val > this.props.val)
});
},
shouldComponentUpdate: function(nextProps, nextState) {
return (nextState.val % 5 ==0)
},
render: function() {
console.log(this.state.increasing);
return (
<div>
<button onClick={this.handleClick}>{this.state.val}</button>
</div>
);
}
});
ReactDOM.render(<Increment />, mountNode);
Any Leads? Thanks in advance
See this fiddle
var IncrementButton = React.createClass({
componentWillReceiveProps : function(nextProps) {
this.setState({
increasing: (nextProps.val > this.props.val)
});
},
render: function() {
return (<button onClick={this.props.handleClick}>{this.props.val}</button>);
}
});
var Increment = React.createClass({
getInitialState: function() {
return {val: 0, increasing: false};
},
handleClick: function() {
console.log("inHandleClick");
console.log(this.state.val);
this.setState({val: (this.state.val+1)});
},
shouldComponentUpdate: function(nextProps, nextState) {
return (nextState.val % 5 ==0)
},
render: function() {
console.log(this.state.increasing);
return (
<div>
<IncrementButton handleClick={this.handleClick} val={this.state.val}/>
</div>
);
}
});
React.render(<Increment />, mountNode);
(Thanks to #Aaron for a more accurate description below)
componentWillReceiveProps is called if your component's props get set; note it may be called even though the props haven't changed in value (You seem to take this into account with your greater than check). Since you want to compare a new value of props to an old value, you need the state to be managed outside your component. I have therefore, in this sample, split your component into two pieces by extracting the button.
I have a parent class and a child class. The child class will perform an initial load operation based on a property passed from the parent. The parent class will access that data using a static method from the child. In essence, I'm trying to use the child class as a service. The code below illustrates the scenario (note that it's just pseudo code).
var Parent = React.createClass({
componentDidMount: function() {
console.log("parent component was mounted");
},
render: function() {
return (
<div>
<Child param={this.props.param} />
<p>Child.fetch('key')</p>
</div>
);
}
});
var Child = React.createClass({
getInitialState: function() {
return {
data: {}
};
},
componentDidMount: function() {
console.log("child component was mounted");
$.ajax({
url: "server/api.php",
data: {
param: param
}
}).done(function(response) {
this.setState({data: response});
}.bind(this));
},
statics: {
get: function(key) {
console.log('get requested for key: ' + key);
var value = null; // default value
if(this.data == null) { return value; }
//get the value from the data based on the key
return value;
}
},
render: function() {
return (
<h2>{this.props.param}</h2>
);
}
});
The problem here is that the parent's render function does not update after the data was loaded from the child class. The order of the console.log results is:
Parent render: get requested for key: key
parent component was mounted
child component was mounted
=> should trigger an update to re-render the parent
I'm not sure if its possible to trigger the parent's render function only once after the child component has been loaded. I'm guessing its not, so triggering an update for the parent's render method would suffice. Any suggestions, improvements are welcome as this is fairly new to me.
You should add a callback prop to your Child component that the child can trigger once the data has been loaded.
On the Parent side you just need to call this.forceUpdate().
What you are experiencing is expected since the lifecycle of your Child component is different than the one for your Parent class.
Still, you might need to analyze the possibility to move your load logic into the Parent and delegate the rendering of parts of the response to each child. This will also help performance since only one HTTP request will be needed to load all the data.
** Code **
var Parent = React.createClass({
componentDidMount: function() {
console.log("parent component was mounted");
},
render: function() {
return (
<div>
<Child param={this.props.param} onDataLoaded={this.forceUpdate} />
<p>Child.fetch('key')</p>
</div>
);
}
});
var Child = React.createClass({
getInitialState: function() {
return {
data: {}
};
},
componentDidMount: function() {
console.log("child component was mounted");
$.ajax({
url: "server/api.php",
data: {
param: param
}
}).done(function(response) {
if (typeof this.onDataLoaded === 'function')
this.onDataLoaded();
this.setState({data: response});
}.bind(this));
},
statics: {
get: function(key) {
console.log('get requested for key: ' + key);
var value = null; // default value
if(this.data == null) { return value; }
//get the value from the data based on the key
return value;
}
},
render: function() {
return (
<h2>{this.props.param}</h2>
);
}
});
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 ?