ACE coding style guide
These are the rules and best practices that will be checked for ACE Designer in the pull requests.
React
Use React.FC interface for components
Use React Functional components via React.FC interface, rather than Class components.
Motivation
Using React.FC interface for components guides the developer to return the proper React FunctionalComponent
structure.
While type inference works great, unless components built are actually used, this will trigger no warnings. It is also convenient to see warnings during component development, that returned structure is not correct, rather than to discover type error when writing the unit test.
Possible issue that is detected during compile/runtime
// no type check error
export const Example = () => "<></>"
Issue detected early on while constructing the component
// tsc detects, that returned type cannot be used by React
export const Example: React.FC = () => "<></>"
// as intended
export const Example: React.FC = () => <></>
Action handlers in React component props
Motivation
Code readability is better, when everything that is related to the particular function is located in the Component module.
When to pass action handlers in props
If component is reusable (for example FlowListItem).
When to create action handlers in module
If component is not reusable (for example FlowList).
React hooks
Use React Hooks to manage component state, apply effects.
Theme in UI Components
Use UI Theme colours, fonts, grid sizes for styling, rather than define them inline in the component style.
Redux
Redux hooks
Use Redux hooks to manipulate application state and retrieve information from application state.
Thunk error handling
await dispatched actions and handle response (either by checking if action is rejected or .unwrap() to throw
exceptions.
If dispatched action is intended to be fire and forget, then need to add comment to indicate that.
Cyclic dependency problem
In the scenario that the file defining the slice is importing an action, and the file defining the action is also importing an action from the file defining the slice, a cyclic dependency occurs. Cyclic dependencies currently break jest tests.
How to resolve cyclic dependencies
To resolve the cyclic dependency, the action coming from the slice has to be extracted. This can be done by manually
creating an action outside the file containing the slice by using createAction from @reduxjs/toolkit.
import { createSlice } from "@reduxjs/toolkit";
import {
myExtraAction,
+ action1
} from "./actions";
const mySlice = createSlice({
name: "slice",
initialState: {},
reducers: {
- action1: (state) => { state.flag1 = true; },
action2: (state) => { state.flag2 = true; }
},
extraReducers: (builder) => {
builder
.addCase(myExtraAction, (state, action) => {
state.flag2 = state.flag1;
})
+ .addCase(action1, (state, action) => {
+ state.flag1 = true;
+ })
},
});
export default settingsSlice.reducer;
export const {
- action1,
action2,
} = slice.actions;
import {
+ createAction,
createAsyncThunk
} from "@reduxjs/toolkit";
-import {
- action1
-} from "./reducers"
import {
myService
} from "../services"
+const action1 = createAction<{ id: string }>("slice/action1");
export const myExtraAction = createAsyncThunk(
"slice/myExtraAction",
async (_, { dispatch, getState }) => {
await myService();
dispatch(action1());
}
);
Cypress
Waiting till element is found
Don't unnecessary wait in tests.
Note - though cypress typically waits till element exists chainable commands may not work.
cy.get("some-selector").contains("some text) - in this example Cypress waits till it finds some-selector and then
searches some text, but if some-selector is not unique and exists in page, then Cypress fails to wait
till some text appears.
One way to fix it is to use cy.contains("some-selector", "some text)
Storybook
Storybook actions
Use Storybook actions to handle events instead
of console.log
Motivation
It allows to see actions immediately in Storybook without checking console
Javascript
Arrow functions vs functions
Motivation
This guide explains arrow function use cases and differences.
Performance vise Arrow functions are the same as regular Functions.
Use arrow functions when there is no intent to instantiate the Function
with new operator or use Function
instance scoped context (this).
When to use Function
If developer wants to leverage the function context (this) and imply that there will be multiple instances
of Cache Singletons, that will use different cache instances.
function Cache() {
this.cache = new Map()
this.getCachedValue = (key) => this.cache.get(key)
}
When to use Arrow Function
If developer knows that there will be only one cache per module/solution.
const cache = new Map()
const getCachedValue = (key: string) => {
return cache.get(key)
}
Naming conventions
Opinionated naming conventions in the project.
File names
- Folders should be in
kebab-case - File names should be in
camelCase.tsfor regular Typescript files. - Files which contains only class or type can use
PascalCase.ts - React components uses
PascalCase.tsxas per React convention. - React hooks and helper files should use
.tsxor.tsextension depending on whether it contains JSX or not
Method prefixes/suffixes
- Use
tryprefix only for getters that throwErroras part of normal, common execution flow (should be rare case, i.e.tryGetCacheValuewhich throwsErrorinstead of returningundefined). If getter is expected to return value in typical scenario, then use regulargetprefix and specifyundefinedas one of return types. - Use
orUndefinedfor cases where there are multiple getters with/withoutundefinedas return type. - Use
orIgnorefor setters which don't throwErrorif value is not set.
lodash imports
Use specific module import to import lodash methods.
Use
import foo from 'lodash/foo'
Don't use
import { foo } from 'lodash'
//or
import _ from 'lodash'
Application layout
- Generic components must stretch to the size of parent container.
- Generic components must not have margins and paddings beyond the need of cosmetic look.
- Application layout must be functional with screen width size of md (900px).