React conventies - RWS-NL/air-node-packages GitHub Wiki
React Redux Naming Conventions
Table of contents
This document will lay out the conventions that we adhere in the React / Redux code in the frontend of AIR applications. Please take note that this is aimed at what are called "new" frontend apps, written in TypeScript and making use of Redux-Saga as middleware. While these conventions can be applied to the classic frontend of air, some divergencies will have to be made.
Folder Structure
We use a folder structure based on create-react-app in the frontend applications, mixed in with common practices from various online articles as well as our own preferences. The common folder structure is as follows. [Any text between means template for a real file name]
:
.
├── .editorconfig
├── .env
├── .env.stubrunner
├── .eslintrc
├── .git-authors
├── .gitattributes
├── .gitignore
├── .stylelintignore
├── .stylelintrc
├── .vscode
│ ├── [other-config].json
│ └── typescriptreact.json.code-snippets
├── Procfile
├── README.md
├── __tests__
│ ├── e2e
│ │ ├── [Test].test.ts
│ │ └── index.test.ts
│ ├── jest-environment.js
│ ├── jest-framework.ts
│ ├── jest-puppeteer-config.ts
│ ├── puppeteer-setup.ts
│ ├── screenshots # Gets autogenerated
│ └── smoke
│ └── smoke.test.ts
├── babel.config.js
├── ci
│ ├── app.yml
│ ├── scripts
│ │ ├── build-and-test.sh
│ │ ├── pr-build-and-test.sh
│ │ └── smoke-test.sh
│ └── tasks
│ ├── build-and-test.yml
│ ├── pr-build-and-test.yml
│ └── smoke-test.yml
├── docker-compose.yml
├── jest.config.js
├── manifest.yml
├── package.json
├── public
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
├── scripts
│ ├── [other-helper-script].[ext]
│ ├── run-airfrontend.sh
│ ├── run-backend.sh
│ ├── validateNoOnly.ts
│ └── yarnAfterPull.ts
├── src
│ ├── App.tsx
│ ├── __tests__
│ │ ├── App.test.tsx
│ │ ├── components
│ │ │ ├── [Component].test.tsx
│ │ │ └── presentational
│ │ │ └── [PresentationalComponent].test.tsx
│ │ ├── config
│ │ │ └── i18n.test.tsx
│ │ ├── containers
│ │ │ ├── AppBase.test.tsx
│ │ │ ├── [Container].test.tsx
│ │ │ └── __snapshots__ # Gets autogenerated
│ │ │ ├── [Container].test.tsx.snap # Gets autogenerated
│ │ │ └── AppBase.test.tsx.snap # Gets autogenerated
│ │ ├── store
│ │ │ ├── navigation
│ │ │ │ ├── __snapshots__ # Gets autogenerated
│ │ │ │ │ └── navigationReducer.test.ts.snap # Gets autogenerated
│ │ │ │ └── navigationReducer.test.ts
│ │ │ ├── oauth2
│ │ │ │ ├── StateProvider.test.ts
│ │ │ │ ├── __snapshots__ # Gets autogenerated
│ │ │ │ │ └── oauth2Reducer.test.ts.snap # Gets autogenerated
│ │ │ │ ├── oauth2ImplicitFlow.test.ts
│ │ │ │ ├── oauth2Reducer.test.ts
│ │ │ │ └── oauth2saga.test.ts
│ │ │ └── [storeSection]
│ │ │ ├── __snapshots__ # Gets autogenerated
│ │ │ │ └── [test].test.ts.snap # Gets autogenerated
│ │ │ ├── [storeSection]Reducer.test.ts
│ │ │ └── [storeSection]Sagas.test.ts
│ │ ├── stubrunner.test.ts
│ │ └── testSupport.tsx
│ ├── components
│ │ ├── [Component].tsx
│ │ └── presentational
│ │ └── [PresentationalComponent]
│ ├── config
│ │ ├── Router.tsx
│ │ ├── i18n
│ │ │ ├── en.json
│ │ │ └── nl.json
│ │ ├── i18n.ts
│ │ ├── localoauth2.json
│ │ ├── mime.types
│ │ ├── nginx.conf
│ │ └── routes.ts
│ ├── containers
│ │ ├── AppBase.tsx
│ │ └── [Container].tsx
│ ├── contexts
│ │ ├── [OtherContext].ts
│ │ ├── NavigationContext.ts
│ │ └── PermissionContext.ts
│ ├── index.tsx
│ ├── react-app-env.d.ts
│ ├── setupTests.ts
│ ├── store
│ │ ├── index.ts
│ │ ├── navigation
│ │ │ ├── navigationActions.ts
│ │ │ ├── navigationReducer.ts
│ │ │ └── navigationTypes.ts
│ │ ├── oauth2
│ │ │ ├── StateProvider.ts
│ │ │ ├── oauth2Actions.ts
│ │ │ ├── oauth2ImplicitFlow.ts
│ │ │ ├── oauth2Reducer.ts
│ │ │ ├── oauth2Saga.ts
│ │ │ └── oauth2Types.ts
│ │ ├── [storeSection]
│ │ │ ├── [storeSection]Actions.ts
│ │ │ ├── [storeSection]Reducer.ts
│ │ │ ├── [storeSection]Sagas.ts
│ │ │ └── [storeSection]Types.ts
│ │ ├── root-action.ts
│ │ ├── root-reducer.ts
│ │ ├── root-sagas.ts
│ │ ├── types.d.ts
│ │ └── utils.ts
│ ├── styles
│ │ ├── font
│ │ │ ├── heading
│ │ │ │ └── regular
│ │ │ │ └── rijksoverheidsansheading-regular_2_0-webfont.woff
│ │ │ └── text
│ │ │ ├── bold
│ │ │ │ └── rijksoverheidsanswebtext-bold-webfont.woff
│ │ │ ├── italic
│ │ │ │ └── rijksoverheidsanswebtext-italic-webfont.woff
│ │ │ └── regular
│ │ │ └── rijksoverheidsanswebtext-regular-webfont.woff
│ │ ├── images
│ │ │ ├── icons
│ │ │ │ └── [name].svg
│ │ │ └── [name].svg
│ │ ├── modules
│ │ │ ├── Common.module.scss
│ │ │ ├── [Component].module.scss
│ │ │ ├── OAuth2.module.scss
│ │ │ └── polyfills.module.scss
│ │ ├── styles.scss
│ │ └── variables
│ │ ├── _colors.scss
│ │ └── _vars.scss
│ └── utils
│ ├── WindowLocationService.ts
│ ├── apiClient.ts
│ ├── apiPaths.ts
│ ├── constants.ts
│ ├── helpers.ts
│ └── linkWithParams.ts
├── tsconfig.eslint.json
├── tsconfig.json
└── yarn.lock
File Naming
- datadropActions, datadropReducer etc.
- PascalCase Components
- camelCase typeScript code + index.tsx
- ComponentName.Module.scss for SCSS modules
- Any Redux related file starts with its subsection of the store (for example
users
orprojects
) followed by one ofActions
,Reducer
,Saga
, orTypes
.- An exclusion goes to OAuth2 where the names
StateProvider
andoauth2ImplicitFlow
are also allowed
- An exclusion goes to OAuth2 where the names
- All React Components have the
.tsx
extension - All non-React Component TypeScript files have the
.ts
extension - All React Components should be PascalCased, for example
NewUserForm
- An exclusion goes to
index.tsx
(which is the app's entry file)
- An exclusion goes to
- All TypeScript files (non-React Components) should be camelCased (a.k.a. Dromedary Case)
- Exclusions go to
WindowLocationService
as service class
- Exclusions go to
- CSS Modules have the naming scheme of
[ComponentName].module.scss
, for exampleProject.module.scss
Redux Naming Structure
State Types
- using Readonly<> for state
ActionTypes Enum
HANDLE_
prefix@@subsection
prefixGET/DELETE/POST
etc. afterHANDLE_
SUCCESS
postfix for storing to state- Exception to oauth2
Action function names
- camelCase name dupe of actiontype
Saga function names
- camelCase name dupe of actiontype minus
handle_
Action Handling
- useEffect
- bindActionCreators
Reducer Handling
- createReducer
- spread syntax
Saga Handling
- When Saga's are called
- using of takeEvery
- typing of actions in saga (when need to access payload)
- typing of actions in component (what goes into mapDispatchToProps)
Component Styling
We strive to follow the Sass Guidelines, found at: https://sass-guidelin.es/
Colors
All colours used throughout the application are set in the src/styles/variables/_colors.scss
. When applying colours in other stylesheets, always refer to a colour defined in this colour stylesheet file. The name should start with an underscore so it is entirely skipped in the compilation of the SCSS and only those parts that are used in other files are included.
Vars
The vars file holds all variables used throughout the applications. It is split into a few subsections defined by // comments
. The vars file is located at src/styles/variables/_vars.scss
. The name should start with an underscore so it is entirely skipped in the compilation of the SCSS and only those parts that are used in other files are included.
Placeholders
Placeholders allow us to define re-usable styles without variables that can be used throughout our stylesheets. Whenever you do need variables, always use mixins instead.
Placeholders (and mixins) make it easy to avoid using non-semantic classes (like .class-normalFont), and to distribute collections of styles in libraries. To use a placeholder, @extend
the placeholder in the style class, for example:
.class {
@extend %placeholderName
}
Mixins
Mixins allow us to define re-usable styles with variables that can be used throughout our stylesheet. Whenever you do not need variables, always use placeholders instead.
Mixins (and placeholders) make it easy to avoid using non-semantic classes (like .class-normalFont), and to distribute collections of styles in libraries. To use a mixin, @include
the mixin in the style class, for example:
.class {
@include mixinName
}
Constants
Constants that can be applied throughout the entire SCSS code for reusability, for example the font-family or font-size
Default Overwrites
Overwrite defaults defined in external libraries here, for example defaults used by react-redux-toastr. The code in the library is defined with the SCSS syntax of !default
, which means it is assumed the SCSS to use unless it is overwritten in our library-using code.
SCSS Modules
CSS Modules have the naming scheme of [ComponentName].module.scss
and can be found in the src/styles/modules
folder. Each React Component that needs styling applied should have its own CSS Module.
Exceptions are made for:
- Styles that are shared between React Components. In this case the these styles should be placed inside the
Common.module.scss
that is found in the same directory as the other CSS modules. - Styles that have to do with Polyfills are found in
polyfills.module.scss
.
To start using a module inside a React Component, first import it by using import css from 'styles/modules/[ComponentName].module.scss';
.
SCSS Modules should look like:
@import '../variables/colors';
@import '../variables/mixins';
.classStyle {
background-color: $very-light-grey;
&NestingStyle {
font-size: 0.8rem;
}
&NestingStyle > element {
color: $blue;
}
}
%placeholderClass {
height: 1rem;
width: 100vw;
}
.classStyleExtending {
@extend %placeholderClass;
}
.classIncludeMixin {
@include someMixin
}
Standardization
SCSS Syntax
- Placeholders over Mixins unles u
- Something about moving to webcomponents