Commit messages must be clear and easily readable, respecting the convention of Conventional Commits.
In short, a commit header lists a summary of its changes preceded by a keyword
indicating the type of
change (fix
, feat
, docs
, test
, style
, chore
, refactor
, etc, read
the Conventional Commits
and @commitlint/config-conventional
docs for explanations on each type), followed optionally by a scope in
parentheses ()
, then by :
and the summary of the changes.
Optionally, a body can be added below the first line (with an empty line for readability) to fully describe the changes.
Optionally, a BREAKING CHANGE can be indicated by a !
next to the type keyword
and described in a footer with BREAKING CHANGE: description
(with an empty
line for readability).
Examples :
feat: added stuff to the thing #12345
chore: removed unused file
- full example with body and BREAKING CHANGE indicated by
!
and in footer :
fix!: prevent racing of requests
Introduce a request id and a reference to latest request. Dismiss
incoming responses other than from latest request.
Remove timeouts which were used to mitigate the racing issue but are
obsolete now.
BREAKING CHANGE: this changes the endpoint of the thing which breaks the frontend
feat(css): added stuff to the other thing #12345
A related US/Work Item/Ticket from another tool can sometimes be linked to a
particular commit, in this case it will be indicated in the scope ()
, such
as feat(redmine xxx): ...
Developer branch names follow a similar convention, with a folder prefix such
as feat
, fix
, docs
, refactor
, chore
, etc. followed by a simple name
related to the change, examples :
feat/pagination
, fix/action-types
, refactor/header-search
A developer branch will typically be rebased on the current version branch (
example: x.x.6
) before making a Pull Request in order to avoid conflicts.
Squashing your local branch before rebasing can simplify the rebase, as you will
only have to resolve 1 commit.
Once you are ready to make your Pull Request, see Submitting a Pull Request in the Contributing guide.
In general, the recommended approach is to look at existing code and follow the same naming patterns. A few notable examples :
- Boolean variables and props should be named with the verb "is" or "has", such
as
isVisible
,isOpened
,hasTopBar
,hasBackground
- Variables and props representing a default value (such as the default value of
a prop or the default value of a
useState
) should start with "default", such asdefaultColumn
,defaultStartingIndex
,defaultVisible
. - Variables and props representing callbacks, most commonly events or callbacks
triggered by interaction, should start with "on", such
as
onChange
,onButtonClick
,onToggleSidebar
,onMenuOpen
. - Functions called by binded events, such as a function called after
a
onChange
event or other interactions, should start with "handle" such ashandleChange
,handleSelectInput
,handleSubmit
,handleToggleSidebar
.
For components :
- When possible, components should be created using the
npm run generate
command which automates a lot of the creation ( see Creating a new component in Haring). - Components are contained in the
src/Components/
folder. - Component folders and Component files should be written in
PascalCase
. - Component test files are suffixed by
.test.tsx
. - Component Storybook files are suffixed
.stories.tsx
. - If the test or storybook file contains testing data or a lot of declarations
for props, these can be put into a separate mock file suffixed
.mock.tsx
. - Optionally, styles created with createStyles can be declared in a separate
style file suffixed with
.style.tsx
. - Components are always declared as exported functions with typescript typing
and a return type such
as
export function MyComponent(): ReactElement {...}
. - If a Component has any props, a typed interface for the props has to be declared.
- Whenever you add a new Component, it must be added to the export
index.tsx
file at the root of/src
.
For interfaces and types :
- Interfaces and Types are always prefixed with
I
, such asIMyInterface
. - Component props interfaces are always suffixed with
Props
such asIMyComponentProps
.
The code in Components is structured in this way :
import { ReactNode } from 'react';
// 1. Styles (here or in a separate style file)
const useStyles = createStyles((theme) => ({
myClass: {
width: '120px',
},
}));
// 2. Types and Interfaces (alphabetically ordered)
export interface IMyComponentProps {
children?: ReactNode;
propA?: boolean;
propB?: boolean;
}
export function MyComponent(props: IMyComponentProps): ReactNode {
// 3. Props extraction
const { propsA, propsB, ...rest } = props;
// 4. Component data: Constants and variables (such as data calculated or derived from props),
// useStates, etc..
const isBothTrue = propA && propB;
const [isVisible, setIsVisible] = useState(false);
// 5. Classes extraction and styles/themes
const { classes } = useStyles();
const defaultTheme = useMantineTheme();
// 5. Functions (such as parsing or handle functions, handleChange, handleInput)
function handleClick(): number {
return 0;
}
// 6. The return, typically a JSX/TSX fragment.
return <div>...</div>;
}
In general, the recommended approach is to avoid unnecessary comments. Naming conventions for Components, functions and constants should make the code easy to understand.
When a particular piece of code could be confusing and cannot be written in a clearer way, short and concise comments are recommended.
For the autodoc that will be displayed on Storybook, each Component should have a comment above the Component declaration :
/** This comment will be displayed on the Storybook page of this component, under the title */
export function MyComponent(props: IMyComponentProps): void {
return null;
}
Component props can also be documented in this way :
export interface MyComponentProps {
/** This comment will be displayed as the description of this prop on the Storybook page */
children?: ReactNode;
/** This comment will be displayed as the description of this prop on the Storybook page */
isVisible?: boolean;
}
This project has a few linters configured : ESLint with a custom configuration
in the eslint-config-custom
package, and Prettier.
You must follow all of ESLint's custom configuration (errors and warnings), which can be stricter than the standard configurations.
For anything else, follow these guidelines :
-
Create helper functions and reusable code for common or repeated tasks, modular classes and functions (one function = one task), components splits into sub-components if necessary. SOLID principles.
-
Allow for configuration and translation. Anticipate and integrate future extensions and configurations of your code. Anticipate translation or easy modification of static content or text content.
-
Use shortened syntaxes (arrow function, ternary notation, etc.) with care and make sure they are readable and clear in how they work. If it stays readable, a shorter block or line of code is preferred.
-
Avoid heavy nesting, especially in loops.
-
Optimize your loops.
-
Do not trust data. Always assume that every user input (or even data from APIs) can be incorrect or empty/null/undefined, and might require sanitizing/validating or error handling.
-
Do not reinvent the wheel, don't hesitate to use tried-and-trusted existing solutions.
As well as the Typescript Do's and Don'ts.