An introduction Preact with basic authentication

Ashok Vishwakarma
Ashok Vishwakarma

May 07, 2018 • 6 min read

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

  1. Tiny (3Kb) in size
  2. Performance in Mobile Web when building PWAs
  3. Easy to swap from React to Preact using preact-compact

Performance comparison

TodoMVC Benchamrk

To set-up Git Submodule you need to create .gitmodules file in your root directory.

What is Preact?

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)

Getting started with Preact

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)

Using preact-cli

Advantages of using preact-cli

  • Fully automatic code splitting for routes
  • Transparently code-split any component with an async! prefix
  • Auto-generated Service Workers for offline caching powered by sw-precache
  • PRPL pattern support for efficient loading
  • Zero-configuration pre-rendering/server-side rendering hydration
  • Support for CSS Modules, LESS, Sass, Stylus; with Autoprefixer
  • Monitor your bundle/chunk sizes with built-in tracking
  • Automatic app mounting, debug helpers & Hot Module Replacement

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.

Official templates

  • default — Default template with all features.
  • material — material template using preact-material-components
  • simple — The simplest possible preact setup in a single file
  • widget — Template for a widget to be embedded in another website.

To more about preact-cli visit it on Github

Auth Flow

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.

Directory structure and files

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.

The login and redirection

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.

References

GitHub - preactjs/preact: ⚛️ Fast 3kB React alternative with the same modern API. Components & Virtual DOM.

⚛️ Fast 3kB React alternative with the same modern API. Components & Virtual DOM. - GitHub - preactjs/preact: ⚛️ Fast 3kB React…

github.com

GitHub - preactjs/preact-cli: 😺 Your next Preact PWA starts in 30 seconds.

😺 Your next Preact PWA starts in 30 seconds. Contribute to preactjs/preact-cli development by creating an account on GitHub.

github.com

GitHub - preactjs/preact-router: URL router for Preact.

:earth_americas: URL router for Preact. Contribute to preactjs/preact-router development by creating an account on GitHub.

github.com

GitHub - developit/preact-redux: Preact integration for Redux (no shim needed!)

:loop: Preact integration for Redux (no shim needed!) - GitHub - developit/preact-redux: Preact integration for Redux (no shim needed!)

github.com

Please feel free to comment your suggestions and don’t forget to share with your friends and teammates.

Happy coding :)

Ashok Vishwakarma

Ashok Vishwakarma

Google Develover Expert — WebTechnologies and Angular | Principal Architect at Naukri.com | Entrepreneur | TechEnthusiast | Speaker