This style guide was created to provide standards based on the fundamentals of React, Javascript and several tools used to make development easier (eg prettier, eslint, typescript). This style guide was also developed based on several existing style guides such as Khan and AirBnB React Style Guide.
- Export Component: Only export one react element per file.
- Format Component: Use functional component (hooks) instead of class component.
- Code Formatter: Use
prettierfor code formatter. - Code Rules: Use
eslintfor code rules. - Static Typing: Use
typescriptfor static typing. - Aliases: Use
babel-plugin-module-resolverto create aliases for the domain.
- Extensions: Use
.tsxextension for file contains React Element and.tsextension for file javascript without React Element.
- Location: All module components located in
./src/components/ - Inheritance: Extends the component properties from its parrent.
import React, {ReactNode} from 'react'; import {Text as TextRN, TextProps as TextPropsRN} from 'react-native' export interface TextProps extends TextPropsRN { extensionKeyProps: string; children: ReactNode; // more extension properties ... } export default function Text({extensionKeyProps, children, ...props}: TextProps) { // handle the extension properties ... return <Text {...props}>{children}</Text> }
- Aliases: Use
@prefix for the module aliases. E.g.,@components - Export: All functions and components must exported and handled in index.ts until reach it's domain. Because, to implement aliases we need to access all exported functions and components from it's domain.
// File Input Component (./src/components/inputs/Input.tsx) export default function Input() { } // File index in inputs folder (./src/components/inputs/index.ts) export * from './Input'; export * from './InputSearch'; export {default as Input} from './Input'; export {default as InputSearch} from './InputSearch'; // File index in components folder (./src/components/index.ts) export * from './inputs'; export * from './buttons'; export * from './modals';
- Import: Sort import module based on precedence. (react -> external library -> aliases -> folder -> file).
import React from 'react'; import {ModalProps} from 'react-native-modal'; import {Button} from '@components'; import {getUserData} from '@services'; import {LocationListProps} from './location'; import MapDetail from './MapDetail';
- Constant: Use
UPPERCASEfor exported constant field andcamelCasefor the otherwise. (Field is variable located in root files)// good export const BUTTON_PADDING = 10; // bad export const buttonPadding = 10; // bad export const ButtonPadding = 10; // bad const COLUMNS = 3; // good const columns = 3; // bad const Columns = 3;
- Let: Use
camelCasefor let variable. And don't export let variable. Use getter and setter or context for accessing let variable outside file.// bad let COUNT = 0; // bad let Count = 0; // good let count = 0;
- Var: Don't use
varfor the variable. This is because the var variable can be accessed from outside the block which causes confusion and is difficult to trace.
- String, Number and Boolean: Use
string|number|booleaninstead ofString|Number|Boolean// good const name = "Charlotte" // good // declaring the type is optional, because typescript will // automatically declare the type same as the type of initial value. const name: string = "Charlotte" // bad const name: String = "Charlotte"
- Type:
- Used For: Abstract Type, Static String Values, Combine Multiple Type Data.
- Basic: Use
PascalCasefor the syntax. And usesnake_casefor type string values.// good type NavigationKey = 'auth_login' | 'auth_register'; // bad type NavigationKey = 'authLogin' | 'authRegister'; // bad type NAVIGATION_KEY = 'authLogin' | 'authRegister';
- Abstraction: Create type data abstract.
// good type Keys = 'key_1' | 'key_2' | 'key_3'; type KeyMap<V> = {[K in Keys]: V}; const maps: KeyMap<string> = { key_1: 'value_1', key_2: 'value_2', key_3: 'value_3', }
- Exception: Sometimes type structure and values adjusted from outsource. E.g. icon names.
// good // type adjusted from icon name svg files. type IconType = 'chevron-right' | 'ellipsis-h' | 'ellipsis-v';
- Interface:
- Used For: Custom Type Data Object.
- Basic: Use
PascalCasefor the syntax. And usecamelCasefor the properties.Exception: Sometimes we need to adjust type data from outsource. E.g. Responses API.// good interface ButtonProps { label: string; labelStyle?: TextStyle; onPress(): void; } // bad interface buttonProps { label: string; labelStyle?: TextStyle; onPress(): void; } // bad interface ButtonProps { Label: string; LabelStyle?: TextStyle; OnPress(): void; }
// good // data structure adjusted from response api. interface UserDataResponse { id: number; username: string; is_verified: boolean; }
- Array:
- Basic: Use literal syntaxt to create array and declare the type data array if the initial value is empty.
// bad const values = new Array(); // bad const values = []; // good const values = [0]; // good const values = ['', 0]; // good const values: number[] = [];
- Populate Values: Use
Array.fromto populate array values.// good const values = Array.from(['Charlotte', 'Celdric'], (item, index) => `${index}: ${item}`); // good const values = Array.from({length: 5}, (_, index) => index); // bad const values = []; for (let index = 0; index < 5; index++) { values.push(index); }
- Combine Multiple Type Data: Type can be used to combine multiple type data.
type ImageSource = string | number;
- Basic: Use literal syntaxt to create array and declare the type data array if the initial value is empty.
- Object: Use literal syntax to create objects and object must declare the type data from the interface or type. Because, typescript cannot define the type data used of the object except if it's handled by generic. The other reason is also to provide tab-completer from the editor.
// bad const user = new Object(); // bad const user = {}; // bad const user = {id: 0, name: 'Charlotte'}; // good const user: User = {id: 0, name: 'Charlotte'};
- Used For: Components, Helpers, Custom Hooks and Services.
- Naming Convention: use
camelCasefor function.// bad function DoSomething() { } // good function doSomething() { }
- Used For: Callbacks.
- Single Line: Don't use block scope for arrow function that only have one line.
// bad return <Input {...props} onFocus={() => { callSomething(); }} /> // good return <Input {...props} onFocus={() => callSomething()} />
- Immediate Return: Don't use block scope to return value if only have one line logic or return.
// bad return <MyComponent {...props} value={() => { return sum; }} /> // good return <MyComponent {...props} value={() => sum} /> // bad return <MyComponent {...props} value={(count) => { if (timestamp < now) { return previousCount; } else { return count; } }} /> // good return <MyComponent {...props} value={(count) => timestamp < now ? previousCount : count} />
- Keys: Keys must be defined first through type or interface. Because it will be used for all locale and.
- Structures: Key are grouped based on scope, feature and function. (general -> category feature -> feature -> function -> specific function -> properties).
// good global_error: "Error" global_login: "Login" // good input_search_label: "Search" input_search_placeholder: "ex. Chocolate" // bad // label and placeholder is part property of input. search is a specific function of input. input_label_search: "Search" input_placeholder_search: "ex. Chocolate" // good // auth is category feature where login is the specific feature of auth. auth_login_input_username_placeholder: "Username" auth_login_input_password_placeholder: "Password" // good // in this example login is dissapeared, this mean all input username and password for auth will use this translation as default. auth_input_username_placeholder: "Username" auth_input_password_placeholder: "Password"