Meteor Course Notes (2/4)

React-Router

Install

1
meteor npm install --save react-router-dom

Set up Routes

Imports

1
2
import { Router, Route, Switch } from 'react-router-dom';
import { createBrowserHistory } from "history";

Define routes

1
2
3
4
5
6
7
8
9
10
11
const browserHistory = createBrowserHistory();
const routes = (
<Router history={browserHistory}>
<Switch>
<Route path={"/"} exact component={Login} />
<Route path={"/signup"} component={Signup} />
<Route path={"/links"} component={Link} />
<Route component={NotFound} />
</Switch>
</Router>
);

<Switch> Renders the first child <Route> or <Redirect> that matches the location.

Use routes at startup

1
2
3
Meteor.startup(() => {
ReactDOM.render(routes, document.getElementById('app'))
});

Navigation (Code Way)

1
2
3
4
browserHistory.push('/some-location');
browserHistory.goBack();
browserHistory.go(2); // go forward 2 pages
browserHistory.go(-2); // go back 2 pages

Navigation (Component Way)

Import

1
import { Link } from 'react-router-dom'

Use

1
<Link to={"/signup"}>Have an account?</Link>

https://stackoverflow.com/questions/42701129/how-to-push-to-history-in-react-router-v4

State

Create a state at constructor

1
2
3
4
5
6
export default class Component extends React.Component {
constructor (props) {
super(props);
this.state = {}
}
}

Update state

1
<button onClick={ function () { this.setState({count: this.state.count + 1}) }.bind(this) }>+1</button>

Ref

1
2
<input type="email" ref={"email"}>
<input type="password" ref={"password"}/>
1
2
let email = this.refs.email.value;
let password = this.refs.password.value;

Authentication & Validation

Install

1
2
meteor add accounts-password
meteor npm install --save bcrypt

Use

1
Accounts.createUser({email, password}, (err) => {...});

Test (Via Chrome)

1
require('meteor/meteor').Meteor.userId()

Logout Test

1
2
require('meteor/accounts-base').Accounts.logout()
require('meteor/meteor').Meteor.userId()

Check User on MongoDB

1
db.users.find()

Login

1
Meteor.loginWithPassword({email}, password, (err) => {  })

Logout

1
Accounts.logout();

Contingent Redirect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const unauthenticatedPages = ['/', '/signup'];
const authenticatedPages = ['/links'];

Tracker.autorun(() => {
const isAuthenticated = !!Meteor.userId();
const isUnauthenticated = !!!Meteor.userId();
const pathname = history.location.pathname;
const isUnauthenticatedPage = unauthenticatedPages.includes(pathname);
const isAuthenticatedPage = authenticatedPages.includes(pathname);
if (isUnauthenticatedPage && isAuthenticated) {
history.replace('/links');
}
if (isAuthenticatedPage && isUnauthenticated) {
history.replace('/');
}
});

Public & Private Routes

An example of redirect to keep component private.

1
2
3
4
5
componentDidMount() {
if (Meteor.userId()) {
history.replace('/links');
}
}

Error Handling

1
2
3
4
5
6
7
Accounts.createUser({email, password}, (err) => {
if (err) {
this.setState( {error: err.reason} )
} else {
this.setState( {error: ''} )
}
});

noValidate

Disables the browser email validation and the like.

1
<form noValidate></form>

ErrorCatching

Generic Error

1
2
3
4
5
try {
throw new Error('error');
} catch (e) {
console.log(e.message);
}

Meteor Error

1
2
3
4
5
try {
throw new Meteor.Error(400, 'error reason');
} catch (e) {
console.log(e.reason);
}

Schema Validation

Install

1
meteor npm install simpl-schema

Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let petSchema = new SimpleSchema({
name: {
type: String,
min: 1,
max: 200
},
age: {
type: Number,
min: 0
},
contactNumber: {
type: String,
regEx: SimpleSchema.RegEx.Phone,
}
});

petSchema.validate({
name: "Troy",
age: 10,
contactNumber: "5197812577"
});

Use (In Server)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Meteor.startup(() => {
Accounts.validateNewUser((user) => {
const email = user.emails[0].address;
try {
new SimpleSchema({}).validate({
email: {
type: String,
regEx: SimpleSchema.RegEx.Email
}
}).validate({ email });
} catch (e) {
throw new Meteor.Error(400, e.message);
}
return true;
})
});

Password Validation

Since the password is hashed, we are unable to check its vadidity, e.g., is it longer than 8 characters? Thus, we need to add a validation in the front-end by:

1
2
3
if (password.length < 9) {
return this.setState({ error: 'Password must be more than 8 characters long.'})
}

Re-render for Specific Component

Use componentDidMount and componentWillUnmount.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export default class LinksList extends React.Component {
constructor(props) {
super(props);
this.state = {
links: []
};
}

componentDidMount() {
this.linksTracker = Tracker.autorun(() => {
let links = Links.find().fetch();
this.setState({ links }); // refresh the page
});
}

componentWillUnmount() {
this.linksTracker.stop();
}
}

Remove autopublish package

It is only used for prototyping, so should be removed.

1
meteor remove autopublish

Run Code Server-Only

1
2
3
4
5
6
if (Meteor.isServer) {
...
}
if (Meteor.isClient) {
...
}

Publication & Subscription

On Server Side

1
2
3
4
5
if (Meteor.isServer) {
Meteor.publish('links', () => {
return Links.find({url: 'some-specific-url'});
})
}

*User-spcific Query

1
2
3
4
5
if (Meteor.isServer) {
Meteor.publish('links', function () {
return Links.find({userId: this.userId});
})
}

On Client Side

1
2
Meteor.subscribe('links');
let links = Links.find().fetch(); // shows 1 only

Server Methods

Define

1
2
3
4
5
Meteor.methods({
greetUser (name = 'user') {
console.log('Greet ' + name);
}
});

Use

1
Meteor.call('greetUser', 'Mike', (err, res) => console.log(res));

res is the return value.

Remove insecure

1
meteor remove insecure

Simple Schema: Throw Custom Error Message

1
2
3
4
5
6
7
8
9
new SimplSchema({
url: {
type: String,
regEx: SimplSchema.RegEx.Url,
label: 'Your Link'
}
}).validate({
url
});

Note that the label field will replace the default message “the url” by “Your Link”.

Simple Schema: Throw Costom Error

1
2
3
4
import SimpleSchema from 'simpl-schema';
SimpleSchema.defineValidationErrorTransform(err => {
return new Meteor.Error(400, err.message);
});

Node HTTP and Connect

Import WebApp

1
import { WebApp } from 'meteor/webapp';

Add Midddleware

1
2
3
4
WebApp.connectHandlers.use((req, res, next) => {
console.log('this is from my custom middleware');
next();
});

HTTP Response Crash Course

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
WebApp.connectHandlers.use((req, res, next) => {
console.log('req.url=', req.url);
console.log('req.method=', req.method);
console.log('req.headers=', req.headers);
console.log('req.query=', req.query);

// Set HTTP status code
res.statusCode = 200;
// Set HTTP headers
res.setHeader('my-custom-header', 'Jude is here!');

// Set HTTP body
res.write('<h1>judegao</h1>');

// End HTTP Request
res.end();
next();
});

Middleware Redirect

1
2
3
4
5
WebApp.connectHandlers.use((req, res, next) => {
res.statusCode = 302;
res.setHeader('Location', 'http://www.judegao.com');
res.end();
});

Short ID in Document

Install shortid

1
meteor npm install shortid --save

Use

1
2
3
4
5
6
import shortid from 'shortid';
Links.insert({
_id: shortid.generate(),
url,
userId: this.userId
});