Starting React Native Project in 2022
Tags: reactnative, expo, typescript
Published: 2021-12-26 20:34:00

> Updated to [Expo 46](https://blog.expo.dev/expo-sdk-46-c2a1655f63f7).

There are many ways to start new React Native project. Here we will be using [Expo CLI](https://docs.expo.dev/get-started/installation/) because it's easier and it has nice defaults out of the box. Also, we will add TypeScript, ESLint, Prettier, and some custom configurations that will make our development process better.

> **TLDR** You can use one command `expo init --template @vladimir-vovk/expo-bare-typescript` to create new React Native project with all tools already setup for you (see [README](https://github.com/vladimir-vovk/expo-bare-typescript) for details) or follow instructions below. 🤓

Please refer to official [React Native](https://reactnative.dev/docs/environment-setup) and [Expo](https://docs.expo.dev/) documentation for more details. 🤩

## General setup

We will need several tools before we start.

1. [Node.js](https://nodejs.org/en/).
2. [Git](https://git-scm.com/downloads).
3. [Yarn](https://classic.yarnpkg.com/en/docs/install#mac-stable).

Then install Expo CLI with `npm install --global expo-cli` or `yarn global add expo-cli`.

Awesome! 👍🏻 Now we have two options to start new React Native project with `Expo CLI`: "managed workflow" and "bare workflow". Let's briefly look into each variant further. For more in depth comparison please read the official [Expo doc](https://docs.expo.dev/introduction/managed-vs-bare/).

## Managed workflow

Managed workflow is the easiest way. Use it if you are new to mobile development and want to start developing your project right away without spending much time on setup and learning how to build native binaries for iOS and Android. 

1. Run `expo init` command.
2. Type your project name.
3. Choose `blank (TypeScript)` template.
4. Change directory to your project: `cd <your-project-name>`.
5. `yarn start` to start Metro Bundler.
6. Press `i` to start the iOS simulator or `a` to run the Android emulator.

## Bare workflow

Use bare workflow if you are already familiar with mobile development or want to use some libraries (native code) that is not supported by Expo. 

1. Run `expo init` command.
2. Type your project name.
3. Choose `minimal` template.
4. Change directory to your project: `cd <your-project-name>`.
5. Start Metro Bundler with `yarn start`.

Check [React Native Setup Guide](https://reactnative.dev/docs/environment-setup) to ensure that everything that needed to build binaries is installed on your machine.   

Use `yarn ios` or `yarn android` commands to build and run the app on the iOS simulator or Android emulator.📱

## Tools

### TypeScript

Let's add [TypeScript support](https://docs.expo.dev/guides/typescript/).

1. Create an empty `tsconfig.json` file in your project root: `touch tsconfig.json`.
2. Rename `App.js` to `App.tsx`: `mv App.js App.tsx`.
3. Run `yarn start`. It will prompt you to install the required dependencies (`typescript`, `@types/react`, `@types/react-native`), and automatically configure your `tsconfig.json`.


### Absolute path imports

To use absolute path imports, e.g. `import { ComponentA } from 'src/components/A'` (notice path starts with `src`), we need to add `baseUrl` and `paths` parameters to the `compilerOptions` of `tsconfig.json`.

```json
{
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
      "src/*": ["src/*"]
    }
  },
  ...
}
``` 

Also we need to create `src/package.json` file.

```json
{
  "name": "src"
}
```

Also, we will need to install the [babel-plugin-module-resolver](https://github.com/tleunen/babel-plugin-module-resolver) to be able to run our project on web. 

☝🏻 You can safely skip it, if you targeting only mobile platforms.

```bash
yarn add --dev babel-plugin-module-resolver
```

Specify the plugin configuration in `babel.config.js`:

```js
module.exports = function(api) {
  api.cache(true);
  return {
    // ... presets settings goes here
    plugins: [
      [
        'module-resolver',
        {
          root: ['.'],
          alias: {
            src: './src'
          }
        }
      ],
    ]
  };
};
```

### Move App.tsx to src folder

It's good to have all source files in one folder. So let's move `App.tsx` to `src` with `mv App.tsx src` command.

Next we need to fix import inside `index.js`.
```js
...
import App from 'src/App'
...
```

### Prettier

[Prettier](https://prettier.io/) is an opinionated code formatter. Let's install it with `npm install --save-dev prettier` or `yarn add -D prettier`. We will also need `.prettierrc.js` config file in the project root.

```js
module.exports = {
  semi: false,
  trailingComma: 'none',
  singleQuote: true,
  printWidth: 100,
  tabWidth: 2,
  useTabs: false,
}
```


### Sort imports

Unsorted imports look ugly. Also, it could be hard to read and add new imports. So why not sort them automatically? We can do it with [trivago/prettier-plugin-sort-imports](https://github.com/trivago/prettier-plugin-sort-imports).

```bash
yarn add --dev @trivago/prettier-plugin-sort-imports
```

Add plugin configuration to the Prettier config `.prettierrc.js`:

```js
module.exports = {
  // ... prettier config here

  importOrderSeparation: true,
  importOrderSortSpecifiers: true,
  importOrderCaseInsensitive: true,
  importOrder: [
    '<THIRD_PARTY_MODULES>',
    // '^(.*)/components/(.*)$', // Add any folders you want to be separate
    '^(.*)/(?!generated)(.*)/(.*)$', // Everything not generated
    '^(.*)/generated/(.*)$', // Everything generated
    '^[./]' // Absolute path imports
  ]
```


### Remove React imports

Starting from React 17 it's now [not necessary to import React](https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) to use JSX. To be able to use this new feature we need to update `babel.config.js` (line 4).

```js
module.exports = function(api) {
  api.cache(true);
  return {
    presets: [['babel-preset-expo', { jsxRuntime: 'automatic' }]]
    // ... plugins settings goes here
  };
};
```

And restart Metro Bundler. Press `Ctrl-C` to stop it and run `yarn start` to start Metro Bundler again.


### Check code for errors

We can use [TypeScript compiler](https://www.typescriptlang.org/tsconfig/#noEmit) and [ESLint](https://eslint.org/) for this.


#### TypeScript Compiler

Let's add new `check-typescript` script to our `package.json`.

```json
...
"scripts": {
  ...
  "check-typescript": "tsc --noEmit"
},
...
```

Now we can run `yarn check-typescript` command to check our code for errors with TypeScript compiler.


#### ESLint

ESLint has a lot configuration options and rules. Let's start with Expo [eslint-config-universe](https://github.com/expo/expo/tree/master/packages/eslint-config-universe) package.

```bash
yarn add --dev eslint-config-universe
yarn add --dev eslint prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser
yarn add --dev eslint-plugin-react-hooks
yarn add --dev eslint-import-resolver-typescript
```

Add `.eslintrc.js` config file to the project root.

```js
module.exports = {
  extends: ['universe', 'universe/shared/typescript-analysis', 'plugin:react-hooks/recommended'],
  overrides: [
    {
      files: ['*.ts', '*.tsx', '*.d.ts'],
      parserOptions: {
        project: './tsconfig.json'
      }
    }
  ],
  settings: {
    'import/resolver': {
      typescript: {} // this loads <rootdir>/tsconfig.json to ESLint
    }
  },
  /* for lint-staged */
  globals: {
    __dirname: true
  }
}

```

Add new `check-eslint` script to our `package.json`.

```json
...
"scripts": {
  ...
  "check-eslint": "eslint './src/**/*{js,ts,jsx,tsx}'"
},
...
```

Now we can run `yarn check-eslint` command to check our code for errors with ESLint. And `yarn check-eslint --fix` to fix errors automatically.


#### Lint script

Let's combine TypeScript and ESLint checks together so we can run both at once.

Add new `lint` script to our `package.json`.

```json
...
"scripts": {
  ...
  "lint": "yarn check-typescript && yarn check-eslint"
},
...
```


### Changelog

We can use [standard-version](https://github.com/conventional-changelog/standard-version) tool to generate a changelog, bump the version of app and create a new tag automatically.

How It Works:

- Follow the [Conventional Commits Specification](https://www.conventionalcommits.org/en/v1.0.0/) in your repository.
- When you're ready to release, run `standard-version`.

```bash
yarn add --dev standard-version
``` 

Create the `.versionrc.js` config:

```js
module.exports = {
  types: [
    { type: 'feat', section: 'New features' },
    { type: 'fix', section: 'Bug fixes' },
    { type: 'change', section: 'Changes' },
    { type: 'chore', hidden: true },
    { type: 'docs', hidden: true },
    { type: 'style', hidden: true },
    { type: 'perf', hidden: true },
    { type: 'test', hidden: true }
  ]
}
``` 

In this config, we enable the `feat`, `fix`, and `change` commit types. And if you want to enable the other commit types, you can remove the `hidden` boolean and replace it with the `section` string and provide a title.

Add new `release` script to `package.json`:

```json
...
"scripts": {
  ...
  "release": "standard-version"
},
...
```

Now when you ready to release, just run `yarn release` command. 


### Husky

> [Husky](https://github.com/typicode/husky) improves your commits and more 🐶 _woof_!

We will use Husky to check if our commit messages follow the [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) rules, run the lint check, format staged code with Prettier and ESLint.

```bash
yarn add --dev husky
yarn add --dev @commitlint/config-conventional @commitlint/cli
yarn add --dev lint-staged
```

Create a config for `commitlint` with `commitlint.config.js` file:

```js
module.exports = {
  extends: ['@commitlint/config-conventional']
}
```

Setup lint-staged with `package.json > lint-staged` configuration: 

```json
...
  "lint-staged": {
    "**/*.{js,jsx,ts,tsx}": [
      "eslint './src/**/*{js,ts,jsx,tsx}' --fix",
      "prettier --write './src/**/*{js,ts,jsx,tsx}'"
    ]
  },
...
```

Configure Husky, add `package.json > prepare` script and run it once:

```bash
npm set-script prepare "husky install"
npm run prepare
```

`husky install` will ensure that every developer using this repo will install Husky Hooks before using the project.

Add pre-commit hook:

```bash
npx husky add .husky/pre-commit 'npx --no-install lint-staged'
```

Add commit message hook:

```bash
npx husky add .husky/commit-msg 'yarn lint && npx --no-install commitlint --edit "$1"'
```


### react-native-safe-area-context

> A flexible way to handle [safe area](https://github.com/th3rdwave/react-native-safe-area-context), also works on Android and Web!

```bash
yarn add react-native-safe-area-context
npx pod-install
```

Wrap your `App` component with `SafeAreaProvider`:
```typescript
import { SafeAreaProvider } from 'react-native-safe-area-context'

function App() {
  return <SafeAreaProvider>...</SafeAreaProvider>
}
```

And now we can use `SafeAreaView` component.
> `SafeAreaView` is a regular `View` component with the safe area insets applied as padding or margin.


## What to add next?

- [React Navigation](https://reactnavigation.org/).
- [Async Storage](https://docs.expo.dev/versions/latest/sdk/async-storage/) or [Secure Storage](https://docs.expo.dev/versions/latest/sdk/securestore/).
- [Watermelon DB](https://nozbe.github.io/WatermelonDB/).
- Tests with [Jest](https://jestjs.io/) and [React Native Testing Library] (https://callstack.github.io/react-native-testing-library/).

---
Post your favourite tools in comments, press 💖 button and happy hacking! 🙌🏻