The duo was back at work. Mike entered with a cup of coffee. It was morning and the office had just started to buzz.
"So Shawn, we did a lot of complex forms stuff last time. Our cart flow is now complete. However, now we have been asked to add a timeout to the cart. We need to show a timer to the user that they need to checkout and complete the order in 15 minutes."
"Any idea how we can do this?"
"Umm, maintain a state for timer and keep updating every second? Take some action when the timer hits zero."
"Right! We will use intervals to reduce the timeout values and keep updating our views to display the timer. As we have been storing the form data in a single place, our Bookstore component, let's go ahead and add a state value that will track this timeout value. Let's change our initial state to something similar to the following:"
getInitialState() {
return ({currentStep: 1, formValues: {}, cartTimeout: 60 * 15});
}
"60 X 15, that's 15 minutes in seconds value. We will also need to add a method to keep updating this state so that we can use it freely from here as well as the child components."
updateCartTimeout(timeout){
this.setState({cartTimeout: timeout});
}
"Cool."
"Now, what we will do is define what are called as mixins."
"Mixins?"
"Yeah, mixins allow us to share a code across components. Let's take a look at how we are going to use it before moving ahead."
var SetIntervalMixin = {
componentWillMount: function() { this.intervals = [];
},
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() { this.intervals.map(clearInterval);
} };
module.exports = SetIntervalMixin;
"So what we are doing here is nothing much but defining an object. We will see how we use it in our components."
"As you can see, what we are trying to achieve here is add a way to track all our interval handlers, as follows:"
componentWillMount: function() { this.intervals = [];
}
"Here, we are first initializing an array to hold instances to intervals that we will be creating. Next, we will define a method that can be used to define new intervals, as follows:"
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
}
"Got it. I see the last bit is defining the componentWillUnmount method and we have already defined componentWillMount; but this isn't a React component. Why do we have these method here?"
"Oh right. Let's take a look at the following method first:"
componentWillUnmount: function() { this.intervals.map(clearInterval);
}
"What this method does is clean up the intervals, which we might have created, before we unmount our component."
"Got it."
"Now, as you mentioned, we have two life cycle methods here—
componentWillMount and componentWillUnmount."
"When we start using this in our component, they are called just like the other similar methods, which we have in our component for life cycle."
"Oh nice. Will both this and the existing method get called?" Shawn asked.
"Exactly. Now that we have the mixing defined, let's start using it!"
"The first place we want to start using this is on the delivery details page. This is as simple as doing the following:"
var DeliveryDetails = React.createClass({
…
mixins: [SetIntervalMixin]
…
"Awesome, next we would like to start using this to take care of storing cartTimout values and updating them. Can you define a mixin to do just that?" asked Mike.
"Okay, I will first define a method to decrement the cart timer, something that will keep updating the state. Next, we will need to actually set the timeout, to call the method at an interval so that it is called every second to decrement the time?"
"Exactly, let's see how you would do it."
var CartTimeoutMixin = {
componentWillMount: function () {
this.setInterval(this.decrementCartTimer, 1000);
},
decrementCartTimer(){
if (this.state.cartTimeout == 0) { this.props.alertCartTimeout();
return;
}
this.setState({cartTimeout: this.state.cartTimeout - 1});
}, };
"Nice, that's exactly what we need. But we missed one piece; we need to be able to send this back to that parent component to store back the timer value that we are updating here."
"We will also take care of passing the current state of timer from the parent to the children."
"Oh, right."
"Let's go back to our parent component to start passing the cart timer value to the children. Here's how our render method should look now:"
……
render() {
switch (this.state.currentStep) { case 1:
return <BookList updateFormData={this.updateFormData}/>;
case 2:
return <ShippingDetails updateFormData={this.updateFormData}
cartTimeout={this.state.cartTimeout}
updateCartTimeout={this.
updateCartTimeout} />;
case 3:
return <DeliveryDetails updateFormData={this.updateFormData}
cartTimeout={this.state.cartTimeout}
updateCartTimeout={this.
updateCartTimeout} />;
……
"Notice that we are passing the updateCartTimeout method here. This is something that we will start using next in our mixin."
"Next, we are going to update the DeliveryDetails component to start storing the cartTimeout value."
getInitialState() {
return { deliveryOption: 'Primary', cartTimeout: this.props.
cartTimeout };
}
"With this setup, we can now set up our render method for the delivery options page, this should now look similar to the following:"
render() {
var minutes = Math.floor(this.state.cartTimeout / 60);
var seconds = this.state.cartTimeout - minutes * 60;
return (
<div>
<h1>Choose your delivery options here.</h1>
<div style={{width:200}}>
<form onSubmit={this.handleSubmit}>
<div className="radio">
<label>
Primary -- Next day delivery
</label>
Normal -- 3-4 days
</label>
<span className="glyphicon glyphicon-time"
aria-hidden="true"></span> You have {minutes} Minutes, {seconds} Seconds, before confirming order
</div>
</div>
);
}
"We also need to start using the CartMixin, so our mixins import should look similar to the following:"
…
mixins: [SetIntervalMixin, CartTimeoutMixin],
…
"Nice, let me see how the shipping information looks like now."
"It works!" exclaimed Shawn.
"Awesome. Remember, Shawn, now we need to pass the information back to our parent component when we change to some other page."
"Yeah, we should add it to the components, where we used the mixin?"
"Better yet, let's add the following code to the mixin:"
….
componentWillUnmount(){
this.props.updateCartTimeout(this.state.cartTimeout);
}
….
"Now our mixin should look similar to the following:"
var CartTimeoutMixin = {
componentWillMount: function () {
this.setInterval(this.decrementCartTimer, 1000);
},
decrementCartTimer(){
if (this.state.cartTimeout == 0) { this.props.alertCartTimeout();
return;
}
this.setState({cartTimeout: this.state.cartTimeout - 1});
},
componentWillUnmount(){
this.props.updateCartTimeout(this.state.cartTimeout);
} };
module.exports = CartTimeoutMixin;
"Our mixin will now update the current cart value when it gets unmounted."
"We missed one thing that is part of this mixin. We call this.props.
alertCartTimeout() when the timer hits zero."
"We are going to define this on the parent component and pass it around to be called from child component, as follows:"
alertCartTimeout(){
this.setState({currentStep: 10});
},
"Then update our render method to take care when we reach the timeout step, as shown in the following:"
render() {
switch (this.state.currentStep) { case 1:
return <Success data={this.state.formValues}
cartTimeout={this.state.cartTimeout}/>;
case 10:
/* Handle the case of Cart timeout */
return <div><h2>Your cart timed out, Please try again!</h2></
div>;
default:
return <BookList updateFormData={this.updateFormData}/>;
} }
Let's see how the DeliveryDetails component looks after completing it, now:"
import React from 'react';
import SetIntervalMixin from './mixins/set_interval_mixin' import CartTimeoutMixin from './mixins/cart_timeout_mixin' var DeliveryDetails = React.createClass({
propTypes: {
alertCartTimeout: React.PropTypes.func.isRequired, updateCartTimeout: React.PropTypes.func.isRequired, cartTimeout: React.PropTypes.number.isRequired },
mixins: [SetIntervalMixin, CartTimeoutMixin], getInitialState() {
return { deliveryOption: 'Primary', cartTimeout: this.props.
cartTimeout };
},
componentWillReceiveProps(newProps){
this.setState({cartTimeout: newProps.cartTimeout});
},
handleChange(event) {
this.setState({ deliveryOption: event.target.value});
},
handleSubmit(event) { event.preventDefault();
this.props.updateFormData(this.state);
},
render() {
var minutes = Math.floor(this.state.cartTimeout / 60);
var seconds = this.state.cartTimeout - minutes * 60;
return (
<div>
<h1>Choose your delivery options here.</h1>
<div style={{width:200}}>
Primary -- Next day delivery
</label>
Normal -- 3-4 days
</label>
<span className="glyphicon glyphicon-time"
aria-hidden="true"></span> You have {minutes} Minutes, {seconds} Seconds, before confirming order
"We are also going to update our ShippingDetails component to look similar to the following:"
import React from 'react';
import SetIntervalMixin from './mixins/set_interval_mixine' import CartTimeoutMixin from './mixins/cart_timeout_mixin'
var ShippingDetails = React.createClass({
propTypes: {
alertCartTimeout:React.PropTypes.func.isRequired, updateCartTimeout: React.PropTypes.func.isRequired, cartTimeout: React.PropTypes.number.isRequired },
mixins: [SetIntervalMixin, CartTimeoutMixin], getInitialState() {
return {fullName: '', contactNumber: '', shippingAddress: '', error: false, cartTimeout: this.props.cartTimeout};
},
_renderError() {
if (this.state.error) { return (
<div className=e"alert alert-danger">
{this.state.error}
</div>
);
} },
_validateInput() { …..
},
handleSubmit(event) { ….
},
handleChange(event, attribute) { var newState = this.state;
newState[attribute] = event.target.value;
this.setState(newState);
console.log(this.state);
},
render() {
var errorMessage = this._renderError();
var minutes = Math.floor(this.state.cartTimeout / 60);
var seconds = this.state.cartTimeout - minutes * 60;
return (
<div>
<h1>Enter your shipping information.</h1>
…..
<div className='well'>
<span className="glyphicon glyphicon-time"
aria-hidden="true"></span> You have {minutes} Minutes, {seconds} Seconds, before confirming order
</div>
</div>
);
} });
module.exports = ShippingDetails;
It should start looking similar to the following screenshot now:
"Awesome," exclaimed Shawn.
"In case of timeouts, we have a simple display:"