I have started using Preact right after watching a talk by Jason Miller at a conference held by Algolia and I was amazed for three basic reasons
To set-up Git Submodule you need to create .gitmodules
file in your root directory.
According to its website its A different kind of library.
It is an alternative to React more concerned about performance. It uses the Virtual DOM like React but very close to it. It also uses real event handlers to handle events and it diffs the Virtual DOM against the DOM itself.
Preact is getting very popular on Github — more than 8.3K stars backed and sponsored by many known people and companies.
Preact is also being used by top companies like Groupon, Uber, The New York Times, Housing and Treebo etc. (see all here)
To get started with Preact you can either use it’s CLI (preact-cli) or take any boilerplate from the Github (preact-boilerplate — it’s not using the preact-cli)
Advantages of using preact-cli
Node V6.x or higher is required to use preact-cli
// Install using npm
npm install -g preact-cli
// Create your first project using
// preact create
preact create default app
// create options
//
// -name The application name.
// --cwd A directory to use instead of $PWD.
// --force Force option to create the directory for the new app [boolean] [default: false]
// --yarn Installs dependencies with yarn. [boolean] [default: false]
// --git Initialize version control using git. [boolean] [default: false]
// --install Installs dependencies. [boolean] [default: true]
// Start in watch more
preact watch
// watch options
//
// --cwd A directory to use instead of $PWD. [string] [default: .]
// --src Entry file (index.js) [string] [default: "src"]
// --port, -p Port to start a server on [string] [default: "8080"]
// --host, Hostname to start a server on [string] [default: "https://www.linkedin.com/redir/invalid-link-page?url=0%2e0%2e0%2e0"]
// --https Use HTTPS? [boolean] [default: false]
// --prerender Pre-render static app content on initial build [boolean] [default: false]
// build your preact app
preact build
// build options
//
// --src Entry file (index.js). [string] [default: "src"]
// --dest Directory root for output. [string] [default: "build"]
// --prerenderUrls Path to pre-render routes configuration. [string] [default: "prerender-urls.json"]
// --template Path to template file. [string] [default: none]
// --service-worker Add a service worker to application. [boolean] [default: true]
// --production, -p Create a minified production build. [boolean] [default: true]
// --no-prerender Disable pre-render of static app content. [boolean] [default: false]
// --clean Clear output directory before building. [boolean] [default: true]
// --json Generate build statistics for analysis. [boolean] [default: false]
// --config, -c Path to custom CLI config.
To more about preact-cli visit it on Github
When building a web application Auth Flow is very important and it becomes more important when using any Front-End library like React, Angular or Preact. I was using preact
along with preact-router
and preact-redux
and the challenge was where to put what.
There are routes
, actions
, reducers
and components
in my web application.
In a real app scenario, your Auth Flow contains Login, Register, Forgot Password, and Reset Password.
To manage the routes I was using preact-router
and redux
to manage the store.
My web application was using an auth service developed in-house using KoaJS and JWT tokens.
Generally, people have their actions, reducers and component in three different directories with the same name and import these where they required, I followed a different approach by putting my action, reducer and component into one common directory named as my component like
componetns/
app/
index.js // my app component
action.js // my app actions
reducer.js // my app reducer
On the top level, I also have one store.js and reducer.js to combine all reducers and make the store out of it.
So my final directory structure was looking something like this'
assets - contains my static assets like scss, img, fonts etc.
components
app
auth - all the components like login, register along with action and reducer
common - contains common components like header and footer
error
home
profile
utility - some utilities like session, helpers etc.
index.js - main preact component
reducers.js - all reducers combined
store.js - app store
The main index.js
import 'babel-polyfill';
import { Provider } from 'preact-redux';
import store from './store';
import './assets/style/main';
import App from './components/app';
export default () => {
return (
<Provider store={store}>
<App />
</Provider>
)
}
The reducers.js
import {combineReducers} from 'redux';
import app from './components/app/reducer';
import auth from './components/auth/reducer';
export default combineReducers({
app,
auth
});
The store.js
import {createStore, applyMiddleware} from 'redux';
import thunkMiddleware from 'redux-thunk';
import reducers from './reducers';
// a cusotm middleware to track all the actions at one place
const myMiddleware = store => next => action => {
next(action);
}
const Store = createStore(reducers, applyMiddleware(
myMiddleware,
thunkMiddleware
));
export default Store;
I have created a session utility in utility/session.js
to manage my session in local or session storage.
import Storage from './storage';
class Session{
constructor(){
this._token = false,
this._user = false;
this.storage = new Storage('sessionStorage');
return {
check: this.check.bind(this),
user: this.user.bind(this),
token: this.token.bind(this),
set: this.set.bind(this),
logout: this.logout.bind(this)
}
}
check(){
if(!this._token) this._token = this.storage.get("_token");
return (this._token !== "" && this._token !== null && typeof this._token !== 'undefined');
}
user(){
if(!this._user) this._user = this.storage.get("_user");
return this._user;
}
token(){
if(!this._token) this._token = this.storage.get("_token");
return this._token;
}
set(key, value){
return this.storage.set(key, value);
}
logout(){
this.storage.remove('_user');
this.storage.remove('_token');
}
}
export default new Session();
Protecting the routes There were two different types of routes in my web application one is public — available to log in and others are protected only available after login. To set up the same I have used one identifier in my Route implementation auth={true}
// part of my app/index.js
handleRoute = e => {
this.props.changeRoute(e.url, e.previous, (e.current.attributes)?e.current.attributes.auth:false);
}
componentWillMount(){
this.props.dispatch(checkLogin());
}
render() {
return (
<div className="app">
<Router onChange={this.handleRoute}>
<Auth path="/auth/login" current={this.props.app.current} />
<Auth path="/auth/register" current={this.props.app.current}/>
<Home path="/" auth={true} />
<_404 default />
</Router>
</div>
);
}
In my changeRoute
action the loggedIn
status was being checked using session utility and based on that I was redirecting my application for initial load
// part of my app/reducer.js
case CHANGE_ROUTE:
let appState = Object.assign({}, state);
appState.current = action.payload.current;
appState.previous = action.payload.previous;
if(appState.current.auth && !loggedIn) {
redirect('/auth/login', true);
}
if((appState.current.url === '/auth/login' || appState.current.url === '/auth/register') && loggedIn) {
redirect('/', true);
}
return appState;
break;
This has solved one very important problem, if not logged in and try to access the protected routes, the user will be redirected to /auth/login
URL and if logged in and tried to access /auth/login
or /auth/register
they will be redirected to the home URL.
After user logged in successfully the application needs to redirect the user to the home (/) URL, PS: I have tried using route method from preact-router but it was throwing some error due to a bug in preact-router.
See the full auth/reducer.js
import {
LOGIN_SUCCESS,
LOGIN_FAILED,
LOGOUT,
CHECK_LOGIN
} from './action';
import session from '../../utility/session';
import {redirect, reload} from '../../utility/helper';
const initialState = {
loggedIn: false,
user: false,
token: false
};
const Reducer = (state = initialState, action) => {
switch (action.type) {
case LOGIN_SUCCESS:
if(action.payload.token){
session.set('_token', action.payload.token);
}
if(action.payload.user){
session.set('_user', action.payload.user);
}
reload();
return state;
break;
case CHECK_LOGIN:
let checkState = Object.assign({}, state);
if(session.check()){
checkState.loggedIn = true;
checkState.token = session.token();
checkState.user = session.user();
}else{
checkState = initialState;
}
return checkState;
break;
case LOGOUT:
let logoutState = Object.assign({}, state);
session.logout();
logoutState.user = false;
logoutState.token = false;
reload();
return logoutState;
break;
default:
return state;
break;
}
}
export default Reducer;
The above reducer is used by auth/action.js
file, see the full auth/action.js
import {request} from '../../utility/helper';
import {URL} from '../../utility/contatnts';
export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_FAILED = 'LOGIN_FAILED';
export const REGISTER = 'REGISTER';
export const LOGOUT = 'LOGOUT';
export const CHECK_LOGIN = 'CHECK_LOGIN';
export const loginSuccess = data => {
return {
type: LOGIN_SUCCESS,
payload: data
}
}
export const loginFailed = data => {
return {
type: LOGIN_FAILED,
payload: data
}
}
export const register = (data) => {
return {
type: LOGIN,
payload: data
}
}
export const logout = () => {
return {
type: LOGOUT,
payload: null
}
}
export const checkLogin = () => {
return {
type: CHECK_LOGIN,
payload: null
}
}
export const login = credentials => {
return async dispatch => {
let data = await request(URL.AUTH.LOGIN, {
body: JSON.stringify(credentials),
method: 'POST'
});
if(data.type === "success") dispatch(loginSuccess(data));
if(data.type === "error") dispatch(loginFailed(data));
}
}
I have used JavaScript fetch method in my request utility to perform Ajax requests
// request method in utlity/helper.js
const _default_fetch_params = {
headers: {
'content-type': 'application/json'
},
method: 'GET',
mode: 'cors',
redirect: 'follow'
};
if(session.check()) {
_default_fetch_params.headers['authorization'] = `Bearer ${session.token()}`;
}
export const request = (url, param) => {
let parmas = Object.assign({}, _default_fetch_params, param);
return fetch(url, parmas).then( res => res.json());
}
Hope after reading this you will be able to start using Preact in your web applications or PWAs, let me know your implementation of Auth Flow in Preact.
Please feel free to comment your suggestions and don’t forget to share with your friends and teammates.
Happy coding :)
Google Develover Expert — WebTechnologies and Angular | Principal Architect at Naukri.com | Entrepreneur | TechEnthusiast | Speaker