Starting React Native Project in 2024
Tags: reactnative, typescript, expo
Published: 2023-01-06 15:42:13

> Updated to [Expo 50](https://expo.dev/changelog/2024/01-18-sdk-50).

There are many ways to start a [new React Native](https://reactnative.dev/docs/environment-setup) project. Here we will be using Expo's [create-expo-app](https://docs.expo.dev/get-started/create-a-new-app/) command line tool because:
- It has nice defaults out of the box.
- It is compatible with Expo packages, which are usually very good, well-tested, and have excellent documentation.
- We can easily switch to a "bare" react-native app at any moment with one `npx expo eject` command. See the [docs](https://docs.expo.dev/introduction/managed-vs-bare/) to learn the difference between managed and bare workflows.

Also, we will add TypeScript, ESLint, Prettier, and some custom configurations that will make our development process better.

> **TLDR** You can use one command `npx create-expo-app -t expo-ts` to create a new React Native project with all tools already set up for you (see [README](https://github.com/vladimir-vovk/expo-ts) for details) or follow instructions below. 🤓

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


## General setup

We will need [Node.js](https://nodejs.org), [Git](https://git-scm.com/downloads) and [Yarn](https://classic.yarnpkg.com/en/docs/install#mac-stable) before we start.

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

Awesome! 👍🏻 
Now let's create a new app.

1. Run `npx create-expo-app` command. 
2. Type your project name.
3. Change the directory to your project with `cd <your-project-name>` command.
4. Run `yarn start` to start Metro Bundler.
5. Press `i` to start the iOS simulator or `a` to run the 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/*"]
    },
    "strictNullChecks": true
  },
  ...
}
``` 

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 the 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 create a new `App.js` file inside our project's folder with `touch App.js`. And import our app logic there:
```js
import App from 'src/App'

export default () => <App />
```


### Prettier

[Prettier](https://prettier.io/) is an opinionated code formatter. Let's install it.

```bash
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
    '^src/(.*)$',
    '^(.*)/(?!generated)(.*)/(.*)$', // Everything not generated
    '^(.*)/generated/(.*)$', // Everything generated
    '^[./]' // Absolute path imports
  ]
}
```


### 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 a 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 the TypeScript compiler.


#### ESLint

ESLint has a lot of configuration options and rules. Let's start with the 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 @typescript-eslint/eslint-plugin @typescript-eslint/parser
yarn add --dev eslint-plugin-react-hooks
yarn add --dev eslint-import-resolver-typescript
yarn add --dev eslint-plugin-prettier
```

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
  },
  rules: {
    'no-console': 'error'
  }
}

```

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

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

Now we can run the `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 a new `lint` script to our `package.json`.

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


### Changelog

We can use the [standard-version](https://github.com/conventional-changelog/standard-version) tool to generate a changelog, bump the version of the 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 are ready to release, just run the `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, and 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 with:

```bash
npx husky init
```

> The `init` command simplifies setting up husky in a project. It creates a `pre-commit` script in `.husky/` and updates the prepare script in `package.json`.

To add a pre-commit hook we need to replace everything inside the `.husky/pre-commit` file with:

```bash
npx --no-install lint-staged
```

To add a commit message hook we need to create the `.husky/commit-msg` file with:

```bash
yarn lint && npx --no-install commitlint --edit "$1"
```


### SafeAreaContext

> [react-native-safe-area-context](https://docs.expo.dev/versions/latest/sdk/safe-area-context/) provides a flexible API for accessing device-safe area inset information.

```bash
npx expo install react-native-safe-area-context
```

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 the `SafeAreaView` component.
> `SafeAreaView` is a regular `View` component with the safe area insets applied as padding or margin.


### Test with Jest and React Native Testing Library

> [Jest](https://docs.expo.dev/guides/testing-with-jest) is a delightful JavaScript Testing Framework with a focus on simplicity.

```bash
npx expo install jest-expo jest
yarn add --dev @types/jest
```

Update the `package.json` to include:

```json
"scripts": {
  ...
  "test": "jest"
}
```

Jest has a number of globally-available functions, so we need to introduce these functions to ESLint with [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest).

```bash
yarn add --dev eslint-plugin-jest
```

Add `'jest'` to the `plugins` section of the `.eslintrc.js` configuration file. We can omit the `eslint-plugin-` prefix:

```js
module.exports = {
  ...
  plugins: ['jest']
}
```

> The [React Native Testing Library](https://github.com/callstack/react-native-testing-library) helps you to write better tests with less effort and encourages good testing practices.

> The [jest-native](https://github.com/testing-library/jest-native) library provides a set of custom jest matchers that you can use to extend jest. These will make your tests more declarative, clear to read, and maintain.

```bash
yarn add --dev @testing-library/react-native
yarn add --dev @testing-library/jest-native
yarn add --dev @testing-library/dom
```

Now it is time to add the `jest.config.js` configuration file:

```js
/** @type {import('jest').Config} */
const path = require('path')

const config = {
  preset: 'jest-expo',
  setupFilesAfterEnv: [path.join(__dirname, 'setup-testing.js')],
  transformIgnorePatterns: [
    'node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg)'
  ]
}

module.exports = config
```

And `setup-testing.js` configuration file to make the `jest-native` library work:

```js
import '@testing-library/jest-native/extend-expect'
```

Let's also add [eslint-plugin-testing-library](https://github.com/testing-library/eslint-plugin-testing-library) and [eslint-plugin-jest-dom](https://github.com/testing-library/eslint-plugin-jest-dom). Both are ESLint plugins that help to follow best practices and anticipate common mistakes when writing tests with Testing Library. For more info, see the excellent [Common mistakes with React Testing Library](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#not-using-testing-library-eslint-plugins) article by Kent C. Dodds. 🤓

```bash
yarn add --dev eslint-plugin-testing-library
yarn add --dev eslint-plugin-jest-dom
```

Add `'testing-library'` to the `plugins` section and `'plugin:testing-library/react'` to the `extends` section of our `.eslintrc.js` configuration file:

```js
module.exports = {
  ...
  extends: ['plugin:testing-library/react', 'plugin:jest-dom/recommended'],
  plugins: ['testing-library']
}
```

Now we can write our first test to the `src/App.test.tsx` file:

```tsx
import { render, screen } from '@testing-library/react-native'

import App from 'src/App'

describe('App', () => {
  it('should mount without errors', () => {
    expect(() => render(<App />)).not.toThrow()
  })

  it('should unmount without errors', () => {
    render(<App />)
    expect(() => screen.unmount()).not.toThrow()
  })
})
```

And run it with the `yarn test` command.


## 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/).


## Credits

Photo by <a href="https://unsplash.com/@spacex?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">SpaceX</a> on <a href="https://unsplash.com/s/photos/rocket?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>.

You can find the 👴🏻 2022 version of this article [here](https://dev.to/vladimirvovk/starting-a-react-native-project-in-2022-31m7). 


---
Please post your favorite tools in the comments, press the 💖 button, and happy hacking! 🙌🏻