A base code to start developing React apps with Typescript.
-
Clone (or download) the project, run:
git clone https://github.com/gmullerb/basecode-react-ts
or
git clone https://gitlab.com/gmullerb/basecode-react-ts
-
Run
npm install
+npm run check
or./gradlew
to install npm dependencies and to check the project. -
Run
npm start
or./gradlew npm_start
to run the project. -
Open browser at
localhost:8080
. -
Start customizing it with your own ideas.
This project is piece together with the purpose of:
- Providing a "well" documented base code from where to start developing React with Typescript.
- "Well" documented in order to give you a "deep" understanding of how things works and to ease customization of this start point [1].
- Providing basic elements for a Web app: Reducers, Router, Web Fetching, etc.
- Providing all the required toys at once: Typings, Linting, Testing, Coverage and E2E Testing.
- Some things may be Opinionated based on best practices and professional experience.
- Some parts of the code may seen messy, careless, funny or dummy but the idea is to provide different scenarios, so this project can be the start for "any" project just removing things and not "adding" ( adding in this context means that the configuration is covered for "any case", no need to add something to configuration, which is usually an "exhausting" part).
- With this base project you don't have the limitation of using the wonderful
const enum
that you will when using e.g. react-scripts or ionic.- [1] If some configuration, some code, anything is not crystal clear, please create an issue and I will "improve" documentation (Although the documentation is still a Work In Progress). Also, Some topics in the README files have References so you can dig more into details.
- All in 1 project.
- Typings for Main source code.
- Code Style Checking.
- for Main source code (Typescript, React & CSS).
- for Test source code (Javascript & React).
- for E2E Test source code (Javascript).
- for Config code (Javascript).
- Test Driven Development.
- Unit, Integration and End to end tests.
- Code coverage checking.
- Internal REST API Server for playing.
- Development environment:
- Node/Npm, Node/Yarn
- or failing Java & Gradle.
- with Gradle Node plugin.
- or failing Java & Gradle.
- Node/Npm, Node/Yarn
- Flux/Redux library:
- Asynchronous/Synchronous React Reducer Provider with Hooks: react-reducer-provider.
- Building:
- Code Style Checking:
- Common checking ("All" files) with lintspaces.
- Javascript: ESlint.
- Typescript: @typescript-eslint.
- Using the Custom set of rules of eslint-plugin-base-style-config.
- CSS: StyleLint
- Test Driven Development:
This tools were selected based on my experience and perception of usability, can be noted that are a mixed, e.g. Typescript come from Microsoft, Jest come from React, Protractor come from Angular, among others Webpack, ESlint, etc., Although they came from "different worlds" together they provide a smooth, friendly and fast experience to the developer.
- Node/Npm, Node/Yarn or failing Java & Gradle.
- Git (only if the project is going to be cloned).
- Chromium or Chrome for E2E Tests.
Clone or download the project[1], in the desired folder execute:
git clone https://github.com/gmullerb/basecode-react-ts
or
git clone https://gitlab.com/gmullerb/basecode-react-ts
-
Replace Git
origin
with yours (lets say it ishttps://github.com/you/superb-react-ts-project
):
git remote rm origin
git remote add origin https://github.com/you/superb-react-ts-project.git
or
Just remove the .git
folder and start a git repository from 0, i.e. git init
.
3 . Remove footer from index.ejs.
4 . Substitute with your name (or company) in every // Copyright (c) 2020 Gonzalo MĂĽller Bravo.
Npm scripts, package.json
:
transpile
: transpile main source files.lint.common
: checks common style of "all" files.lint.config
: checks eslint style of config files [1].lint.src
: checks eslint style of main source files [1].lint.css
: checks style of.css
files.lint.test
: checks eslint style of test source files [1].start
: will build and run the Web App in Watch mode, and run the REST API Server.test
: runs Jasmine/Jest tests.test.watch
: runs Jasmine/Jest tests in Watch mode.- etc.
Additionally:
npm run check
: will execute all tasks (lint.common
, ...,test
, etc.).npm run
: will list all available script/task for the project.
[1] Using rules from eslint-plugin-base-style-config.
Run any scripts using ./gradlew npm_run_.name.
, where .name.
is the name of the npm script, e.g.:
lint.common
=> ./gradlew npm_run_lint.common
/config
/main
/test
/e2e
/src
/main
**/___tests/
/environments
/typings
/e2e
/server
-
config/main
: Configuration files for Main source code. -
config/test
: Configuration files for Tests. -
config/e2e
: Configuration files for E2E Tests. -
src/main
: Main source files. -
src/**/___tests/
: Test source files[1]. -
src/environments
: Environment files. -
src/typings
: Additional typings: Globals, CSS, png, and anything else that is required. -
e2e
: E2E Test source files. -
server
: Small REST API Server. -
build/
: is a generated folder where build results are placed and also tests and coverage reports.
[1] Tests folders are inside
src/main
mainly for Maintainability, 2 reasons: Keep Close (Easy to find the associated Tests) and Easy to Remove (When a component is removed, just remove the "folder"). There are several strategies defined in the project to avoid using or including these folders and Test files in final bundling.
-
Typescript for main files [1]:
*.ts
: Typescript main source files.*.tsx
: Typescript React main source files.
-
Javascript for test files [1]:
*.test.js
: Javascript Test (Unit and/or Integration) source files.*.test.jsx
: React test source files.*.e2e-test.js
: E2E test source files.
-
Folder's name should use
_
, but not-
or.
. -
Files' name should use
-
&.
, but not_
. [2] -
class in
variable.css
should be named using hyphen names [3], and -
in local
*.css
should be named using CamelCase [3][4].
As you can see this project has a "lot" of test, TDD is essential, so try to create Test:
- For new features, you can relax TDD, a little, i.e. start coding your functionality, then do some test for them if time is available.
- When fixing a bug, you Must be strict, i.e. create some test that "reproduce" the bug, then start coding.
- Mock only what is need it, i.e. db, complex service, etc. if is a "simple" function, don't spend time mocking that and gain the value of some integration.
[1] There is no need for using typings in Test files, seems unnecessary and in some cases can be "time" consuming and/or harmful.
[2] Prefer dot.notation than CamelCase for file new, why? this is not Java code, and avoid some OS/git naming "issues".
[3] So it can be easily figure out where the css come from.
[4] I defined a generic Typing for all css, that is better than using individual.css.d.ts
files, checkcss-declaration.d.ts
.
why not bootstrap? I was a fan of bootstrap for years, but it is too integrated with its own js libraries, i.e mixing React with jquery, doesn't make any sense. Prefer Only CSS libraries.
App uses:
- Asynchronous/Synchronous React Reducer Provider with Hooks: react-reducer-provider.
- React Router.
RoutedContainer.tsx
is where the Flux/Redux + Router magic happens, by combining:
-
and different
Route
s
Main Components:
- AsyncCounter.tsx uses a Local Asynchronous React Reducer (react-reducer-provider).
- RestEcho.tsx uses REST Services and CSS Modules.
Function components and Hooks are the preferred approach for React Components, is even encourage through linting.
- variable.css contains Global CSS values.
Since reducer functions tends to be big, extracting operation to its own module is recommended, leaving only a switch with a "lot" of function calls. This pattern will increase Readability, Maintainability and Testability:
e.g. :
async function reduce(prevState: DeepReadonly<CounterState>, action: CounterAction): Promise<DeepReadonly<CounterState>> {
switch (action) {
case CounterAction.UP:
return goUp(prevState)
case CounterAction.DOWN:
return goDown(prevState)
default:
return prevState
}
}
and its brain:
counterBrain.ts
:
async function goUp(prevState: DeepReadonly<CounterState>): Promise<DeepReadonly<CounterState>> {
return delay(750, {
value: {
count: prevState.count + 1,
updatedAt: new Date()
}
})
}
async function goDown(prevState: DeepReadonly<CounterState>): Promise<DeepReadonly<CounterState>> {
return delay(750, {
value: {
count: prevState.count - 1,
updatedAt: new Date()
}
})
}
(and it is applicable in other contexts)
Instead of using ref
everywhere, use name
s and extractor:
1 . Define a const enum
with the names of the fields:
const enum SomeFormFieldsName {
field1 = '@name1',
fieldN = '@nameN'
}
- Add a character that is not valid in a JS name to the field name, this way there is not possible to have any collision with any HTMLFormElement object member, .e.g. can not use
name
since this is a member of HTMLFormElement, then use@name
as a field name.
2 . Use in form fields:
<input
name={SomeFormFieldsName.field1}
type='text'
/>
<input
name={SomeFormFieldsName.fieldN}
type='text'
/>
3 . Define an extractor function:
function extractData(form: HTMLFormElement): SomeData {
return {
field1: (form[SomeFormFieldsName.field1] as HTMLInputElement).value
fieldN: (form[SomeFormFieldsName.fieldN] as HTMLInputElement).value
}
}
Optional . Define an processor function that stops propagation and default:
function processForm(event: React.FormEvent<HTMLFormElement>): HTMLFormElement {
event.preventDefault()
event.stopPropagation()
return event.currentTarget
}
4 . Use it onSubmit
:
<form
onSubmit={async event => handleSubmit(extractData(processForm(event)))}
>
Can be used with any type of form field, although in here is only shown with
input
.
Rememberref
allows to access the internal DOM of any control for the form and for the form itself.
This pattern will avoid using an state for every input in the form, i.e. React Controlled components, that seems totally unnecessary, since every HTML control in the form will have its own internal state provided by DOM, so why having 2 states for each control?.
Tests are presented as a starting point, i.e. tests may not cover all possible cases, neither Unit & Integration tests, nor E2E tests.
Coverage thresholds should be raise and/or sets to the specific project needs.
Test Files name ends with .test.js
and are written using mainly jasmine(and some jest) and enzyme, and run with jest [1].
[1] A mix of unit and integration tests is done, If desired then they could be separated as it is done in basecode - front.
E2E Test Files name ends with .e2e.js
and are written using jasmine and protractor.
Configuration is defined in: protractor.config.js
- E2E tests with Protractor requires
browser.ignoreSynchronization = true
to allow e2e for non-angular apps. - Two reporters are set:
SpecReporter
from jasmine-spec-reporter is set for console report.JUnitXmlReporter
from jasmine-reporters for XML report, mainly for CI.
- Headless chrome can be used, if desired:
capabilities: {
browserName: 'chrome',
chromeOptions: {
args: [ '--headless' ]
}
}
- If working as a root, then use No-sandbox Chrome [1]:
capabilities: {
browserName: 'chrome',
chromeOptions: {
args: [ '--no-sandbox' ]
}
}
[1] This can be set even if not running e2e as a root, useful for Local and CI.
Basically Webpack module rules are defined:
a. reactRule
for processing .tsx
files:
const reactRule = {
test: /\.tsx$/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-react", "@babel/preset-typescript"]
}
}
}
b. cssRule
to been able to import .css
files:
const cssRule = {
test: /\.css$/,
use: [
'style-loader',
{ loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
sourceMap: true
}
},
'postcss-loader'
]
}
CI Configuration can be checked at gitlab-ci.yml and Real results can be check at Gitlab pipelines.
- Code should be "self-documented", but add documentation were given complexity is high or misunderstanding may happened.
CHANGELOG.md
: ONLY to add information of NOTABLE changes for each version here, chronologically ordered [1].
[1] Keep a Changelog
- Use it.
- Share it.
- Give it a Star :start:.
- Request more documentation.
- Propose changes or improvements.
- Report bugs.
- Use code style verification tools => Encourages Best Practices, Efficiency, Readability and Learnability.
- Code Review everything => Encourages Functional suitability, Performance Efficiency and Teamwork.
- If viable, Start testing early => Encourages Reliability and Maintainability.
Don't forget:
- Love what you do.
- Learn everyday.
- Learn yourself.
- Share your knowledge.
- Think different!.
- Learn from the past, dream on the future, live and enjoy the present to the max!.
- Enjoy and Value the Quest (It's where you learn and grow).
At life:
- Let's act, not complain.
- Be flexible.
At work:
- Let's give solutions, not questions.
- Aim to simplicity not intellectualism.