This document aims to provide general guidelines for development with React Native in Melon. The goals are to:
- make code easier to maintain and scale
- make it easier to read and quickly understand unfamiliar code
- reduce cognitive load while coding
It is good to have guidlines and rules but they can never be perfect and apply to every single project or code base. Think outside of the box and always strive for readability, simplicity, and clarity!
- Naming
- File names
- [React Components](#React Components)
- [Component Exports](#Component Exports)
- [Class Prefixes](#Class Prefixes)
- [React Fragments](#React Fragments)
- Variables
- [Mutable vs Immutable](#Mutable vs Immutable)
- [Global Constants](#Global Constants)
- Styles
- [Props destructure](#Props destructure)
- [Props drilling](#Props drilling)
- [Optional props](#Optional props)
- [Default state values](#Default state values)
- [Access modifiers](#Access modifiers)
- SafeAreaView
- Ordering
- Keys
- [Custom hooks](#Custom hooks)
- Comments
- [Absolute imports](#Absolute imports)
- [Unused Code](#Unused Code)
- [General Rules](#General Rules)
- [Custom hooks](#Custom hooks)
- [Creating new Project](#Creating new Project)
- References
Descriptive and consistent naming makes software easier to read and understand.
- Component files and folders should always be named after the exported component inside them.
src/*
folders should belowercase
- Component styles should be in a
styles.ts
file unless they have dynamic data and need to be in the component instead. - Every component dir should have
index.ts
file that exports the component. - Always use
eslint
to keep the code formated and consistent between different developers and their IDE
React components should be functional because there is less boilerplate code and they are faster. They are simple objects instead of classes with internal functionality.
//Good
const Component: React.FC = () => {
return (
<Child/>
)
}
//Good
const Component: React.FC = () => <Child/>
//Avoid
class Component extends React.Component => {
render() {
return (
...
)
}
}
Named exports should be used due to their strict and explicit import syntax.
//Possible
export {Component} from 'components/Component'
//Possible
export {Component as NewName} from 'components/Component'
//Impossible
export {NewName} from 'components/Component'
Now in the following example we are going to use default export and show that the component Component
can be actually imported with any name we want, which introduces a layer of possible human error.
//Possible
export Component from 'components/Component'
//Possible
export NewName from 'components/Component'
//Possible
export Anything from 'components/Component'
This also forces the reference naming to be explicitly like the component name which means that the PascalCase will be 100% followed
Possible named export reference namings.
//Possible
import {NewComponent} from './NewComponent.tsx'
//Impossible
import {newComponent} from './NewComponent.tsx'
import {newcomponent} from './NewComponent.tsx'
import {Newcomponent} from './NewComponent.tsx'
Possible default export reference namings.
//Possible
import NewComponent from './NewComponent.tsx'
import newComponent from './NewComponent.tsx'
import newcomponent from './NewComponent.tsx'
import Newcomponent from './NewComponent.tsx'
React fragments <></>
should be used when possible, instead of <View/>
because it doesn't create a node in the DOM tree. This means that the DOM tree won't be cluttered with unnecessary elements.
//Good
const Component: React.FC = () => {
return (
<>
<Child/>
<Child/>
</>
)
}
//Good
const Component: React.FC = () => {
return (
<View style={styles.children}>
<Child/>
<Child/>
<View/>
)
}
//Avoid
const Component: React.FC = () => {
return (
<View>
<Child/>
<Child/>
<View/>
)
}
//Avoid
const Component: React.FC = () => {
return (
<>
<View style={styles.children}>
<Child/>
<Child/>
<View>
</>
)
}
Avoid using vars
, their scope might be problematic when it comes to React and it's VDOM re-rendering. Use const
and let
instead.
//Good
const filteredItems = items.filter(...)
let filteredItems = items.filter(...)
//Avoid
var filteredItems = items.filter(...)
Avoid mutating variables, it's exteremly dangerous and can lead to problematic situations like not re-rendering. Use immutable data or useState
instead.
//Good
const [state, setState] = useState(DEFAULT_STATE)
setState(newState)
//Avoid
const [state, setState] = useState(DEFAULT_STATE)
state = newState
//Avoid
let state = DEFAULT_STATE
state = newState
Unlike the other variables, global constants are all caps with underscore syntax to distinguish the difference easily. Ex: ALL_CAPS
.
//Good
const HOME_SCREEN = "Home screen"
//Avoid
const HomeScreen = "Home screen"
const Home_Screen = "Home screen"
const homeScreen = "Home screen"
const HOMESCREEN = "Home screen"
Make sure to freeze variables with data type of objects and arrays, because they might be mutated which can also lead to unwanted situations.
//Good
const DEFAULT_OPTIONS = Object.freeze([1,2,3,4])
//Avoid
const DEFAULT_OPTIONS = [1,2,3,4]
Avoid inline styles or const
variables without StyleSheet.create
.
//Good
const styles = StyleSheet.create({
background: {
backgroundColor: 'black'
}
})
<Component style={styles.background} />
//Avoid
<Component style={{backgroundColor: 'black'}} />
//Avoid
const styles = {
background: {
backgroundColor: 'black'
}
}
<Component style={styles.background} />
Destructuring the props object increases the readability and makes it easier to detect unused props.
//Good
export const Component: React.FC<ComponentProps> = ({id, name, surname}) => {
return (
<>
<Text>{id}</Text>
<Text>{name}</Text>
<Text>{surname}</Text>
</>
)
}
//Avoid
export const Component: React.FC = (props: ComponentProps) => {
return (
<>
<Text>{props.id}</Text>
<Text>{props.name}</Text>
<Text>{props.surname}</Text>
</>
)
}
//Avoid
export const Component: React.FC = (props: ComponentProps) => {
const {id, name, surname} = props
return (
<>
<Text>{id}</Text>
<Text>{name}</Text>
<Text>{surname}</Text>
</>
)
}
Props drilling should be used only when necessary with the proper names to keep the declerative syntax as readable as possible.
In this example we pass the same function to two different props because it makes it obvious where this function will be called and makes it scalable for the future, if we want to pass another function for update we just need to replace that prop.
//Good
<Component
onClickUpdate={editUser}
onClickAdd={editUser}
/>
In this second example we pass the function to only 1 prop, however we have no idea where it's used and in how many places, this decreases the readability, and ruins the scalability of the component.
//Avoid
<Component
editUser={editUser}
/>
Declare interface props as optional with ?
where a undefined
value is acceptable and the prop is not requied to be passed down to the component.
interface Names {
surname?: string;
}
//Possible
<Names surname="Surname" />
//Possible
<Names surname={undefined} />
//Possible
<Names />
If the prop might be undefined
but you want it to be always required, you should use the following syntax instead
interface Names {
surname: string | undefined;
}
//Possible
<Names surname="Surname" />
//Possible
<Names surname={undefined} />
//Impossible
<Names />
Avoid using undefined
or null
for default values and use the appropriate default value of that given type.
//Good
const [counter, useCounter] = useState<number>(0)
//Good
const [counter, useCounter] = useState<number>()
//Avoid
const [counter, useCounter] = useState<number | null>(null)
//Avoid
const [counter, useCounter] = useState<number | undefined>(undefined)
Access modifiers are not needed in React because of the functional components and their declerative syntax, it's not possible to expose any of the variables even if they are public.
All of the screens should be wrapped in SafeAreaView
to render the content within the safe area boundaries of the devices.
- 1 Imports
- 2 Interfaces / Types
- 3 Global variables
- 4 Component
- 4.1 Hooks
- 4.2 Variables
- 4.3 Functions
- 4.4
return
//Good
import {Button, Text} from 'react-native'
import styles from './styles.ts'
interface ComponentProps {
id?: string;
}
interface Names {
name: string;
surname: string;
}
export const Component: React.FC<ComponentProps> = ({id}) => {
const navigation = useNavigation()
const [names, setNames] = useState<Names>({
name: "",
surname: ""
})
useEffect(() => {
...
}, [])
const doSomething = () => {
...
}
return (
<>
<Text>Something</Text>
<Button onClick={doSomething}>Click me!</Button>
</>
)
}
Avoid using index
when it comes to keys, they are not unique enough and can lead to complex situations. For example, if we are to iterate 2 arrays and use their indexes as keys, we would have duplicated keys. You may use them if the array items don't have stable ids, however it's not safe and other id should be used instead.
//Good
return (
<>
{users.map((user, index) => {
return (
<View key={user.id}>
...
</View>
)
})}
</>
)
//Avoid
return (
<>
{users.map((user, index) => {
return (
<View key={index}>
...
</View>
)
})}
</>
)
Always name your hooks with use
prefix and camelCase
syntax
//Good
export const useBoolean = () => {
...
}
//Avoid
export const UseBoolean = () => {
...
}
//Avoid
export const dynamicBoolean = () => {
...
}
-
When they are needed, use comments to explain why a particular piece of code does something. Comments must be kept up-to-date or deleted.
-
Avoid block comments inline with code, as the code should be as self-documenting as possible.
-
Use
/** + Enter
to generate automatic documentation for functions, hooks and components. This will allow the IDE to automatically show the necessary information on hover.
//Good
/**
* Hook that allows the user to handle boolean value easily.
* @param defaultValue - Optional parameter that can be used to set the initial value to `true`.
* @returns boolean
* @example const {bool, setTrue, setFalse} = useBoolean(true)
*/
export const useBoolean = (defaultValue: boolean) => {
...
}
//Avoid
//Hook that allows the user to handle boolean value easily.
export const useBoolean = (defaultValue: boolean) => {
...
}
Always use absolute import paths instead of relative ones, they are much better when it comes to project scalability and they work in case of directory change.
If we change the location of the component importing the code below, the imports would still work becase they are using the absolute path which is always the same.
//Good
import {ChildComponent} from "components/ChildComponent"
import {useBoolean} from "hooks/useBoolean"
However if we were to move this component in another directory, for example one level above, the code below won't work because of the relative import path.
//Avoid
import {ChildComponent} from "../../components/ChildComponent"
import {useBoolean} from "../../hooks/useBoolean"
Unused (dead) code, including React Native template code and placeholder comments should be removed. Imported files that contain unused code on global level will increase the bundle size.
- Avoid Class components.
- use
PascalCase
for components and types,camelCase
for everything else - prefix custom hooks with
use
. Ex:useModal
- prefix callback props with
on
. Ex:onConfirm
- Name booleans like
isSpaceship
,hasSpacesuit
, etc. This makes it clear that they are booleans and not other types.
Warning! Make sure you meet all of the requirements listed in the official React Native environment setup before you start creating a new project.
Warning! Do not use any special characters or spaces in the name of the project when replacing PROJECT_NAME
. Use -
to separate multiple words. Ex: melon-react-app
Creating a new React CLI project is quite simple. Just run the following command:
npx react-native init PROJECT-NAME --template react-native-template-typescript
If you want to generate the project with expo, run the following commands instead:
-
yarn global add expo-cli
ornpm install --global expo-cli
-
expo init PROJECT-NAME -t expo-template-blank-typescript