BUilding Reactive Applications with Meteor and React
Ramsay Lanier
I LOVE JAVASCRIPT
ON THE CLIENT
ON THE server
in a box
with a fox
javascript everywhere
so what is meteor?
"Meteor is a full-stack JavaScript App Platform that assemble all the pieces you need to build modern web and mobile apps, with a single JavaScript codebase."
A library of packages: pre-written, self-contained modules that you might need in your app.
the seven principles of meteor
1. Data on the wire
Meteor doesn't send HTML over the network. The server sends data and lets the client render it.
(you can, with a little bit of effort, have server side rendering though.)
2. one language
Isomorphic
3. Database everywhere
You can use the same methods to access your database from the client or server, thanks to minimongo.
4. latency compensation
Client-side, Meteor prefetches data and simulates models to make it look like a server method call returns instantly.
We call this optomistic UI.
5. full stack reactivity
Everything gets updates automatically, in real-time, and only when necessary.
6. embrace the ecosystem
Meteor is open source and it should be able to integrate with other open source platforms.
Like React.
7. simplicity = productivity
Clean, easy to use APIs.
meteor on the server
MongoDB
hammer TIME
built with Meteor and React and Giphy and parachute pants.
REACT
// /lib/router.jsx
FlowRouter.route('/', {
action: function(){
ReactLayout.render(MainLayout, {
content: <HammerPage/>
})
}
})
// client/components/Layout/mainLayout.jsx
MainLayout = React.createClass({
render(){
return (
<div className="application">
<Header className="app-header">
<div className="title-container">
<p className="title"></p>
</div>
</Header>
<main>
{this.props.content}
</main>
</div>
)
}
});
REACT
// /client/components/Page/_hammerPage.jsx
HammerPage = React.createClass({
mixins: [ReactMeteorData],
getMeteorData(){
let subscription = Meteor.subscribe('hammerList');
return {
loading: !subscription.ready(),
hammers: Hammers.find({}, {sort: {createdOn: -1}}).fetch()
};
},
render(){
if (this.data.loading){
return <p>...loading</p>
} else{
return (
<Page>
<HammerButton />
<HammerList hammers={this.data.hammers}/>
</Page>
)
}
}
});
// /server/publications.js
Meteor.publish('hammerList', function(){
return Hammers.find();
})
REACT
// /client/components/Page/page.jsx
Page = React.createClass({
componentDidMount(){
//animate Page on load
let item = $(this.getDOMNode());
TweenMax.fromTo(page, .5, {
opacity: 0
}, {
opacity: 1,
ease: Power4.easeOut
} )
},
render(){
let styles = {
backgroundImage: "url('" + this.props.backgroundImage + "')",
}
let children = this.props.children;
return(
<div className={"page " + this.props.className} style={styles}>
{children}
</div>
)
}
});
REACT
// /client/components/Button/_hammerButton.jsx
HammerButton = new React.createClass({
render(){
return(
<div className="hammer-button">
<input type="text" className="hammer-name-field" placeholder="Name Your Hammer"/>
<Button action={this.action} className="primary-btn">Add Hammer</Button>
</div>
)
},
action(){
// use the meteor HTTP package to get a random MC Hammer GIF!
HTTP.get('http://api.giphy.com/v1/gifs/random?api_key=dc6zaTOxFJmzC&tag=mc+hammer', function(error,result){
let hammerImage = result ? result.data.data.image_url : '/images/hammertime.gif';
let hammerName = $('.hammer-name-field').val();
//call a Meteor Methods to create a new Hammer. This methods is available on both the client and the server! Latency Compensation FTW!
Meteor.call('addHammer', hammerImage, hammerName, function(err, res){
if (err){
alert(err.reason)
}
});
});
}
})
REACT
// lib/collections/hammers.js
//this is our Hammers collection
Hammers = new Mongo.Collection('hammers');
Meteor.methods({
addHammer: function(hammerImage, hammerName){
//we should see this logged to both the client and the server logs!
console.log(hammerName);
if (!hammerName){
throw new Meteor.Error(422, 'Please name your Hammmmmmer.')
}
//uses Meteor's Check package to check that arguments are Strings, not Objects. Objects can be bad!
check(hammerName, String);
check(hammerImage, String);
//create a new Hammer by inserting it into a Mongo DB collection. This returns the Mongo ID of the new document
return Hammers.insert({image: hammerImage, name: hammerName, createdOn: new Date()});
}
})
REACT
// client/components/List/_hammerList.jsx
HammerList = new React.createClass({
render(){
// this is passed in from HammerPage - we only get data from top level components and let it flow down.
let hammers = this.props.hammers;
//loop through each object in hammers and render the HammerItem component
return (
<List className="hammer-list">
{hammers.map( hammer => {
return(
<HammerItem key={hammer._id} hammer={hammer} />
)
})}
</List>
)
}
})
HammerItem = new React.createClass({
componentDidMount(){
//lets animate these when the get rendered!
let count = Session.get('itemCount') || 0;
Session.set('itemCount', count + 1);
let item = $(React.findDOMNode(this.refs.item));
let delay = (count % 6) / 20;
TweenMax.fromTo(item, 1, {
opacity: 0,
y: 20
}, {
opacity: 1,
y: 0,
ease: Power4.easeInOut,
delay: delay
})
},
render(){
let style = {
backgroundImage: "url('" + this.props.hammer.image + "')"
}
return(
<li ref="item" style={style} className="hammer-item">
<h2 className="hammer-name">{this.props.hammer.name}</h2>
</li>
)
}
})
Meteor Resources
QUESTIONS
Reactive Applications with Meteor and React
By Ramsay Lanier
Reactive Applications with Meteor and React
- 1,506