Our Initial ReactJS/TypeScript setup

Initial project creation

The command to create initial frontend project is:

npx create-react-app frontend --template typescript.

This will create the project in ./frontend directory.

While create-react-app initializes the project, you have plenty of time to get fresh cup of coffee/tea.

Change everything as development dependency

You should go and edit ./package.json so that everything in the dependencies section is located at devDependencies. We will not have any runtime dependencies since the project is compiled as a single bundle.

Setup IDEA's configuration not to be in git

You should append .idea to .gitignore, even if you are not using IDEA. If .idea is already in git, please remove it completely from git first.

Install core dependencies

Open a terminal and execute:

npm i --save-dev \
   node-sass@4.14.1 '@types/node-sass' \
   lodash '@types/lodash' \
   'moment-timezone' '@types/moment-timezone'

Install our core TypeScript library

You should also install our core TypeScript library:

mkdir -p src/fi/hg
git submodule add git@github.com:heusalagroup/fi.hg.core.git src/fi/hg/core
git config -f .gitmodules submodule.src/fi/hg/core.branch main
git add .gitmodules

Rename CSS files as SCSS

Now, you should rename *.css as *.scss.

If you use IDEA, it should automatically replace paths in imports and exports correctly.

Expert note: Configure shortcut (or Apple TouchBar action) to Rename files.

Move App to its own subdirectory

Next, you should move App.* to directory src/components/app.

Same here, you shouldn't need to manually fix imports/exports.

Expert note: Configure shortcut (or Apple TouchBar action) to Move files.

Remove default exports

The project builder will initialize the project with some default exports. Refactor out of these.

We don't use default exports. Default exports are not compatible between different project builders. Using them will make your life miserable for example when trying to bundle your frontend inside a server side rendering NodeJS server.

For example, change code like this:

import ReactDOM from 'react-dom/client';

...to:

import { createRoot } from 'react-dom/client';

Also remove the default export from generated ./App.tsx.

Create constant file for class names

The default template will implement App component using class name as App.

It's best practice to create a single file src/constants/classNames.ts where you define all of your class names in one place.

Create this file and add the App class there:

export const APP_CLASS_NAME = 'hg-app';

Then use it in your App.tsx code:

<div className={APP_CLASS_NAME}>...</div>

Keep in mind that the name must match with the component's name. Also the class constant must always go to the top level element in your component. Otherwise you make messy code that's slow to debug and develop later.

Any class name in your component must be unique. For example, never do this:

return (
  <div className={APP_CLASS_NAME}>
    <h3 className={APP_CLASS_NAME}>Title</h3>
  </div>
);

Instead, do this:

return (
  <div className={APP_CLASS_NAME}>
    <h3 className={`${APP_CLASS_NAME}-title`}>Title</h3>
  </div>
);

Setup Dockerfile

Create file ./Dockerfile with contents:

ARG NODE_IMAGE=node:14

FROM $NODE_IMAGE as node-image

FROM node-image

ARG DEFAULT_REQUEST_CLIENT_MODE=WINDOW
ARG DEFAULT_NODE_ENV=production
ARG DEFAULT_GENERATE_SOURCEMAP=false
ARG DEFAULT_PUBLIC_URL=http://localhost:3000

ENV PATH=/app/node_modules/.bin:$PATH
ENV REACT_APP_REQUEST_CLIENT_MODE=$DEFAULT_REQUEST_CLIENT_MODE
ENV REACT_APP_PUBLIC_URL=$DEFAULT_PUBLIC_URL
ENV NODE_ENV=$DEFAULT_NODE_ENV
ENV GENERATE_SOURCEMAP=$DEFAULT_GENERATE_SOURCEMAP

WORKDIR /app
COPY ./package*.json ./
RUN npm ci --silent --also=dev
RUN npm rebuild node-sass --sass-binary-name=linux-x64-83
COPY tsconfig.json ./tsconfig.json
#COPY config ./config
#COPY scripts ./scripts
COPY public ./public
COPY src ./src
#COPY --from=nor-shared-image /app/shared/src ./src/shared
RUN npm run build

If you are going to be using a backend, set up a development proxy now

First, install http-proxy-middleware:

npm i --save-dev http-proxy-middleware

Create a file named src/setupProxy.js and append this there:

const REACT_APP_BACKEND_TARGET_URL = process?.env?.REACT_APP_BACKEND_TARGET_URL ?? 'http://localhost:3500';

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
    app.use(
        '/api',
        createProxyMiddleware({
            target: REACT_APP_BACKEND_TARGET_URL,
            changeOrigin: true,
            autoRewrite: true,
            //protocolRewrite: 'http',
            pathRewrite: {
                ['^/api'] : ''
            }
        })
    );
};

Now, you can control where /api/... is forwarded:

REACT_APP_BACKEND_TARGET_URL='http://localhost:8080' npm start

This will redirect http://localhost:3000/api to http://localhost:8080.

REACT_APP_BACKEND_TARGET_URL defaults to http://localhost:3500.

Test if the system runs

You can test if the app starts:

npm start

Sometimes IDEA fails to fix import/exports, so fix them manually now.

Make sure everything is in git

Finally, make sure everything is committed to git and push your initial working version now.