React deploy stages with Netlify CI/CD

In this article we go over a recipe for defining deployment stages (development, staging, production, etc.) with a React application. We are going to use the Netlify CI/CD service. We are going to see how React can consume environmental variables, and how we can ‘inject’ these at build-time when we are using Netlify service.

The scenario we’ll go through is the following:

  • We have simple React application created using create-react-app command.
  • The app will have to know whether is running in test, development, or production environment.
  • The app will also have the following deploy stages: development, staging, production.

By deploy stages we mean a set of settings for the app to communicate with the external world. For example a per-stage setting is a backend URL to connect to, or a remote database. Or maybe we want to enable some kind of statistics in production only, and not when we run the app in development or tests.

Before continuing we should make clear the following:

DO NOT expose any security-sensitive information in your environment variables (secret API keys, etc.). Environment variables are injected into the code base by create-react-app build scripts, and the source code of web apps is exposed to the user. Even if we use minification, the sensitive information will still be there, ready to be discovered.

NODE_ENV

The create-react-app exposes the context of build/run operation to our React app via the NODE_ENV environment variable. Actually we cannot even override it.

When we run yarn test its value is test. When we run yarn start its value is development. When we build with yarn build it’s value is production.

And we can consume this variable in our React app by reading process.env.NODE_ENV.

For example:

1
<h6>Environment: {process.env.NODE_ENV}</h6>

Thus we can have our app behave differently when it is running on tests, on development and on production.

Other environment variables

We can define other custom environment variables to be consumed in our application. They have to start with the prefix REACT_APP_. This is another security measure to avoid exposing sensitive environment variables.

For example we can have REACT_APP_STAGE, REACT_APP_BACKEND_URL, etc.

Then our React app consumes our variables via process.env.REACT_APP_STAGE, process.env.REACT_APP_BACKEND_URL, etc, same as above.

Netlify: Build per branch

Now that we know how to consume environment variables in our React app we have to configure them in our CI/CD service and have different values for different stage builds.

First step is to configure Netlify to create separate builds for our development and staging branches. Netlify, by default, builds production by monitoring changes in master branch.

Go to Deploy Settings > Build & Deploy > Continuous Deployment > Deploy Context > Edit Settings. There we can define which branches will trigger builds upon changes.

This way Netlify will build each time a change is pushed to each of those branches, and will deploy it in a random unique URL for previewing.

Environment variables per build

Having build per branch in place, we now need to define custom environment variables for each stage.

With Netlify it is recommended to have a netlify.toml file in our root folder to configure our builds per branch. Here’s a sample file that define environment variables per branch/stage:

netlify.toml

1
2
3
4
5
6
7
8
[context.development.environment]
REACT_APP_STAGE = "development"

[context.staging.environment]
REACT_APP_STAGE = "staging"

[context.production.environment]
REACT_APP_STAGE = "production"

There are a lot of configuration options that can go in netlify.toml, for customizing build behavior. You can find them in Netlify Docs.

Happy building!

Handling asynchronous fetching of data with Redux

This article is a quick explanation and guide on how to use redux with async functions. Most common case is fetching data from remote source (eg. an API on the internet).

A full working example can be found here:
https://snack.expo.io/@killerchip/redux-async

The example is build with React-Native, but the core concepts are exactly the same for web.

What we know so far

Up to now you should be familiar with the classic redux model. In short:

  • A state is stored centrally in a store.
  • The UI subscribes to state via the connect HOC from react-redux. It renders and re-renders, following changes in the state.
  • State changes are launched with the help of action-creators. An action creator function actually return an action-object, which has at least the type property.
  • The action-object is passed to redux‘s dispatch function, which eventually passes it to the reducer function, we define.
  • The reducer function composes and returns a new state, based on the received action-object.
  • The returned state is becoming new state in the store.
  • And the changes are propagated to UI parts that have subscribed to it.
  • And the story goes on…

Handling Asychronous operations with synchronous actions

Let’s keep it simple. Imagine we have a simple app, fetching popular cat names from the internet. The app typically:

  • Display a list of fetched cat names.
  • Display a message, icon, or in general a component when it is actually fetching data from the internet.
  • If the cat names are fetched OK, then we display them.
  • If the fetch operation failed for some reason, display the error message.

So, for a simple list of cat names, we probably need the following state form:

1
2
3
4
5
const initialState = {
data: null,
isFetching: false,
error: null
}
  • data holds the latest cat names fetched from the internet
  • isFetching is indicating whether a fetch operation is in progress.
  • error holds the error object from our last fetch operation, if it failed. If not, then error is null.

The above state should be enough to allow our UI display the information as requested above.

But launching a single fetch operation requires many changes in our state. Additonally these changes are occuring asynchronously.
Let’s follow the chain of events…

A fetch operation is lauched: The state updates to:

1
2
3
{
isFetching: true
}

Case 1: The fetch operation succeeds and returns newData:

1
2
3
4
5
{
data: newData,
isFetching: false,
error: null
}

Case 2: The fetch operation fails with an error object:

1
2
3
4
5
{
data: null, // or we may choose to keep the old data from previoius fetch.
isFetching: false,
error: error
}

So we need the corresponding synchronous action-objects and their action-creators:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export const CAT_NAMES = {
START_FETCH: "CAT_NAMES_START_FETCH",
FAIL_FETCH: "CAT_NAMES_FAIL_FETCH",
FINISH_FETCH: "CAT_NAME_FINISH_FETCH"
};

export const startFetchCatNames = () => ({
type: CAT_NAMES.START_FETCH
});

export const failFetchCatNames = error => ({
type: CAT_NAMES.FAIL_FETCH,
payload: error
});

export const finishFetchCatNames = data => ({
type: CAT_NAMES.FINISH_FETCH,
payload: data
});

And a reducer function that changes the state accordingly:

1
2
3
4
5
6
7
8
9
10
11
12
const reducer = (state = initialState, action) => {
switch (action.type) {
case CAT_NAMES.START_FETCH:
return { ...state, isFetching: true };
case CAT_NAMES.FINISH_FETCH:
return { isFetching: false, data: action.payload, error: null };
case CAT_NAMES.FAIL_FETCH:
return { ...state, isFetching: false, error: action.payload };
default:
return state;
}
};

Now each time we wish to launch a new fetch action, we actually have to perform the asynchronous logic in a separate function that dispatches separate actions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import store from './store.js' // or whatever module exports your store

function fetchCatNames = async url => {
const {dispatch} = store;

dispatch(startFetchCatNames()); // indicate that fetch has started

try {
const data = await fetchData(url); // fetching data
dispatch(finishFetchCatNames(data)); // handle success
} catch (error) {
dispatch(failFetchCatNames(error)); // handle failure
}
}

Well this is a bit out of our react-redux pattern. We access the store directly. And can get things a bit more complicated. Wouldn’t be nice if our function could be return by an action-creator and be injected in our redux chain of action-processing seamlessly with the other good-old, action objects?

redux-thunk middleware

Well, a more elegant approach to this, is the redux-thunk middleware.!

Wow! A lot of terms. Let’s explain:

Middleware: redux allows for middleware plugins. This means that each action that is dispatched, before is delivered to redux‘s reducer, can be handed-over to 3rd party tools. These can modify actions, tigger side actions, or even prevent redux actions from executing.

redux-thunk: This is a specific plugin that can handle asynchronous functions.
Actually redux-thunk will receive an action before it is handed-over to reducer. It will check the type of the action. If the action is a function, then it will execute it, and stop further proecessing. If it is an object, then it will pass it on untouched, and be done with it.

So we can pass our asynchronous function as an action, and have it launched automatically by redux-thunk. The actual function will then trigger a series of synchronous events based on the progress of our fetch operation.

Creating a thunk

A thunk actually is a function that is returned from another function. So it defers execution for later.

A redux-thunk is a function that accepts dispatch as a parameter and uses it as it wishes to dispatch actual actions.
So in our case, our fetchCatNames function as thunk should be:

1
2
3
function fetchCatNames = dispatch => {
// our rest of logic does not change...
}

But what if we want to pass our own parameters to our thunk? Then we create our function (a thunk) via an action creator. So our special action-creator returns a function and not a redux action-object.

1
2
3
4
5
6
7
8
9
10
export const fetchCatNames = url => async dispatch => { // <== notice the two fat-arrows
dispatch(startFetchCatNames());

try {
const data = await fetchData(url);
dispatch(finishFetchCatNames(data));
} catch (error) {
dispatch(failFetchCatNames(error));
}
};

Actually we need to create our thunk via an action-creator also because it will be used by react-redux exactly in the same way as our typical synchronous action-creators. It’s just that it returns a function (thunk) instead of an object, and it will be handled by redux-thunk middleware.

So when one or more components, need to call our asynchronous action, we pass its action creator the same way as we would with the our classic action-creators.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class ButtonBar extends React.PureComponent {
fetchCatName = () => this.props.fetchCatNames('data'); // <== calling action creator is equivalent to passing it to dispatch. See below

render() {
return (
<View style={styles.view}>
<Button onPress={this.fetchCatName}>Fetch Names</Button>
</View>
);
}
}

const mapActionsToProps = {
fetchCatNames, // <== we pass our action creator to the component, here
};

export default connect(
null,
mapActionsToProps
)(ButtonBar);

Note: In the above example we used the mapActionsToProps object. This is a form of react-redux that allows us directly to call our _action_creators_. In the background our action creators will be passed to the dispatch function as argument. More details here.

Note: we said that our thunk receives dispatch as parameter. Well, it actually can also receive getState function, in case it needs the current state in its logic. See more details here.

Applying the middleware

This is an easy step. We just apply the middleware using applyMiddleware function when we define our store.

1
2
3
4
5
6
7
8
9
10
11
12
import { combineReducers, applyMiddleware, createStore } from "redux";
import thunk from "redux-thunk";
import catNames from "./reducer";

const reducer = combineReducers({
catNames
});

export default createStore(
reducer,
applyMiddleware(thunk) // <== Applying redux-thunk middleware
);

Summary / Cheatsheet:

So in order to handle asynchronous operations with redux we need to define special functions that return other functions (thunks).

Step 1: we define the actions and state as normal. They are synchronous actions:

actions.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export const CAT_NAMES = {
START_FETCH: "CAT_NAMES_START_FETCH",
FAIL_FETCH: "CAT_NAMES_FAIL_FETCH",
FINISH_FETCH: "CAT_NAME_FINISH_FETCH"
};

export const startFetchCatNames = () => ({
type: CAT_NAMES.START_FETCH
});

export const failFetchCatNames = error => ({
type: CAT_NAMES.FAIL_FETCH,
payload: error
});

export const finishFetchCatNames = data => ({
type: CAT_NAMES.FINISH_FETCH,
payload: data
});

reduxer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { CAT_NAMES } from "./actions";

const initialState = {
data: null,
isFetching: false,
error: null
};

const reducer = (state = initialState, action) => {
switch (action.type) {
case CAT_NAMES.START_FETCH:
return { ...state, isFetching: true };
case CAT_NAMES.FINISH_FETCH:
return { isFetching: false, data: action.payload, error: null };
case CAT_NAMES.FAIL_FETCH:
return { ...state, isFetching: false, error: action.payload };
default:
return state;
}
};

export default reducer;

Step 2: we define our asynchronous action creator that handles the asynchronous operations.

actions.js

1
2
3
4
5
6
7
8
9
10
11
12
import { fetchData } from '../api/client';

export const fetchCatNames = url => async dispatch => {
dispatch(startFetchCatNames());

try {
const data = await fetchData(url);
dispatch(finishFetchCatNames(data));
} catch (error) {
dispatch(failFetchCatNames(error));
}
};

Step 3: When defining the store, we apply the redux-thunk middleware.

store.js

1
2
3
4
5
6
7
8
9
import { combineReducers, applyMiddleware, createStore } from "redux";
import thunk from "redux-thunk";
import catNames from "./reducer";

const reducer = combineReducers({
catNames
});

export default createStore(reducer, applyMiddleware(thunk));

Step 4: we bind our action creator normally via the connect HOC of react-redux.

ButtonBar.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { Button } from 'react-native-paper';
import { connect } from 'react-redux';
import { fetchCatNames } from '../redux/actions';

class ButtonBar extends React.PureComponent {
fetchCatName = () => this.props.fetchCatNames('data');

render() {
return (
<View style={styles.view}>
<Button onPress={this.fetchCatName}>Fetch Names</Button>
</View>
);
}
}

const mapActionsToProps = {
fetchCatNames, // <== action-creator as prop function
};

export default connect(
null,
mapActionsToProps
)(ButtonBar);

React-Redux-Typescript guide

React and React-Native are powerfull and popular libraries allowing to build web-apps and mobile-apps, using Javascript. And they almost always are used with Redux state-management library, that allows managing state globally in the app from a central point. Microsoft’s Typescript on the other hand is a superset language of Javascript, that brings the benefits of typing-languages to it.

Combine these technologies (React-Native, Redux, Typescript) you have a powerfull toolbox for building mobile-apps.

The following is an example-based guide/checklist on how to use the React-Native (applies to React also) Typescript, and Redux altogether, to build and consume your states with your components. It assumes that you are already familiar with React-Native, Redux, and Typescript language.

Contents

The following topics are covered:

  • Preparing the app
  • Creating the state
  • Writing Actions
  • Writing the store
  • Consuming the store
  • Typed Components
  • Using styles

Preparing the App

Initialize React-Native Typesscript project

To initialize a RN-Typescript project follow the instrucitons of this article:

React-Typescript with ESlint and Prettier

Apart from setting up the project, it also guides you to setup ESlint and Prettier for better code quality control.

Install Redux

Install the following packages for Redux, React-Redux with Typescript flavor.

In the root folder your projec:

1
2
3
npm install redux react-redux
npm install --save-dev redux-tools
npm install @types/react-redux -D

Folder Layout

For my Redux state management I like to use the following folder structure:

1
2
3
4
5
6
state/
types.ts // interfaces common to all domains
store.ts // redux store
<domain>/
actions.ts // actions specific to domain
reducer.ts // the reducer of the domain

All types are gathered in the types.ts file. It can be also a folder, but in general I find it a good way to have types in one place, for other parts of the app easily access them.

The store.ts will export the one combined state of various domain sub-states.

Then for each sub-state there is a correspondingly-named folder. In this folder the actions and reducer each sub-state are stored.

Creating the State

Clarify the State and Actions

First sit back and think of what your App state will be. Let’s go for a simple example of a user-list of some sort.

Our user-list will have the following form:

user-list

1
2
3
4
5
6
7
8
9
10
[
{
name: John;
surname: Doe;
age: 32
},
{
// next user here, etc.
}
]

Then we need to think of the actions that will manage the state.

For our user-list example, let’s assume we need the following actions:

user-list actions

1
2
3
4
5
6
7
8
// Adding a new user in the list
addUser(name, surname, age);

// Updating the data of an existing user in the list
updateUser(index, userData);

// Remove a user from the list
removeUser(index);

Create types

Since we have our state and actions clarified we can now start creating the types that will be used to define state and actions later-on.

State Components Types

Our state will have sub-objects. We can start define their types. In our example we need to define the User type.

state/types.ts

1
2
3
4
5
export type User = {
name: string;
surname: string;
age: number;
}

State Type

Since we defined the state components types we should now define our whole state type.

In our case things are simple:

state/types.ts

1
export type UserListState = User[];

Define actions Enum

Now we should start bulding the interfaces of our actions. First define a Typescript enum for the actual action types.

state/user-list/actions.ts

1
2
3
4
5
export enum USER_LIST_ACTION_TYPES {
ADD_USER: 'USER_LIST/ADD_USER',
REMOVE_USER: 'USER_LIST/REMOVE_USER',
UPDATE_USER: 'USER_LIST/UPDATE_USER'
}

Define action types

Remember that each redux _action_creator_ returns a specific action object to pass-on to reducers. These action objects have specific format.
With Typescript we strongly type them.

types.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export type AddUserAction = {
type: string;
userData: User;
}

export type UpdateUserAction = {
type: string;
index: number;
userData: User;
}

export type RemoveUserAction = {
type: string;
index: number;
}

And always define a union type. We are going to need it with our reducer:

state/types.ts

1
export type UserListAction = AddUserAction | UpdateUserAction | RemoveUserAction;

Writing Actions

Action Creators

Next we can start writing our action creators.

Here’s the example of our addUser creator.

state/user-list/actions.ts

1
2
3
4
5
6
7
8
export const addUser = (name: string, surename: string, age: number): AddUserAction => ({
type: USER_LIST_ACTION_TYPES.ADD_USER,
userData: {
name,
surename,
age
}
});

You can continue and define the rest of the actions accordingly.

Write the reducer

Next we can move on with writing the redux reducer:

define an initial state

state/user-list/reducer.ts

1
export const initialState: UserListState = [];

create a dummy reducer

First we write a dummy reducer. A dummy reducer does nothing more than return the state that is receiving. It will be our base for adding actions immediately after:

state/user-list/reducer.ts

1
2
3
4
5
6
7
8
9
const userList = (
state: UserListState = initialState,
action: UserListAction
) => {
switch (action.type){
default:
return newState;
}
}

Pay attention to the type of action parameter. It is the union of types of each individual actions.

add actions to reducers
Next we can start handling actions per action.type:

state/user-list/reducer.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const userList = (
state: UserListState = initialState,
action: UserListAction
) => {
const newState: UserListState = deepClone(state); // a deep-cloning function

switch (action.type){
case USER_LIST_ACTION_TYPES.ADD_USER:
// pay attention to type-casting on action
const { name, surename, age } = <AddUserAction>action;
return [...newState, { name, surename, age }];

// define rest of actions here

default:
return state;
}
}

Pay attention in line

1
const { name, surename, age } = <AddUserAction>action;

Because our actions are of different shape, most times we have to type-cast them to use one of their particular forms.

Writing the store

Now we can define our global app state store. Typically our entire app state is comprised of our sub-states (like user-list sub-state in our example). We will use reac-redux combineReducers to do it.

define AppState type

First we define our entire App’s state type. This is a combination of our sub-states.

state/types.ts

1
2
3
4
export type AppState = {
userList: UserListState,
// add future state slices here
}

create store / combineReducers

Then we create that store for our whole app state. We export it to be consumed by the rest of our app.

state/store.ts

1
2
3
4
5
6
7
8
9
import { combineReducers } from 'react-redux';
import { AppState } from './types';

export default store.create(
comabineReducers<AppState>(
userList, // this is the user-list reducer
// other sub-states reducers go here
)
)

Write tests for reducers

We will not go into details here. But as a note, you might want to write tests for your reducers.

The test cases should be:

  • Reducer returns initial state when no state is given.
  • Reducer returns the same state when an irrelative action is provided to it.
  • At least one test per action.

Consuming the store

Provide the store

Once we have our store defined we can pass it down to our App, via React-Redux Provider component.
Of course we have to import our store from the state folder.

App.tsx

1
2
3
4
5
6
7
8
9
10
11
import store from './state/store';

const App = () => {
return (
<Provider store={store}>
<Fragment>
{/* Your app magic goes here. */}
</Fragment>
</Provider>
);
};

Smart components

Smart components are the ones that consume the state and use dispatch action of the store to dispatch actions. So when you want to consume your state, first define the smart component using React-Redux connect to wrap a presententional component.

ui/components/user-list/user-list.tsx

1
2
3
4
5
6
const UserList = connect(
mapStateToProps,
mapDipatchToProps
)(UserListBase);

export default UserList;

You’ll notice that in general I prefer to use a ‘public’ component name for the smart component (e.g. UserList) and use the extension Base for the internal presentational component. (e.g. UserListBase).

Then create the mapStateToProps method to feed your component exactly the data needed from the state.

ui/components/user-list/user-list.tsx

1
2
3
const mapStateToProps = (state: AppState) => ({
users: state.userList
});

Also you may create the mapDispatchToProps method, to send actions to the reducer.

1
2
3
4
5
6
7
8
import { Dispatch } from 'react-redux';

const mapDipatchToProps = (dispatch: Dispatch) => ({
onAddUser: (name: string, surename: string, age: number) => {
dispatch(addUser(name, surname, age));
},
// other callbacks go here...
});

Typed Components

Types for Component Props

For components to work with Typescript we must type their properties first. We define both data properties and callback functions. For our example:

ui/components/user-list/user-list-base.tsx

1
2
3
4
type Props = {
users: UserListState,
onAddUser: (name: string, surename: string; age: number) => void;
}

Presentational Component (Funcational)

Then we use the Props type when we destructuring the props in the function:

ui/components/user-list/user-list-base.tsx

1
2
3
4
const UserListBase = ({
users,
onAddUser
}: Props) => { /* your component here */ };

Presentational Component (Class based)

Defining a class-based component is similar. We pass the Props type in Component class:

ui/components/user-list/user-list-base.tsx

1
2
3
class DiceEditorBaseComponent extends React.Component<Props> {
/* define your Component class here */
}

Using styles

Using styles with Typescript is similar as with plain React, since we let Typescript to automatically infer the types of styles.

I prefer having styles in their own files though:

ui/components/user-list/styles.ts

1
2
3
4
5
6
7
8
9
10
import { StyleSheet } from 'react-native';

export default StyleSheet.create({
list: {
// your styles here...
},
listItem: {
// your styles here...
}
});

React-Native Typescript with ESLint and Prettier

React Typescript is picking up speed, as typing offers a lot of benefits in a project.

The following is a walkthough on how to:

  • Setup a React-Native Typescript project
  • Install and configure ESlint and Prettier
  • How to configure ESLint and Prettier VSCode plugins.

The linting rules proposed here are @typescript-eslint/eslint-plugin and eslint-config-airbnb-typescript

Bear in mind that due to TSLint depreciation, Typescript linting is now taken over by ESLint, and there’s work in progress.

Contents:

  1. Setup React-Native Typescript project
  2. Install and configure eslint and prettier
  3. Setup scripts
  4. Git hooking with husky
  5. Setup ESlint VSCode plugin.

Setup React-Native Typescript

You can setup a react-native typescript project with the cli:

1
2
react-native init <project-name> --template typescript
cd <project-name>

Before git init your project make sure to add the following lines to .gitignore file:

.gitignore

1
2
# CocoaPods
/ios/Pods/

Then you can git init your project

1
2
3
git init
git add .
git commit -m "project init"

Install and configure eslint and prettier

The project comes already with eslint and prettier installed. We will add support for react but mainly for typescript and airbnb‘s proposal for typescript eslint.

So install the packages:

1
npm install --save-dev eslint-plugin-react eslint-plugin-import eslint-plugin-jsx-a11y @typescript-eslint/eslint-plugin eslint-config-airbnb-typescript eslint-config-prettier

And then configure .eslintrc.js to use these plugins:

.eslintrc.js

1
2
3
4
module.exports = {
root: true,
extends: ['@react-native-community', 'airbnb-typescript', 'prettier', 'prettier/@typescript-eslint', 'prettier/react'],
};

In addition you can create a .prettierrc file to customize the prettier behavior to your liking. Here’s my personal favourite:

.prettierrc

1
2
3
4
5
6
{
"singleQuote": true,
"trailingComma": "none",
"tabWidth": 4,
"semi": true
}

ADD the following in excludes section of tsconfig.json to suppress a typescript error in tests:

.tsconfig.json

1
2
3
"exclude": [
"__tests__/**/*-test.ts"
]

Setup scripts

You should setup scripts in package.json so you can run those tools manually when needed.

Add a script for running prettier in WRITE mode. (Caution: It will change your files).

package.json

1
2
3
4
"script": {
...
"prettier:write": "npx prettier --write **/*.{js,jsx,ts,tsx,json} && npx prettier --write *.{js,jsx,ts,tsx,json}"
}

Also add as script for running lint on all files including .ts and .tsx ones:

package.json

1
2
3
4
"script": {
...
"lint": "tsc --noEmit && eslint --ext .js,.jsx,.ts,.tsx ./",
}

Before proceeding it’s a good idea to commit your existing changes:

1
2
git add .
git commit -m "eslint typescript prettier"

Fix the existing project

Now you can run

1
npm run prettier:write

This command will re-format your project files according to the rules we just installed.

Then you can run lint:

1
npm run lint

You will see that eslint finds some issues. These must be corrected by hand. Once you fix those run again the commands:

1
2
npm run prettier:write
npm run lint

If no issue found, you can commit your changes:

1
2
git add .
git commit -m "prettify project"

Git hooking with husky

You do not want to run those commands manually, each time you commit. You should automate this process to run automatically each time you commit.

This can be done with husky. Also pretty-quick will help you to prettify only the files that you changed in this commit.

So install those packages:

1
npm install --save-dev pretty-quick husky

Then configure your package.json to enable husky pre-commit hook.

package.json

1
2
3
4
5
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged && npm run lint"
}
}

Now each time you commit, your changes will be prettified and checked by eslint. If issues are found, the commit will be blocked.

VSCode plugin setup.

To be able to have the eslint and prettier information real-time in you VSCode Editor you should install the following plugins:

ESLint plugin needs a bit further configuration to work with typescript.

Open the VSCode settings and the following configuration snippet.

settings.json

1
2
3
4
5
6
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
]

Done

That’s it. You can now write your code in React-native.
Happy coding!

Code style and best practices enforcement in Angular (TSLint - Prettier)

Every language and framework has its best practices rules to make developer life’s easier and avoid pitfalls. But not ‘walking astray from the path’ in a manually way is not easy and becomes impossible when multiple developers / teams get involved.

Having a toolbox to enforce these best practices, as automatically as possible is one of the ‘base’ preparations every project should have before even start writing a single line of code.

The web languages and Angular are no exception to this rule and
there are various tools doing just that. After searching around and do some testing, here is my personal recommandation for setting up ‘Lint’ and ‘Code formatting’ enforcements.

The tools

The tools proposed here are of two categories: ‘CLI based’ and ‘Editor Plugins / Extensions’.

The CLI tools are part of the project (in package.json). So this means that is easy to be kept in sync and enforced in all developer’s environments and CI/CD deployments.

The editor plugins presented here are for the propular VSCode editor (my personal favourite). They help you ‘stay in the path’ real-time, while you are coding.

The CLI tools:

The VSCode plugins are:

tslint

tslint is a package that parses your Typescript code looking for points that ‘break the best practice rules’. It comes with a pre-configured set of rules for best practices in Typescript in general.

You don’t have to do something to install it. It comes pre-included in any new Angular project you create, when you run ng new command.

codelyzer and tslint-angular

codelyzer and tslint-angular packages are a set of rules for tslint, and they are enforce Angular-specific best practices including the Angular Style Guide.

You don’t have to install codelyzer as it included with new projects created with ng new command.

To install tslint-angular

  1. run npm install --save-dev tslint-angular
  2. edit tslint.json to extend the configuration
    1
    2
    {
    "extends": ["tslint:recommended", "tslint-angular"],

To run your project against these rules, just run: ng lint.

prettier and tslint-config-prettier

Prettier is a tool that enforces code style formatting. It is just concerned with the display format of the code. It has nothing to do code operation and effectiveness checks that tslint performs.

To install it, run: npm install --save-dev prettier

You will also want to install tslint-config-prettier. This is an additional rules configuration file that resolves conflicts between prettier and tslint. tslint includes some code formatting rules, that conflict with prettier. So when you prettier you break tslint, and when you fix to conform with tslint, then prettier starts to complain. So the above mentioned configuration file, resolve this conflict once and for all.

To install it

  1. run: npm install --save-dev tslint-config-prettier.
  2. edit tslint.js file to apply the config
    1
    2
    {
    "extends": ["tslint:recommended", "tslint-angular", "tslint-config-prettier"],

You might want also to override some prettier rules with your own. You can create a .prettierrc file in root folder of your project.
Here’s my preferred rules:

.prettierrc

1
2
3
4
{
"tabWidth": 4,
"singleQuote": true
}

You can have look at Prettier options to see what else can be overriden to your liking.

You might also want to exclude certain files from prettier checks. Just place a .prettierignore configuration file in project’s root folder. Here’s my preferrence:

.prettierignore

1
2
3
4
package.json
package-lock.json
yarn.lock
dist

To manually run a prettier, just enter:

1
npx prettier --write "**/*"

CAUTION: The --write parameter will directly change your files. Having a clean commit state before you do that it’s a good idea so you can revert any changes that did not work out well (this can happen).

To format only specific file-types of your project, you can run (e.g):

1
npx prettier --write "**/*.{ts, html}"

You can also launch it for specific files. e.g.:

1
npx prettier --write "src/app/app.component.ts"

pretty-quick and husky

Having to run tslint and prettier manually each time you wish to commit, is a real burden. And is almost sure that it cannot be effectively enforced just by telling your colleagues to respect the policy and run these tools themselves each time.

That is why we need to enforce these rules automatically, before committing files. Code should be automatically formatted and if it does not follows our best practices, the commit should stop.

Welcome, husky package. It allows you to automatically run your script within git life-cycle hooks like pre-commit and pre-push.

But then again running these checks enforcement on the entire code base each time, is a waste of time, resources. And it messses up commits.

Welcome, pretty-quick: It identifies which files have changed and allow applying prettier fixes only on those.

So:

  1. install pretty-quick: npm install --save-dev pretty-quick
  2. install husky: npm install --save-dev husky
  3. Configure husky to run before commits. Add the following to package.json
    1
    2
    3
    4
    5
    "husky": {
    "hooks": {
    "pre-commit": "pretty-quick --staged && ng lint"
    }
    }

So before each commit:

  1. prettier will format the changed files automatically re-stage them.
  2. Then ng lint will run. If the code does not comply with the rules, then the commit will be stopped.

Otherwise the commit will procced as normal.

VSCode Plugins

As we mentioned earlier, it is much more efficient if you have the above mentioned checkes real-time while you are writing your code. Fortunately there are corresponding VSCode plugins to do exactly that. Here are my recommendations.

TSLint and codelyzer.

It performs tslint analysis based on the files you are editing. Just install the plugin and you are set for the tslint part.

For enabling also the codelyzer rules for VSCode: Open Code > Preferences > User Settings, and enter the following lines:

{
“tslint.rulesDirectory”: “./node_modules/codelyzer”,
“typescript.tsdk”: “node_modules/typescript/lib”
}

Prettier

It will suggest code format corrections in real-time. It binds to Format Document command of VSCode - Right-click menu in the file contents you are editing - and formats based on your prettier rules the entire document you are working on.

Angular specific plugins

The following plugins are Angular specific and they are also recommended by Angular guru John Papa.

And the following are also a very useful.

Closing

As mentioned above, these are personal recommendations. They are not ‘written in stone’, and I expect some of those to change over time as I bump onto cases that need fine-tunning. I will be updating this post accordingly.

Happy Angular coding !

React router cheatsheet

This is a cheatsheet for React Router. It can also be found in my React Cheatsheet github project.

Install

1
npm install react-router-dom

Basics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const BasicExample = () => (
<Router>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/users">Users</Link></li>
</ul>

<hr/>

<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/users" component={Users}/>
</div>
</Router>
)

About Paths

<Route path="/about" component={About}/> means: when in route /about render the About component.

path= uses a regex. So path="/home" will match both http://.../home and http://.../home/123

If more than one Routes match a path, all matching routes will be rendered.

Root (/) path

Especially take care about the / (root) path, as it will match all paths (e.g. it will match also /home, /home/123/ etc.). Use exact path= for the root path (or any other path that needs exact match).

Trailing slashes

Trailing slashes on the URL don’t matter on the match. strict path make the trailing slash matter. So:

  • strict path="/home/" will match http://.../home/ but not http://.../home
  • path="/home/" will match http://.../home/ and http://.../home
  • strict path="/home" will match http://.../home/ and http://.../home, anyways

A Route component with no path property will always trigger a match and render.

<Link to...> changes the URL path

All Route Component‘s get passed the match prop which contains information on the current URL.

About Route render methods

Use component= for components. Don’t pass inline functions.

Use render= for inline function.

Use children for components that always will be rendered. Also make sure that you pass inline function here. Using a Route children... path instead of a typical React Component allows the rendered component to get some route information via the props injected by the router.

1
<Route path="/about" children={(props) => <div>Always rendered</div>}/>

Parameters

An example:

1
2
3
4
5
6
7
8
9
const About = (props) => {
console.log(props.match)
// Object {path: "/about", url: "/about", isExact: true, params: Object}

console.log(props.location)
// Object {pathname: "/about", search: "", hash: "", state: undefined}

console.log(props.history)
// Object {length: 1, action: "POP", location: Object}

Match

  • params (object) - contains key/value pairs of the dynamic segments that are used in the path pattern (e.g. {userId = 123} when path = “/user/:userId” and the URL is “/user/123”)

  • isExact (boolean) - true if the URL is an exact match with the path property.

  • path (string) - the path property of the route component. Useful for building nested routes.

  • url (string) - the matched portion of the URL. Useful for building nested Links

Note: The match object is null when there is no match.

Location

A location object is passed down to all components rendered from Route components as a property. The location object contains the following information about the path that was rendered:

  • pathname (string) - the full path of the URL
  • search (string) - the URL query string
  • hash (string) - the URL hash fragment
  • state (object) - the current state, if a state was provided to history.push(path, [state]), otherwise undefined

History

A history object is passed down to all components rendered from Route components as a property. The history object contains the following information about the path that was rendered:

  • length (number) - The number of entries in the history stack
  • action (string) - (PUSH, POP, or REPLACE)
  • location (object) - see location

It also has the following methods:

  • push(path, [state]) - (function) Pushes a new entry onto the history
  • replace(path, [state]) - (function) Replaces the current entry on the history stack
  • go(n) - (function) Moves the pointer in the history stack by n entries
  • goBack() - (function) Equivalent to go(-1)
  • goForward() - (function) Equivalent to go(1)
  • block(prompt) - (function) Prevents navigation (see the history docs)

Nested routes

It is pretty straight forward to nest Route components within each other. The match.url property is useful if you want to add path segments onto the original matched url. For example, if you want to add additional Route components within a component rendered from an original Route component do the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const Info = ({match}) => {
return (
<div>
<Route path="{match.url + "/telephone"}" render={(props) => <div>Phone: 999 999 9999</div>}/> // renders when URL = /info/phone`

<Route path="{match.url + "/e-mail"}" render={(props) => <div>Email: email@example.com</div>}/> renders when URL = /info/email

<Route path="{match.url + "/strAddress"}" render={(props) => <div>Address: 211 Sesame Str</div>}/> renders when URL = /info/address

</div>
)}

const App = () => (
<div>
<BrowserRouter>
<Route path="/info" component = {Info}/>
</BrowserRouter>
</div>
);

See that nested Route extends the parent url match.url.

NavLinks are like links but get special CSS classes when they are active:

1
NavLink to="/info" activeClassName="selected">Info</NavLink>

activeStyle

1
<NavLink to="/info" activeStyle={{color:'red'}}>Info</NavLink>

isActive

The NavLink component has an isActive property that triggers a function when the NavLink is active.

1
<NavLink to="/info" isActive={() => console.log('Info router selected))}>Info</NavLink>

exact

The NavLink component has an exact property that only lets the NavLink become active when the location is matched exactly.

strict

The NavLink component has a strict property that causes trailing slashes to be taken into consideration when determining whether the NavLink is active.

Switch Component

It will only render the first route that will match.

example:

1
2
3
4
5
6
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
<Route path="/users/:id" component={User}/>
<Route component={DefaultRoute}/> //if no other routes match, this route will render for sure
</Switch>

Redirect component

Redirects to URL when rendered. It’s a way to ‘programmatically’ cause redirection.

Typical case is to be used inside other Routes. Redirect on error example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let validUser = false;

const ValidateUser = () => {
if(validUser){
return <div>Welcome User</div>
}
else{
return <Redirect to="/error"/>
}
}

const Login = () => {
validUser = true
return <div>Logged In</div>
}

const Error = () => {
return <div>Please Log In</div>
}
<Route exact path="/user" render={ValidateUser}/>
<Route exact path="/error" render={Error}/>
<Route exact path="/login" render={Login}/>

Redirects can also be used inside Switches. To use a Redirect within a Switch component, add a from property to the Redirect component. The Switch component will render the first component that matches, whether it is a Route or a Redirect. The from property can be only used with Redirects that are within Switch components.

1
2
3
4
5
6
7
<Switch>
<Route path='/user' component={User}/>
<Redirect exact from='/' to='/user'/>
<Redirect from='/oldPageURL' to ='/newPageURL'/>
<Route component={DefaultPage}/>

</Switch>

Example of default root route redirect

1
2
3
4
5
6
7
8
<Switch>
<Redirect exact from="/" to="/home" />
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
<Route path="/users/:id" component={Users} />
<Route path="/error" component={Error} />
<Route component={DefaultRoute} />
</Switch>

Prompt

When rendered it watches if you want to leave the current URL path and prompts.

1
2
3
4
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/users/:id" component={Users} />
<Prompt message="Are you sure you want to leave?" />

when

The when attribute is a boolean that can be used to enable or disable the Prompt component. Set it to false when you want to disable the Prompt component and allow the user the navigate away without seeing a Prompt. The when attribute is set to true by default.

1
2
 var notSaved = true
<Prompt when={notSaved} message="Are you sure you want to leave without saving?"/>

Example of ‘subroutes’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import React from "react";
import { Link, Switch, Route } from "react-router-dom";

const projects = [
{link: '/game_project', name: 'Game Project'},
{link: '/react_project', name: 'React Project'},
{link: '/db_project', name: 'Database Project'},
{link: '/ml_project', name: 'Machine Learning Project'},
]

const Projects = ({ match }) => (
<div>
<ul>
{projects.map (
project => (
<li>
<Link to={`${match.url}${project.link}`}>{project.name}</Link>
</li>
)
)}
</ul>

<Switch>
<Route
path={match.url + '/:project_link'}
render = { ({match}) => <div>{match.params.project_link}</div>}
/>

<Route exact path={match.url} render={() => <div>Pick a Project to view!</div>} />å
</Switch>
</div>
);

export default Projects;

MeteorJS - Role Based Routing example

This is a sample project / guide on setting up a Meteor app with top level routing based on the role of the user.

Overview

The app based on the following concepts.

  • Users can be ‘normal’ users, or can have an admin role.
  • The app has top level parts (routes).
    • Public area: Can be accessed by anyone, in order to authenticate
    • App area: This is the normal usage of the app that can be used both types of users.
    • Admin Portal area: This area should be accessible only by users with admin role.
  • In addition:
    • Public area is available both on web and cordova platforms
    • Admin Portal is availble only in Web platform. admin users that login in Cordova environment are redirected to App area.
    • App area is only available on Cordova platform. If any user tries to access it on Web, the user is redirected to a ‘block’ page.

The app can be adapted to switch various other cases, like adding more roles or removing the above restrictions.

Packages used

The app is based on the following atmosphere.js packages.

  • alanning:roles for managing roles of users
  • kadira:flow-router for routing.

It is based on React but it can easily be adapted to Blaze templates.

Router init after subscriptions complete

A basic concept that it demonstrates is how to ‘stall’ the router initialization until the needed subscriptions are finished. This is because we need specific information fetched from database before making a routing decision.

In the example below, the router initialization waits for 3 subscriptions to finish.

  • The me subscription: It fetches our full user document with all necessary fields.
  • The Roles.subscription: This subscription is created by the alanning:roles package. In this case is not necessary since we have the me subscription. It is put here as demo. In only the roles field is needed to make routing decision, the me subscription can be skipped.
  • The companies subscription: This demonstrates the case were there is need for other subscriptions needed for the routing decision. Again it is put here as demo, because it is not actually used in the routing decision.

Finally before the router is initialized, a check is performed in case it already has been initialized.

routes.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FlowRouter.wait();

Tracker.autorun(() => {
const mesub = Meteor.subscribe('me');
const companiesSubscription = Meteor.subscribe('companies');

const shouldInitializeRouter =
mesub.ready() &&
Roles.subscription.ready() &&
companiesSubscription.ready() &&
!FlowRouter._initialized; // eslint-disable-line no-underscore-dangle

if (shouldInitializeRouter) {
FlowRouter.initialize();
}
});

The top-level areas

We app is ‘partitioned’ in 3 areas.

  • Public Area: which will contain pages accessible by unauthenticated users. In this example, it has only one route. It is the /login which renders the Login page.
  • App area (/app): This area offers the App’s functionality for normal users.
  • Admin area (/admin): This area is the Admin Portal of the app. It should be accessible only by users with admin role.

These areas are translated in routes definitions as FlowRouter groups.

routes.js

1
2
3
4
5
6
7
8
9
10
11
12
13
const publicRoutes = FlowRouter.group({
name: 'publicRoutes'
});

const appRoutes = FlowRouter.group({
prefix: '/app',
name: 'appRoutes',
});

const adminRoutes = FlowRouter.group({
prefix: '/admin',
name: 'adminRoutes',
});

And the corresponding routes (only one page per area in this demo.)

routes.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
publicRoutes.route('/login', {
action() {
mount(LoginPage);
}
});

appRoutes.route('/', {
action() {
mount(DummyPage, { title: 'App for Users' });
}
});

adminRoutes.route('/', {
action() {
mount(DummyPage, { title: 'Portal for Admins' });
}
});

The root ‘/‘ path

The app’s entry point will be the root (/) path. So this path is not part of any group (or area). Is it used for entry point decision. It lands the user on the proper area based on the user’s authentication status and role.

router.js

1
2
3
4
5
FlowRouter.route('/', {
action() {
FlowRouter.go(getRouteBasedOnRole());
}
});

role-routing.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export const getRouteBasedOnRole = () => {
const user = Meteor.user();

// land unauthenticated users on login page
if (!user) {
return '/login';
}

// land Admins on Admin Portal
if (Roles.userIsInRole(user, ROLES.ADMIN)) {
return '/admin';
}

// No ROLE means normal user. Land on App area
return '/app';
};

Route Guards

So the app’s entry will land user normally. But in case of web, user can directly enter the URL paths to land on. Protection is needed on pages from unauthorized access (authentication status and roles) and not-supported access (area app vs platform; remember that certain parts of the app should be available only on specific platforms).

The approach here is to write guard functions that will check specific conditions and block access to the route, by redirecting to the user elsewhere.

These functions will be bound to route groups by using the triggersEnter hooks of FlowRouter.

One guard function per case is created and then it is ‘hooked’ into the necessary routes group. Care must be taken in the order in which the guards are applied. It is important for delivering the guarding logic (review again the requirements at the start of this document).

If all the guards are executed and no redirect happens, then the route will be entered.

So the route groups now become:

routes.js

1
2
3
4
5
6
7
8
9
10
11
12
const appRoutes = FlowRouter.group({
prefix: '/app',
name: 'appRoutes',
triggersEnter: [authGuard, blockAppUsageInWeb]
});


const adminRoutes = FlowRouter.group({
prefix: '/admin',
name: 'adminRoutes',
triggersEnter: [authGuard, adminRoleGuard, blockAdminPortalInCordova]
});

The route guards in detail

The triggersEnter hooks are calling the guard functions with two parameters.

  • context which contains the current route properties
  • redirect. A function that can be used to redirect the browser to a new route.

Notice, that when a user is ‘kicked out’ of a route, the user is redirected to root route (/) so the proper routing decision is taken. This way the routing logic does not have to be implemented in each guard. Care must be taken though, so no ‘loops’ are created.

authGuard

If the user is not authenticated, is kicked out of the route

1
2
3
4
5
export const authGuard = (context, redirect) => {
if (!Meteor.userId()) {
redirect('/');
}
};

adminRoleGuard

If the user has not the admin role, is kicked out of the route.

1
2
3
4
5
6
7
export const adminRoleGuard = (context, redirect) => {
const user = Meteor.user();

if (!Roles.userIsInRole(user, ROLES.ADMIN)) {
redirect('/');
}
};

blockAdminPortalInCordova

This guard is used in Admin Portal group and after the user has been evaluated for the admin role. So if the admin user is in mobile platform, then redirect to App area, in order to use the app as normal user.

1
2
3
4
5
export const blockAdminPortalInCordova = (context, redirect) => {
if (Meteor.isCordova) {
redirect('/app');
}
};

Note: The user is not redirected to / because the root logic will land him again in this area.

blockAppAreaInWeb

This guard is used in App area. If the platform is web, the user is not allowed to use. The user is not redirected to / because the logic will land him again here. Thus the user is redirected in a ‘block’ page, to be informed that the usage is not allowed in web-platform.

1
2
3
4
5
6
export const blockAppAreaInWeb = (context, redirect) => {
// App (as normal user) is not allowed in Web
if (!Meteor.isCordova) {
redirect('/ban-access');
}
};