ReactJs is currently the most popular One Page App out there
ReactJs is a library for creating one page apps. Unlike Angular it is a library rather than a framework. This means that you need to do some work at the beginning to configure it to have equivalent features to Angular. This means you need a more senior dev to configure ReactJs at the beginning if you want similar features to Angular.
If you do not integrate ReactJS with other libraries there is much less to learn than Angular. This is one of the reasons why it is pretty popular - it has a lower learning curve to get started. However if you bolt on lots of libraries you will have an equivalent learning curve to Angular.
ReactJs used to be much more performant that Angular which is another reason it became popular. However the gap in performance between ReactJS and Angular has narrowed in recent times.
One of the reasons ReactJs was more performant that Angular is that instead of updating the entire dom everytime something changes it keeps track of changes in a Virtual Dom. It can then update only what has changed in the real Dom.
Unlike Angular ReactJs does not have two way data binding. Two data binding uses more CPU cycles and is thus sometimes argued as a reason ReactJs is more performant. However in Angular you do not have to use two way data binding. Anyway let us explain what data binding is.
With data binding you have some fields. When you update those fields it updates the GUI components. That is called: "One Way Data Binding".
However if you want to the user to be able to update an HTML component and have it automatically update the same fields then you need "Two Way Data Binding". Angular adds listeners behind the scenes that allow this to be achieved. If you have lots of listeners everywhere you will uses many more CPU cycles than if you do not have them. Hence the argument that this "Two Way Data Binding" feature of Angular makes it slower. However as mentioned before the usage of "Two Way Data Binding" in Angular is optional.
Like Angular ReactJs allows you to make different components and even embed components within each other. This makes life easier when you have several devs working on the same project.
In Angular you have seperate template files ending in the html extension where you place html - this was done to separate areas of concern - a bit like how JSPs were created in the J2EE arena to separate html mark up from back end controller code.
However in ReactJS they have chosen a path of simplicity over a strategy of "separation of concerns".
The novel approach of ReactJS is to embed the HTML mark up in the Java Script or TypeScript files without using lots of single or double quotes:
export const Counter = () => {
const [count, setCount] = useState(0)
return (
<div>
<h3>Update the count and edit src/App.tsx, state is preserved</h3>
<button onClick={() => setCount((c) => c + 1)}>Count - {count}</button>
</div>
)
}
Notice how the div tag has no quotes around it. A dev tool is used to convert the embedded html and add all the necessary quotes etc.
When you add html in this fashion into a javascript file you change its file extension from js to jsx
Similarly when you add html in this fashion to a typescript file you change its file extension from ts to tsx
The x in jsx and tsx refers to the X of XML.
NOTE: that unlike Angular, ReactJs does not support TypeScript - you need to intergrate the ReactJs library with TypeScript and configure Babel to translate the TypeScript into JavaScript that can be read by browsers.
Components
States
Props
More about these below
@todo
@todo
@todo
Note that unlike Angular, ReactJS is a library and not a framework. That means you need an expert configurator to get ReactJs working with TypeScript and other features.
This section shows you how to configure your reactJs app and add all the dev tools to your environment.
The content here was taken from the series of youtrube videos at:
https://www.youtube.com/watch?v=Elpu7CIuqjY&list=PLC3y8-rFHvwiWPS2RO3BKotLRfgg_8WEo
You can also download the starter pack from github:
git clone https://github.com/gopinav/React-TypeScript-Webpack-Starter
Alternatively there are tools out there to start your project:
Fpr example to create a react project with typescript you can type:
npx create-react-app my-app --template typescript
If however you want to do stuff the manual way see:
Here we are going to configure our ReactJs project so it supports:
Webpack packaging
TypeScript
Babel to compile TypeScript to JavaScript
(i) Create project folder
(ii) Initialize git
git init
(iii) Create .gitignore
Add:
build
node_modules
(iv) Create folders:
src and build
(v) Generate package.json
npm init --y
(vi) Install Dependencies:
You can install dependencies with npm but here we will use yarn. It can install stuff in parallel which makes it faster. It is also supposed to be more secure than npm.
Install React
yarn add react react-dom
Install Typescript
yarn add -D typescript @types/react @types/react-dom
Install babel to compile typescript to javascript:
yarn add -D @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript
Install webpack package manager dependencies
yarn add -D webpack webpack-cli webpack-dev-server html-webpack-plugin
To allow for merging webpack config of common functionality into different environments
yarn add -D webpack-merge
To transpile javascript files using babel and webpack:
yarn add -D babel-loader
To enable React Refresh where the code is redeployed in Dev mode without losing state:
yarn add -D @pmmmwh/react-refresh-webpack-plugin react-refresh
To add EsLint, a dev tool for analyzing code and finding errors:
yarn add -D eslint
yarn add -D enlint-plugin-react eslint-plugin-react-hooks
yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
yarn add -D eslint-plugin-import eslint-plugin-jsx-ally
To install Prettier dev dependencies for consistent formatting
yarn add -D prettier eslint-config-prettier eslint-plugin-prettier
To ensure that all files committed do not have formatting errors
yarn add -D husky@4 lint-staged
To ensure the re-use of Babel's injected helper code to save on codesize.
yarn add -D @babel/runtime @babel/plugin-transform-runtime
To copy static assets to the build folder
yarn add -D copy-webpack-plugin
To add Bundle analyzer:
yarn add -D webpack-bundle-analyzer
(vii) Create index.html:
In body create div:
<div id=”root”></div>
(viii) Create App.tsx
import { Counter } from './Counter'
export const App = () => {
return (
<>
<h1>React TypeScript Webpack Starter Template</h1>
<Counter />
</>
)
}
(ix) Create index.tsx
import ReactDOM from 'react-dom'
import { App } from './App'
ReactDOM.render(<App />, document.getElementById('root'))
(x) Create config for typescript in a file called tsconfig.js:
{
"compilerOptions": {
"target": "ES5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "ESNext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ /* Type declaration files to be included in compilation. */,
"lib": [
"DOM",
"ESNext"
] /* Specify library files to be included in the compilation. */,
"jsx": "react-jsx" /* Specify JSX code generation: 'preserve', 'react-native', 'react' or 'react-jsx'. */,
"noEmit": true /* Do not emit outputs. */,
"isolatedModules": true /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
"strict": true /* Enable all strict type-checking options. */,
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
"resolveJsonModule": true
// "allowJs": true /* Allow javascript files to be compiled. Useful when migrating JS to TS */,
// "checkJs": true /* Report errors in .js files. Works in tandem with allowJs. */,
},
"include": ["src/**/*"]
}
(xi) Create config for babel in a file called .babelrc
{
"presets": [
"@babel/preset-env",
[
"@babel/preset-react",
{
"runtime": "automatic"
}
],
"@babel/preset-typescript"
],
"plugins": [
[
"@babel/plugin-transform-runtime",
{
"regenerator": true
}
]
]
}
(xii) Create config for webpack
Create a directory called
webpack
Under the webpack directory create webpack.config.js:
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
module.exports = (envVars) => {
const { env } = envVars
const envConfig = require(`./webpack.${env}.js`)
const config = merge(commonConfig, envConfig)
return config
}
Under the webpack directory create webpack.common.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: path.resolve(__dirname, '..', './src/index.tsx'),
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
module: {
rules: [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
},
],
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(?:ico|gif|png|jpg|jpeg)$/i,
type: 'asset/resource',
},
{
test: /\.(woff(2)?|eot|ttf|otf|svg|)$/,
type: 'asset/inline',
},
],
},
output: {
path: path.resolve(__dirname, '..', './build'),
filename: 'bundle.js',
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, '..', './src/index.html'),
}),
],
stats: 'errors-only',
}
Under the webpack directory create webpack.dev.js
const webpack = require('webpack')
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
module.exports = {
mode: 'development',
devtool: 'cheap-module-source-map',
devServer: {
hot: true,
open: true,
},
plugins: [
new ReactRefreshWebpackPlugin(),
new webpack.DefinePlugin({
'process.env.name': JSON.stringify('Vishwas'),
}),
],
}
Note: A sourcemap is a mapping between the generated/transpiled/minified JavaScript file and one or more original source files
Under the webpack directory create webpack.prod.js
const webpack = require('webpack')
module.exports = {
mode: 'production',
devtool: 'source-map',
plugins: [
new webpack.DefinePlugin({
'process.env.name': JSON.stringify('Codevolution'),
}),
],
}
(xiii) Add the webpack server to package.json for running in dev mode
"start": "webpack serve --config webpack/webpack.config.js --env env=dev --open",
Note that to call this you need to do as follows:
yarn start
(xix) Add the webpack build to package.json
"build": "webpack --config webpack/webpack.config.js --env env=prod",
Note that to run the build you do as follows:
yarn build
Once built you change into the build directory and run it:
cd build
npx serve
To open in a browser:
http://localhost:5000
Note that if you compare bundle..js side between prod and dev mode you will see that in prod mode it is less thanb 50kb and in prod mode it is over 1.5 MB. This is thanks to webpack.
(xix) Refresh in Dev mode and keep state
This was configured with the yarn install above and some config.
The config is in the dev config files:
const webpack = require('webpack')
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin')
module.exports = {
mode: 'development',
devtool: 'cheap-module-source-map',
devServer: {
hot: true,
open: true,
},
plugins: [
new ReactRefreshWebpackPlugin(),
new webpack.DefinePlugin({
'process.env.name': JSON.stringify('Vishwas'),
}),
],
}
Notice we configured the stuff we installed with yarn:
new ReactRefreshWebpackPlugin()new ReactRefreshWebpackPlugin()
We also added :
open: true
The hot deployment works with the css files as well. State is maintained. Without this refresh plugin any component with state such as a counter will loose its state.
(xx) Add ESLint dev tool
ESLint is a tool for analyzing code and finding errors.
We installed the ESLint dependencies above using yarn. Now we need to creat the .eslintrc.js file:
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module',
},
settings: {
react: {
version: 'detect',
},
},
extends: [
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/errors',
'plugin:import/warnings',
'plugin:import/typescript',
'plugin:jsx-a11y/recommended',
'plugin:eslint-comments/recommended',
'prettier/@typescript-eslint',
'plugin:prettier/recommended',
],
rules: {
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error'],
'@typescript-eslint/no-var-requires': 'off',
'react/prop-types': 'off',
'react/jsx-uses-react': 'off',
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
},
}
In VSCode also install:
ESLint extension (Integrates ESLint JavaScript ....)
In package.json you need to add another script:
"lint": "eslint --fix \"./src/**/*.{js,jsx,ts,tsx,json}\"",
(xxi) Setup a Formatter for consistence
Install the VSCode Prettier Code formatter
Create .prettierrc.js
module.exports = {
semi: false,
trailingComma: 'es5',
singleQuote: true,
printWidth: 80,
tabWidth: 2,
endOfLine: 'auto'
}
Add a formatting script to package.json
"format": "prettier --write \"./src/**/*.{js,jsx,ts,tsx,json,css,scss,md}\"",
Visit user settings in VSCode (ctrl comma) and set:
formatonsave to true
If it does not format hit
ctrl-shit-v
Click Format Document and set Prettier as the code formatter
(xxii) Ensure all files committed do not have formatting errors
Add to package.json husky and lint-staged configuration:
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,jsx,ts,tsx,json}": [
"eslint --fix"
],
"src/**/*.{js,jsx,ts,tsx,json,css,scss,md}": [
"prettier --write"
]
This will try and format on committing. If it cannot format it shows an error.
When starting a new project clone from here:
git clone https://github.com/gopinav/React-TypeScript-Webpack-Starter
It will save you a lot of pain
Otherwise use Angular where the framework is already configured.
Back: One Page JS
Page Author: JD