Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Build Applications with Meteor

You're reading from   Build Applications with Meteor Isomorphic JavaScript web development

Arrow left icon
Product type Paperback
Published in May 2017
Publisher Packt
ISBN-13 9781787129887
Length 388 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Author (1):
Arrow left icon
Dobrin Ganev Dobrin Ganev
Author Profile Icon Dobrin Ganev
Dobrin Ganev
Arrow right icon
View More author details
Toc

The frontend with React

The best way to start with React is by modifying and extending a Hello World example. There is a basic template available in the Facebook documentation.

For simplicity, you don't need a server to get started with React; you can load it and explore it as an HTML document by opening it in your browser.

Create a basic HTML page and place the scripts into the head tag:

<head>
<meta charset="UTF-8" />
<title>Hello World</title>
<script

src="https://unpkg.com/react@latest/dist/react.js">
</script>
<script

src="https://unpkg.com/react-dom@latest/dist/
react-dom.js
"></script>
<script

src="https://unpkg.com/[email protected]/
babel.min.js
"></script>
</head>

With the preceding scripts, we loaded react and react-dom and the third script--babel--will make (transpile) our ES6 and JSX scripts compatible with the browsers.

Also, we can render our first component in the body tag:

<body>
<div id="root"></div>
<script

type="text/babel"> //this will make ES6 code and React Jsx compatible with the browser.


ReactDOM.render(<h1>Hello, world!</h1>,
document.getElementById('root'))

</script>
</body>

There are two ways to define components in React: as plain JavaScript functions or as an ES6 class that extends from React.Component.

Let's look at a basic ES6 class component:

class Greeting extends React.Component 

{
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

In the preceding code, we have an ES6 class Greeting that extends from React Component class; then we defined a render method that returns HTML tags. HTML tags in JavaScript is the special thing that comes with React. In essence, it is JavaScript XML (JSX) that Facebook added to React to make it more descriptive, and it also saves typing.

You can use plain JavaScript but many developers, including myself, find it actually easier to write HTML into the JavaScript.

Next, paste the Greeting component just above the ReactDOM.render line:

class Greeting extends React.Component {
render() {
return <h1>Hello,

{this.props.name}</h1>;
}
}
ReactDOM.render(
<h1>Hello, world!

</h1>,document.getElementById('root')
)

If you save your file and refresh your browser, you'll see that nothing changed. To render a component, you need to attach it to an actual DOM element or add it to a parent component that can render it.

The idea of React is that we can build components or blocks and put them together to assemble complex user interfaces. Another thing you may find different is that the data flow between components is not that well prescribed like other frameworks. React itself has no actual strict pattern on how that should be done.

However, the intention is to have the data always flowing from top to bottom in one direction, or unidirectionally as many refer to it, like a waterfall. Top-level components can pass data to their child components in one direction; if you want to update the data of a child component, you'll need to update it in the parent and pass it down again. Let's see how this works.

Here's how we can render the Greeting component that we created:

ReactDOM.render(


<Greeting name="Joe"/>,document.getElementById('root')
);

We added the Greeting component as a <Greeting/> element, and we also passed a parameter, called name, with the value Joe.

Save the document, refresh the browser, and see what it does.

The properties or the props in the components are the way we pass data to components and the parameter name is accessible from the Greeting component as this.props.name.

If we want to render many instances of the Greeting component, for example, we need a parent component that can render multiple child Greeting components:

class App extends React.Component {
constructor(props) {
super

(props);
console.log(props); // array of three children components
}
render() {


return (
<div>
{this.props.children} // render the children components


</div>)
}
}
const Greeting = ({ name}) => (
<div>{name}

</div>
)

Here the parent App component wraps and renders many children Greeting components:

ReactDOM.render(
<App>
<Greeting

name="Joe"/>
<Greeting name="Tony"/>
<Greeting name="Harry"/>


</App>
,document.getElementById('root'))

The React's state

Besides the flexibility of decoupling your development with reusable components, another powerful feature of React is in its component state. Every React component comes with an internal state.

If the state of a component changes, the render method will fire and re-render the component. One thing that's very different from other frameworks is that React will re-render only the changes and nothing else.

Also, it does so in a very efficient way, and it doesn't have to touch the DOM for searching before making any changes. What is happening in the back is that JSX actually represents the DOM in JavaScript, and all the updates are happening on the script site in memory first; then it will do some checks, then batch the changes and, finally, it will commit them to the DOM.

Adding state to a stateless function component

What is a state in React's components?

Components can be declared as pure JavaScript functions, and they are called Stateless, such as this one:

function Greeting({ hello }) {
return <div>{hello}</div>;
}

Alternatively, you can write the preceding code as an ES6 arrow function:

const Greeting = ({ hello }) => (
<div>{hello}</div>
)

Every component can hold internal encapsulated data, and we call this a state of the component. If you want to add a state to a stateless component, you should define it as an ES6 class.

Before adding an ES6 constructor and super methods to the stateless components, we can overview what an ES6 constructor is and what the super method does in ES6 classes.

In ES6, we can create a class, such as the following:

class Component {
constructor(props){
this.props = props;
}
}

The names of the parameters and methods are named as React's once. This is not from the React library source.

We can add a method called render, as mentioned in the following code:

class Component {
constructor(props){
this.props = props;
},

render() {
return this.props;
}
}

In an ES6 classical way, we can extend the base/parent class Component:

class Greeting extends Component{
constructor(name){
super(name) // passing the value to the parent class which gets
assigned to the props
console.log(super.render()) // with the super method we can call the functions from the parent class
console.log(this.render()) // or directly as this.render
}
}

In a classical way, we can create a new instance of the Greeting class, like this:

let greet = new Greeting('Joe');
console.log(greet.render()) // have an access to the parent method render

We can create a render() method with the same name in the child class:

class Greeting extends Component{
constructor(name){
super(name)
this.name = name;
console.log(super.render())
}
render() {
return 'Hi ' + this.name;
}
}
let greet = new Greeting('Harry');
console.log(greet.render()) // child overrides the parent

Inheritance versus composition

Facebook and the React community encourage the use of composition over classical inheritance. It can be said that any other pattern to reuse code is considered anti-pattern in React. A lot more can be said about that, but React has been heavily used in production at Facebook without using inheritance for sure. In such a large-scale application, the advantage of building independent pieces and combining them to form a complex functionally is what made React so popular. Components can be moved around, organized by common functionality; they can be well tested and refactored without much of a risk of breaking other parts of the system.

Extending from the React.Component:

class Greeting extends 

React.Component {
constructor(props){ // ES6 class constructor
super(props) // ES6
}
render() {
return <h1>Hello, {this.props.name}</h1>; //local
}
}

Instead of creating a new Greeting instance, we are adding it as a <Greeting/> tag:

ReactDOM.render(
<Greeting name="Johny"/>,document.getElementById('root'))

Some components can be strictly presentational; others may not know who can be their children components in advance. Many namings later came from the community, Smart and Dumb components, Containers and Components, functional and classical.

How React is used it's all up to the developers, the team, and the organization. It's a very powerful and unopinionated library and can be used along with other frameworks.

Adding a state to a component

One can say that React barely has any API. It has about 9-10 methods and that is all we get.

The State is one of the main APIs in React. The best place to define the initial state of a component is in the class constructor.

Create a class component and initialize its state:

class Button extends React.Component 

{
constructor(props){
super(props)
this.state = {text: 'OFF'}
this.handleClick = this.handleClick.bind(this)
}
handleClick(){
this.state.text === 'OFF' ? this.setState({text: 'ON'}) :
this.setState({text: 'OFF'})
}
render() {
return (
<button type="button" onClick={this.handleClick}>
{this.state.text}</button>)
}
}

This is an example of an internal state of a button that changes its text on user click (it toggles between ON and OFF).

A few things are required for that component to maintain a state:

  • We defined the state in the class constructor method. This is the first entry of the class that will be executed. We can think of that definition as the default state of the component.
  • We created a handleClick() method and bound it to the this class in the class constructor. It will fire when the user clicks on the button.
  • In the handleClick method, we used one of the React's APIs--this.setState--and based on the comparison of the current state, we set our new state.
React is built with functional purity in mind, and if you set the state directly as this.state.text = 'OFF', the component will not re-render.
  • In the button, we added a simple JavaScript event--onClick. Here, React has a slightly different syntax on events; it uses camel case. onClick becomes onClick.

The second form of the setState() method is passing a function in the state instead of an object; in the callback, we get the previous state:

this.setState
(function(prevState) {
return {
text: prevState.text === 'OFF' ? 'ON' : 'OFF'
};
});

Alternatively, an arrow ES6 function can be used:

this.setState((prevState) => ({
text: prevState.text === 'OFF' ? 'ON' : 'OFF'
}));

Other React methods.

A few other important React APIs are the component life cycles methods:

class Greeting extends React.Component {
constructor(props) {
super(props);
console.log('constructor');
}
componentDidMount() {
console.log('componentDidMount');
}
componentWillUnmount() {
console.log('componentWillUnmount');
}
render() {
return (
<div>
<p>{this.props.name}</p>
{this.props.children}
</div>)
}
}

Let's test these life cycle methods by adding a simple button to the body of the HTML:

<button onclick="umountComponent()">Unmount</button>

Also, we can define that function in the JavaScript tag, as follows:

function umountComponent(){
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}

When the app is loaded, the order of the events is as follows:

  1. Constructor method is called.
  2. componentDidMount() is called when the component is mounted to a parent or directly to the DOM.
  3. componentWillUnmount() is called just before the component will unmount from the DOM.

A simple use of these events can be as shown:

  1. Constructor: Initialize the component's default state in the constructor.
  2. componentDidMount(): The component is ready. You can perform any actions at that point.
  3. componentWillUnmount(): The component will be detached from the DOM; here, you can clear any resources that may cause memory leaks.

Meteor with React

Starting from the client, let's create our simple app:

  1. Delete all the content of the client folder.
  2. Create the index.html file with the following content:
<head>
<title>Timer</title>
</head>
<body>
<div id="root"></div>
</body>
  1. Meteor will take care of the missing HTML tags. It will compile it as a template rather than serve it as an HTML document.
  2. It will throw an error if you try to add the <!DOCTYPE html> and html> tags:
      While processing files with <cdpcomment data-comment-id="2521" 
data-comment-text="Sounds incomplete. ">">templating-compiler
(for target web.browser):

client/index.html:1: Expected one of: <body>, <head>, <template>.

Since we are using only React, we don't need to have the blaze-html-templates package installed.

Adding and removing atmosphere packages in Meteor

There are two ways to add and remove atmosphere packages in Meteor: directly add it in .meteor/packages or use the meteor add or meteor remove commands.

We will remove blaze and jquery for this example:

>> meteor remove blaze-html-templates

This will result in the following terminal:

blaze-html-templates removed from your project
caching-compiler removed from your project
caching-html-compiler removed from your project
templating removed from your project
templating-compiler removed from your project
templating-runtime removed from your project
templating-tools removed from your project

Now if you run your app, you will note that nothing is loaded. You'll need to install the html-static package to let Meteor load a static HTML file.

You can install it with the following command:

>> meteor add static-html

It will add all the needed dependencies:

caching-compiler added, version 1.1.8
caching-html-compiler added, version 1.0.7
static-html added, version 1.1.13
templating-tools added, version 1.0.5
Remove Jquery by deleting it in the package file (.meteor/packages)
[email protected] # Helpful client-side library

You can add and remove packages even if the server is running. Meteor will restart it automatically when a package is removed or added.

The next step is directories and files:

  • Create index.js in the root of the client folder. This will be our start up file where we can import and render the rest of the components.
  • Create the components folder. For a small demo app like this, we can keep the components in a folder components.
  • In folder components, create a Timer.js file, you can use and also Timer.jsx; Meteor will transpile JSX into JavaScript by default. Both ways are fine.

Install react and react-dom with npm:

        >> npm install react --save
>> npm install react-dom --save

In the Timer.js file, let's define our component:

import React from 'react';
import ReactDOM from 'react-dom';
class Timer extends React.Component {
constructor(props) {
super(props);
}
render() {
return (<div>
<p>Timer</p>
</div> )
}
}
export default Timer;

It is a class component that will display the time pushed in real time from the server. The only difference between preceding examples and this one, is that we exported that component to use it in other components.

Now, let's import and render it in the index.js file:

import React from 'react';
import {render} from 'react-dom';
import Timer from './components/Timer'
render(<Timer/>, document.getElementById('root'))

First, let's create the functionality of the app on the client; then we can start pushing the data from the server.

Initialize the state in the constructor and set a new state every second:

constructor(props) {
super(props);
this.state = {time: 0};
}
componentDidMount() {
setInterval(() => this.setState({time: new Date().toLocaleString()}), 1000);
}

Also, we have the following code in the render() method:

render() {
return (<div>Time :
{this.state.time}
</div>)
}

When the component mounts, we set a new state every second. This will force the component to re-render and display the latest state.

To exercise the idea of the state and the props, we can now create a child component that the Timer component will render and pass its state as prop.

Create a child component in the components folder, called TimerDisplay:

class TimerDisplay 
extends React.Component {
constructor(props) {
super(props);
}
render() {
return
(
<div>Time : {this.props.time}</div>
)
}
}
export default TimerDisplay;

The TimerDisplay will receive and render the data through the props.time. It can be a stateless presentational component, and it can be defined as a function instead of a class:

import TimerDisplay from './TimerDisplay';
class Timer extends React.Component {
constructor() {
super();
this.state = {time: new Date().toLocaleString()};
}
componentDidMount() {
setInterval(() => this.setState({time: new Date().toLocaleString()}), 1000);
}
render()
{
return (
<div>
<TimerDisplay time={this.state.time}/>
</div>)
}
}
export default Timer;

Here, the parent component, Timer, re-renders every time its state changes, and then renders the child component, TimerDisplay, by passing its state as props.

What will happen if we add another TimerDisplay, but this time with a static value:

render() 
{
return (
<div>
<TimerDisplay time={this.state.time}/>
<TimerDisplay time='today'/>
</div>)
}

To see the power of React in action, open the console in Chrome and press the Esc key then go to Rendering and click on the checkbox Paint Flashing. You will note that even if we rendered both TimerDisplay, only the one that has its value changed is re-rendering.

Integrating React with Meteor's reactive data system

In the preceding example, the data passed to the child component via props was generated in the parent component itself in the setInterval function. In order to render React components on Meteor's data change, we need to create a component container.

The steps are as follows:

  1. Add react-meteor-data and react-addons-pure-render-mixin npm packages using the following command:
        >> npm install react-meteor-data --save
>> npm install react-addons-pure-render-mixin --save
  1. Import createContainer into the Timer.js component:
        import React from 'react';
import TimerDisplay from './TimerDisplay';
import { createContainer } from 'react-meteor-data';
  1. Export the container function instead of the component:
        export default createContainer(() => {
return {
time: Time.find().fetch()
};
},
Timer);

The first parameter is a callback function that returns the result as an object named time, and the second parameter is the Timer component. The way it works is that on any changes in the browser database Minimongo, the data will be fetched and passed to the component as props (time in our case).

To break this down, refer to this:

  1. We defined Time as a MongoDB collection.
  2. find() is a MongoDB method to query records (records are called documents in MongoDB) from the collection. If there is no query specified, will return cursor for the first 20 records by default.
  3. Adding the fetch() method will return the documents as an array.

Meteor allows us to create a collection directly in the code:

Time = new Mongo.Collection('time');

Explore MongoDB in the Meteor shell

You can access all the application data with the Meteor MongoDB shell.

Open another terminal, cd to the app directory, and start the MongoDB shell.

The app should be running at the same time.
>> meteor mongo

Show all the databases in MongoDB:

>> show dbs

The default database for the app is named meteor; switch to the meteor database:

>> use meteor

Display all collections in the database:

>>show collections

The show collections command will not return any results.

The reason is that we created a collection on the client side only. This collection exists in Minimongo, and it is in the browser's memory. If we refresh the page, the collection and the data will be gone.

In order to create the same collection and persist data into MongoDB, we have to execute the exact same Time = new Mongo.Collection('time'); on the server. As soon as we do that, both the collections will be in sync and the data will be persisted into the database.

For now, we don't want to save the timer data on the server; we only want to take advantage of the Meteor server to client real-time communication.

Publishing and Subscribing

The way we can get real-time updates from the server is through the Publishing and Subscribing messaging pattern of Meteor.

The server publishes an event and on the other side, the client subscribes to that event and listens to data changes.

On the server, in our case in the server/main.js file, we can publish an event in a very simple way:

  import { Meteor } from 'meteor/meteor';
Meteor.startup(() => {
// code to run on server at startup
Meteor.publish('time', function() {
...
});
});

The publish function takes two arguments: name of the collection and a callback function that will be executed on each subscription from the client.

On the client, we can subscribe to it as shown here:

Meteor.subscribe('time');

All updates from the server will push an update to Minimongo collection on the client. The subscriber can also pass a parameter as a second argument and/or have subscription handlers as callback functions:

Meteor.subscribe('time', id);

Also, the subscription handlers look like this:

Meteor.subscribe('time', {

//called when data is availble in Minimongo
onReady: function() {
},
// called on error
onError: function() {
},
// called when the subcription is stopped.
onStop: function () {
}
});

Here's the server-side code of the subscription:

import { Meteor } from 'meteor/meteor';
import { Random } from 'meteor/random';

Meteor.startup(() => {
// code to run on server

at startup
Meteor.publish('time', function() {
let self = this;
const newTime = () => {
let

id = Random.id();
let time = {
time: new Date().toString()
}
self.added

('time', id, time);
}
Meteor.setInterval(function() {
newTime();
}, 1000);
});

What we did is covered in the following points:

  1. We created a publisher with a collection called time.
  2. We used one of the Meteor's timer functions, setInteval(), to call a newTime() function every 1000 milliseconds or one second.
  3. In the newTime() function, we created an ID of the document and the document as the current time.
  4. We then called the added method, which will notify the subscriber that we added a new document to the time collection.

The client-side code is as follows:

Time = new Mongo.Collection('time');
class Timer extends React.Component {
constructor(props)

{
super(props);
Meteor.subscribe('time');
}
render() {
return

<TimerDisplay time={this.props.time}/>;
}
}
export default createContainer(() => {
return

{
time: Time.find().fetch()
};
}, Timer);

This is what we did here:

  1. We created a collection called time. The collection is created locally in Minimongo. If we don't create the same collection on the server, it will be only in the browser's memory.
  2. In the class constructor, we subscribed to the dataset time published on the server.
  3. In the render method, we rendered the TimerDisplay component as we passed time as props.
  4. At the end, we created a Meteor React container that listens to data changes, fetches the latest data from the collection, and passes it down to the component as props.

Improvements in the current code

In both the server and the client, we didn't provide an unsubscribing mechanism. For example, if you close the browser, the server will continue calling the newTime() function indefinitely.

Meteor.publish() provides some other useful APIs. When the client is subscribed for the first time, we can send a notification that the initial dataset was sent using this.ready(). This will call the onReady callback on the client.

We can also unsubscribe the client with this.stop() or listen whether the client subscription is stopped.

The publisher on the server:

Meteor.publish('time', function() {
let self = this;
self.added('time', id, time); // notify if record is added to the collection time
let interval = Meteor.setInterval(function() {
newTime();
}, 1000);
this.ready(); //

notify that the initial dataset was sent
self.onStop(function () {
self.stop()
console.log('stopped called')
Meteor.clearInterval(interval); // clear the interval if the the client unsubscribed
});
});

The subscriber on the client:

const handle = Meteor.subscribe("time", {
onReady: function() {
// fires when this.ready() called on the server.
}
});
handle.stop() // will call onStop(callback) on the server.
handle.ready() // returns true if the server called ready()

The clientTimer component:

Time = new Mongo.Collection('time');
const handle = Meteor.subscribe('time');
class Timer extends React.Component {
constructor(props) {
super(props);
}
shouldComponentUpdate() {
return this.props.handle.ready() && this.props.time.length > 0;
}

componentWillUnmount() {
this.props.handle.stop();
}
render() {
return <TimerDisplay time = { this.props.time }/>;
}
}
export default createContainer(() => {
return {
time: Time.find().fetch(), handle: handle
};
}, Timer);

Here, we passed the subscriber's handle as a prop to the component and in shouldComponentUpdate, we check whether the publisher sent the first dataset and whether it has any records. If yes, it will render the imerDisplay component.

When we unmount the component, we send a notification to the that we stopped the subscription.

lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image