- File -
PascalCase
.tsx - Class -
PascalCase
- Enum -
PascalCase
enum DoobooScreen { Ios, Android, HelloWorld, TestDevice }
- Constants -
UPPER_SNAKE_CASE
- Object, classes, variables and functions -
camelCase
- Asset file name -
lower_snake_case
.png
Put domain as a prefix to filename.
// Do
CompanyAdd.tsx
CompanyEdit.tsx
// Don't
AddCompany.tsx
EditCompany.tsx
For component,
// Do
function CompanyAdd() {}
function CompanyEdit() {}
// Do
const onAddCompany = () => {}
const onDeleteCompany = () => {}
// Don't
const onCompanyAdd = () => {}
const onCompanyDelete = () => {}
Usually, developers put all boolean prefix with is
and it often causes confusion. For example below.
const isFriend = () => user.friends.length > 0;
Above code is not quite readable. It'd be better to choose the right prefix. In this case, hasFriend
makes sense.
There are a few other prefixes you can choose to write instead of "is". Below are other options we can recommend.
- has
- should
Also, we prefer avoiding prefixes in boolean if it looks clear enough to stand by themselves. For examples, we think loading
, disabled
, checked
are better without prefixes.
When the function returns Promise
or is an async function, we prefer adding suffix Async
.
// Do
function readAsync() {
return new Promise();
}
async function readAsync() {
await fetch();
return;
}
class User {
// Don't
void addUser() {
}
// Do
void add() {
}
void addMessage() {
}
}
Assets are all kinds of resources used in applications. They should not stay at the same level with sourcecode.
YourApp/
├─ assets
│ └─ icons
│ └─ images
│ └─ localizations
├─ src/
Config files should be separated from the sourcecode.
YourApp/
├─ src/
├─ .buckconfig
├─ .flowconfig
├─ .gitattributes
├─ .gitignore
├─ .watchmanconfig
├─ app.json
├─ babel.config.js
├─ index.js
├─ jest.config.js
├─ package.json
├─ README.md
├─ tsconfig.json
└─ eslint.config.js
The config files for jest, babel, eslint, typescript, flow, git are all separated from the
src
directory.
YourApp/
├─ src/
│ └─ components/
│ └─ pages/
│ └─ HelloWorld.tsx
│ └─ uis/
│ └─ providers/
├─ test/
│ └─ components/
│ └─ pages
│ └─ HelloWorld.test.tsx
Test files should all lie under
test
dir.
YourApp/
├─ src/
│ └─ utils/
│ └─ localize.ts
│ └─ localize.spec.ts <=== Unit test may stay along with the original file
YourApp/
├─ __mocks__/
│ └─ reanimated-masonry-list.ts
3-1-1. Follows the dooboolab eslint package
Follow the import rules of @dooboo/eslint-config-react
for React and @dooboo/eslint-config-react-native
for React Native.
If you don't manage your asset paths, you'll often face problems like the one below.
Module not found: Error: Can't resolve '../icons/btn_add'
Module not found: Error: Can't resolve '../../icons/btn_back'
Module not found: Error: Can't resolve '../../../icons/camera'
Instead, we recommend organizing assets in one file like below and export them.
import icAddW from '../../assets/icons/btn_add.png';
import icBack from '../../assets/icons/btn_back.png';
import icCamera from '../../assets/icons/camera.png';
import icCircleX from '../../assets/icons/x_circle.png';
export const IC_ADD_W = icAddW;
export const IC_BACK = icBack;
export const IC_CAMERA = icCamera;
export const IC_CIRCLE_X = icCircleX;
// Don't
function Page({}: Props) {}
// Do
function Page({}: Props): ReactElement {}
// Don't
const Page = () => {};
class Page extends ReactComponent {}
// Do
function Page({}: Props): ReactElement {}
// don't
const onPress = () => {
doSomething();
};
// do
const onPress = () => doSomething('work');
const onPress = doSomething;
type Styles = {
container?: StyleProp<ViewStyle>;
text?: StyleProp<TextStyle>;
disabledButton?: StyleProp<ViewStyle>;
disabledText?: StyleProp<TextStyle>;
hovered?: StyleProp<ViewStyle>;
};
function Button({
styles,
style,
...
});
You can see the example in Button L62-L63. This lets developers check what kind of styles are nested in the component. It is also easy to handle reusable component's style without knowing what styles are nested with the
style
prop. The user would want to change itsmargin
orpadding
.
// Don't
if (checkStatus(user) === 'busy') {
message = 'User is busy!';
} else if (checkStatus(user) !== 'busy') {
message = 'User is not busy!';
if (availableForCall(user)) {
call("010-xxxx-xxxx");
} else {
call("119");
}
}
// Do
if (checkStatus(user) !== 'busy') {
message = 'User is not busy!';
availableForCall(user) ? call('010-xxxx-xxxx') : call('119');
return;
}
message = 'User is busy';
Replace nested conditional with guard clauses makes your statements more concise and readable.
// Don't
availableForCall(user) ? call('010-xxxx-xxxx') : call('119');
// Do
call(availableForCall(user) ? '010-xxxx-xxxx' : '119');
Try to remove the duplication function calls if possible.
Table driven method helps you build better software with decision tables in place of if-else
.
// Don't
public string GetMontName(int month, string language) {
if (month == 1 && language == "EN") {
return "January";
} else if (month == 1 && language == "DA") {
return "Januar";
} else if (month == 1 && language == "KO") {
return "1월"
} else if (month == 2 && language == "EN") {
return "Feburary";
} else if (month == 2 && language == "DA") {
return "Feburar";
} else if (month == 2 && language == "KO") {
return "2월";
} else {
return "Unknown";
}
}
// Do
const monthDictionary = {
"EN": {
1: "January",
2: "Faburary",
},
"DA": {
1: "Januar",
2: "Feburar",
},
"KO": {
1: "1월",
2: "2월",
},
}
public string GetMonthName(string language, int month) => monthDictionary[language][month];
Refer to article.
Extracting function is often used to make code more readable.
// Don't
const checkString = (str: string) => {
let subString = string.slice();
let counter = 0;
while (subString !== '') {
counter++;
subString = subString.slice(1);
}
setString(`${str}: ${counter}`);
}
// Do
function getStringLengthWithoutLengthProperty(string) {
let subString = string.slice();
let counter = 0;
while (subString !== '') {
counter++;
subString = subString.slice(1);
}
return counter;
}
const checkString = (str: string) => {
const strLength = getStringLengthWithoutLengthProperty(str);
setString(`${str}: ${counter}`);
}
// Do
const setYoutubeVideo = async (url: string): Promise<void> => {
if (urlType === 'youtube') {
const videoURL = await getYouTubeDetails(url);
if (videoURL) {
setYoutubeURL(videoURL);
}
}
};
setYoutubeVideo(url);
In the above
if (str.length % 2)
code, the intention of the code is unknown until the developer who is new to the code dissects the logic. In this case, you need to extract the function and make it more meaningful. Also, youtube video tells how to extract functions from vscode more easily.
// Do
function Page({}: Props): ReactElement {
return (
<Container>
<Text>Page</Text>
</Container>
);
};
Also, see templates in dooboo-cli.
// don't
{list.length && <div>{list.map((item) => (...))}</div>}
// do
{list.length ? <div>{list.map((item) => (...))}</div> : null}
Find out more in the link for the decision.
If there are same conditions used multiple times inside props, extract them to conditional statement like below.
// Don't
function Parent() {
const [isHighlighted, setIsHighlighted] = useState(false);
return (
<div>
<Child
style={...{isHighlighted && {color: '#fff'}}}
headerColor={isHighlighted ? 'red' : 'blue'}
/>
</div>
);
}
// Do
function Parent() {
const [isHighlighted, setIsHighlighted] = useState(false);
if(isHighlighted) {
return (
<div>
<Child
style={{color: '#fff'}}
headerColor='red'
/>
</div>
)
}
return (
<div>
<Child headerColor='blue' />
</div>
);
}
Here,
isHighlighted
is used more than once inChild
component props. Extracting them makes it more maintainable because we can concentrate on the target code instead of the side effect. Also note that using&&
operator is good in props like{isHighlighted && {color: '#fff'}
unlike 3-5-2.
Try to make props as simple as possible but flexible. You can able to achieve this by justifying minimal requirements and handing over authority to users if more specs are required.
For example, below Button
has type loading
and disabled
and have title
and onPress
function.
type Props = {
type?: 'loading' | 'disabled' | undefined,
onPress?: () => {}
title: string
}
function Button({
type,
onPress,
title,
}: Props): ReactElement {
if (type === 'loading') {
return ...
}
if (type === 'disabled') {
return ...
}
return (
<TouchableOpacity onPress={onPress}>
<Text>{title}</Text>
</TouchableOpacity>
);
};
If the above code is the minimal requirement to use in the product, keep it the way it is and expose the possibility with the render callback function instead of exposing more and more props.
type Props = {
type?: 'loading' | 'disabled' | undefined,
onPress?: () => {}
render?: (type: 'loading' | 'disabled' | undefined) => ReactElement
title: string
}
function Button({
type,
onPress,
title,
onPress
}: Props): ReactElement {
if (type === 'loading') {
return ...
}
if (type === 'disabled') {
return ...
}
return (
<TouchableOpacity onPress={onPress}>
{render?.(type) || <Text>{title}</Text>}
</TouchableOpacity>
);
};
Current habits will help developers prevent producing abstruse components as the possibility grows.
// Do
<Button textStyle={textStyle} />
// Don't
<Button textColor={textColor} />
Exposing a single attribute in style props leads to creating not only complicated but also unmaintainable components. Example flutter_calendar_carousel.
If ternary operator is used multiple times when rendering, it hurts readability.
function Button({
type,
onPress,
title,
onPress
}: Props): ReactElement {
// Don't
return type === 'loading' ? ... : type === 'disabled' ? ... : return ...
// Do
// 1. If statement
if (type === 'loading') return ...;
if (type === 'disabled') return ...;
return ...;
// 2. Switch statement
switch (type) {
case 'loading':
return ...
case 'disabled':
return ...
default:
return ...
}
// 3. Good with single condition
return loading ? ... : return ...
}
Use the rest
term when extracting the rest of the props with the spread operator as shown below.
This is to unify communication by limiting terms that can be used in various ways such as otherProps, restProps
.
function Button({
type,
onPress,
title,
onPress
...rest,
}: Props): ReactElement {
Please do not use the render
function if unnecessarily, as shown below.
function Sample() {
const renderHello = () => {
return <Text>HELLO</Text>;
}
return <>
{renderHello()}
</>
}
Replace render functions element.
function Sample() {
const Hello = <Text>HELLO</Text>;
return <>
{Hello}
</>
}
// Don't
<View style={{
backgroundColor: 'red',
}}/>
// Do
<View style={{backgroundColor: 'red'}}/>
Never abstract the business logic in a reusable component. Deliver them to the business manager.
// Don't
function Button(): ReactElement {
return (
// Don't
<TouchableOpacity onPress={() => {
console.log('Hello I am doing something!!!');
}}>
{render?.(type) || <Text>{title}</Text>}
</TouchableOpacity>
);
};
// Do
function Button(): ReactElement {
return (
// Don't
<TouchableOpacity onPress={onPress}>
{render?.(type) || <Text>{title}</Text>}
</TouchableOpacity>
);
};
When UI components have multiple nested child components, try refactoring with provider. We prefer recoil in this case.
// Don't => But only when needed
interface Props {}
// Do
type Props = {}
// Don't
const name = useState<string>('');
// Do
const name = useState('');