Updated to Expo 52.
We will use Expo's create-expo-app
command recommended by the docs.
Also, we will add 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 for details) or follow instructions below. 🤓
Please refer to the official React Native and Expo documentation for more details. 🤩
General setup
We will need the Node.js and Git installed before we start.
Please check the docs to ensure everything is installed on your machine.
Now let's create a new app.
- Run
npx create-expo-app@latest --template blank-typescript
command. - Type your project name.
- Change the directory to your project with
cd <your-project-name>
command. - Run
npm run start
to start Metro Bundler. - Press
i
to start the iOS simulator ora
to run the Android emulator.📱
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
parameter to the compilerOptions
of tsconfig.json
.
{
"compilerOptions": {
"baseUrl": "./"
},
...
}
Move App.tsx to src folder
It's good to have all source files in one place. So let's move App.tsx
to src
with mv App.tsx src
command.
Next, we need to change the index.ts
file inside our project's root to load the App
component from the src
folder:
import { registerRootComponent } from 'expo'
import App from 'src/App'
registerRootComponent(App)
Check code for errors
We can use TypeScript compiler and ESLint for this.
TypeScript Compiler
Let's add a new check-typescript
script to our package.json
.
...
"scripts": {
...
"check-typescript": "tsc --noEmit"
},
...
Now we can run the npm run check-typescript
command to check our code for errors with the TypeScript compiler.
ESLint
ESLint is a JavaScript linter that helps you find and fix errors in your code. It's a great tool to help you write better code and catch mistakes before they make it to production. In conjunction, you can use Prettier, a code formatter that ensures all the code files follow a consistent styling.
npx expo lint
If you're using VS Code, install the ESLint extension to lint your code as you type.
Add a new check-eslint
script to our package.json
.
...
"scripts": {
...
"check-eslint": "eslint './src/**/*{js,ts,jsx,tsx}'"
},
...
Now we can run the npm run check-eslint
command to check our code for errors with ESLint. And npm run check-eslint --fix
to fix errors automatically.
Lint script
Let's combine TypeScript and ESLint checks so we can run both at once.
Add a new lint
script to our package.json
.
...
"scripts": {
...
"lint": "npm run check-typescript && npm run check-eslint"
},
...
Prettier
Prettier is an opinionated code formatter. Let's install it.
npx expo install -- --save-dev prettier
We will also need .prettierrc.js
config file in the project root.
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.
npm add --save-dev @trivago/prettier-plugin-sort-imports
Add plugin configuration to the Prettier config .prettierrc.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
]
}
Changelog
We can use the 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 in your repository.
- When you're ready to release, run
standard-version
.
npm add --save-dev standard-version
Create the .versionrc.js
config:
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 }
]
}
We enabled the feat
, fix
, and change
commit types. 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
:
...
"scripts": {
...
"release": "standard-version"
},
...
Now we can run the npm run release
command to get the changelog.
Husky
Husky improves your commits and more 🐶 woof!
We will use Husky to check if our commit messages follow the conventional commits rules, run the lint check, and format staged code with Prettier and ESLint.
npm add --save-dev husky
npm add --save-dev @commitlint/config-conventional @commitlint/cli
npm add --save-dev lint-staged
Create a config for commitlint
with commitlint.config.js
file:
module.exports = {
extends: ['@commitlint/config-conventional']
}
Setup lint-staged with package.json > lint-staged
configuration:
...
"lint-staged": {
"**/*.{js,jsx,ts,tsx}": [
"eslint './src/**/*{js,ts,jsx,tsx}' --fix",
"prettier --write './src/**/*{js,ts,jsx,tsx}'"
]
},
...
We can add the "no-console" rule to the .eslintrc.js
to check that no console.log
s are committed:
module.exports = {
...
/* for lint-staged */
globals: {
__dirname: true
},
rules: {
'no-console': 'error'
}
...
}
Configure Husky with:
npx husky init
The
init
command simplifies setting up Husky in a project. It creates apre-commit
script in.husky/
and updates the prepare script inpackage.json
.
To add a pre-commit hook we need to replace everything inside the .husky/pre-commit
file with:
npx --no-install lint-staged
To add a commit message hook we need to create the .husky/commit-msg
file with:
npm run lint && npx --no-install commitlint --edit "$1"
SafeAreaContext
react-native-safe-area-context provides a flexible API for accessing device-safe area inset information.
npx expo install react-native-safe-area-context
Wrap your App
component with SafeAreaProvider
:
import { SafeAreaProvider } from 'react-native-safe-area-context'
function App() {
return <SafeAreaProvider>...</SafeAreaProvider>
}
And now we can use the SafeAreaView
component.
SafeAreaView
is a regularView
component with the safe area insets applied as padding or margin.
Test with Jest and React Native Testing Library
Jest is a delightful JavaScript Testing Framework with a focus on simplicity.
npx expo install -- --save-dev jest-expo jest
npm add --save-dev @types/jest
Update the package.json
to include:
"scripts": {
...
"test": "jest"
}
Jest has a number of globally-available functions, so we need to introduce these functions to ESLint with eslint-plugin-jest.
npm add --save-dev eslint-plugin-jest
Add 'jest'
to the plugins
section of the .eslintrc.js
configuration file. We can omit the eslint-plugin-
prefix:
module.exports = {
...
plugins: ['jest']
}
The React Native Testing Library helps you to write better tests with less effort and encourages good testing practices.
The 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.
npm add --save-dev @testing-library/react-native
npm add --save-dev @testing-library/jest-native
npm add --save-dev @testing-library/dom
Now it is time to add the jest.config.js
configuration file:
/** @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)'
],
moduleDirectories: ['node_modules', '<rootDir>']
}
module.exports = config
And setup-testing.js
configuration file to make the jest-native
library work:
import '@testing-library/jest-native/extend-expect'
Let's also add eslint-plugin-testing-library and 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 article by Kent C. Dodds. 🤓
npm add --save-dev eslint-plugin-testing-library
npm add --save-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:
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:
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 npm run test
command.
Credits
Photo by Bill Jelen on Unsplash
Please post your favorite tools in the comments, press the 💖 button, and happy hacking! 🙌🏻
Top comments (0)