Skip to content

A base code to start developing React apps with Typescript.

Notifications You must be signed in to change notification settings

gmullerb/basecode-react-ts

Repository files navigation

Base code for a Web project in React and Typescript

License Github repo Gitlab repo coverage

A base code to start developing React apps with Typescript.


Quick Start

  1. 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

  2. Run npm install + npm run check or ./gradlew to install npm dependencies and to check the project.

  3. Run npm start or ./gradlew npm_start to run the project.

  4. Open browser at localhost:8080.

  5. Make it yours.

  6. Start customizing it with your own ideas.


Goals

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.

Features

  • 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.

Inside the Code

Tools been used

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.

Prerequisites

Getting it

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

[1] Cloning a repository

Make it yours

  1. Create your repository at Github, Gitlab or any other.

  2. Replace Git origin with yours (lets say it is https://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.

Running it

Npm

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.

From Gradle

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

Folders structure

  /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.

Conventions

  • 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, check css-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 Structure

App uses:

RoutedContainer.tsx is where the Flux/Redux + Router magic happens, by combining:

Main Components:

Function components and Hooks are the preferred approach for React Components, is even encourage through linting.

Brain pattern

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:

Brain pattern

e.g. :

CounterActions.ts:

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)

Extract Form pattern

Instead of using ref everywhere, use names 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.
Remember ref 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

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.

Unit & Integration Test

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

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.

Webpack configuration

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

CI Configuration can be checked at gitlab-ci.yml and Real results can be check at Gitlab pipelines.


Documentation

  • 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

Contributing

License

MIT License


Remember

  • 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.

Additional words

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.