This project is deprecated
Expo's gotten better at making it easy to create new projects with this stuff configured.
I'd start with
npx create-expo-app@latest --template blank-typescript
Then check out their using eslint and prettier docs and well as their manual installation steps for adding expo-router.
Begin your new expo app projects with some pep; that is, including:
- Prettier
- Typescript
- Eslint
- React Navigation
- Absolute imports relative to your
src/
directory
Here is also a blog post with more about the motivation for creating pep.
gem install pep
pep my-app-name # this will take a minute and you'll see yarn output
cd my-app-name
yarn start
Now, edit your src/App.tsx
file.
Prettier, typescript, eslint, and react navigation are all added to your project and set up in discrete commits; The way you'd do it by hand if you spent the extra time.
The best way to inspect the changes pep
makes to a default Expo project is by creating a new project with pep
and inspecting the project's git log.
We'll cover other ways to peek ahead and inspect the default configurations ahead of time, if that's your thing 😉
You can view all the changes pep makes to add prettier to a default Expo project here.
You can view the default prettier configuration here.
Once you've created a project with pep
, you can view or edit the prettier configuration by opening the hidden dotfile named .prettierrc
You can view all the changes pep makes to add eslint to a default Expo project here.
You can view the default eslint configuration here.
Once you've created a project with pep
, you can view or edit the eslint configuration by opening the hidden dotfile named .eslintrc.js
You can view all the changes pep makes to configure typescript in a default Expo project here.
You can view the default changes made to expo's default typescript configuration here.
Once you've created a project with pep
, you can view or edit the tyepscript configuration by opening the file named tsconfig.json
The default expo tsconfig.json
combined with pep
's changes (which are discussed in detail in Expo's docs here) generates the following tsconfig.json
content:
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"allowJs": true,
"esModuleInterop": true,
"jsx": "react-native",
"lib": ["DOM", "ESNext"],
"moduleResolution": "node",
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"target": "ESNext"
}
}
You can view all the changes pep makes to add absolute imports to a default Expo project here.
Once a new project is created with pep, you can always import code relative to the src/ directory.
... For Example ...
If you are importing the component in src/components/FancyTextInput.tsx
from src/screens/HomeScreen.tsx
, you can do so like this:
import FancyTextInput from "components/FancyTextInput";
... Rather than doing it like this ...
import FancyTextInput from "../components/FancyTextInput";
I've found this setting enables easier refactoring when copying and pasting import lines, and especially when moving files around.
The best way to view the changes that go into making this feature work is to inspect the commit titled "Set /src as the root directory for app code
" after creating a new pep
project.
The setup for this feature touches several configuration files, including: .eslintrc.js
, babel.config.js
, and tsconfig.json
.
You can view all the changes pep makes to add React Navigation to a default Expo project here.
Your default src/App.tsx
file will look like this after setting up a new pep project:
import { Button, Text, TextInput, View } from "react-native"
import { DefaultTheme, NavigationContainer } from "@react-navigation/native"
import { createNativeStackNavigator } from "@react-navigation/native-stack"
import type { NativeStackScreenProps } from "@react-navigation/native-stack"
import { useState } from "react"
type StackNavigatorParams = {
Home: undefined
Details: { homeScreensTextInputValue: string }
}
type HomeProps = NativeStackScreenProps<StackNavigatorParams, "Home">
type DetailsProps = NativeStackScreenProps<StackNavigatorParams, "Details">
const HomeScreen = ({ navigation }: HomeProps): React.ReactElement => {
const [homeScreensTextInputValue, setHomescreensTextInputValue] =
useState<string>("")
return (
<View style={{ flex: 1, alignItems: "center" }}>
<Text style={{ fontWeight: "bold", marginTop: 100 }}>
Open `src/App.tsx` to begin editing your app.
</Text>
<Text style={{ marginTop: 100 }}>Home Screen</Text>
<TextInput
value={homeScreensTextInputValue}
onChangeText={setHomescreensTextInputValue}
placeholder="Type something here to see it on the details screen"
style={{
width: "90%",
height: 30,
textAlign: "center",
borderWidth: 1,
borderColor: "#aaa",
marginTop: 20,
marginBottom: 20,
}}
/>
<Button
title="Go to Details Screen"
onPress={(): void => {
navigation.navigate("Details", { homeScreensTextInputValue })
}}
/>
</View>
)
}
const DetailsScreen = ({ route }: DetailsProps): React.ReactElement => {
const { homeScreensTextInputValue } = route.params
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Text>Details Screen says: {homeScreensTextInputValue}</Text>
</View>
)
}
const Stack = createNativeStackNavigator<StackNavigatorParams>()
const MyTheme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
background: "#fff",
},
}
const App = (): React.ReactElement => (
<NavigationContainer theme={MyTheme}>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
)
export default App
Something I really value about this setup (in addition to it setting up react navigation) is the effort that goes into making clear how to use react navigation in a way that makes typescript happy; eg everything which uses this type definition:
type StackNavigatorParams = {
Home: undefined;
Details: { homeScreensTextInputValue: string };
};
Which includes the creation of the stack navigator ...
const Stack = createNativeStackNavigator<StackNavigatorParams>();
... As well as the creation of a type for the top level screens's component props ...
import type { NativeStackScreenProps } from "@react-navigation/native-stack"
// ...
type HomeProps = NativeStackScreenProps<StackNavigatorParams, "Home">
type DetailsProps = NativeStackScreenProps<StackNavigatorParams, "Details">
const HomeScreen = ({ navigation }: HomeProps): React.ReactElement => {
// ...
return (
// ...
<Button
title="Go to Details Screen"
onPress={(): void => {
navigation.navigate("Details", { homeScreensTextInputValue })
}}
/>
// ...
)
}
const DetailsScreen = ({ route }: DetailsProps): React.ReactElement => {
const { homeScreensTextInputValue } = route.params
return (
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<Text>Details Screen says: {homeScreensTextInputValue}</Text>
</View>
)
}
... Which is needed to tell typescript where the navigation
and route
props come from, as well as which route params each screen expects passed to it, like above with the line ...
const { homeScreensTextInputValue } = route.params
Pep also creates a custom React Navigation theme and applies it:
import { DefaultTheme } from "@react-navigation/native"
const MyTheme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
background: "#fff",
},
}
const App = (): React.ReactElement => (
<NavigationContainer theme={MyTheme}>
// ...
</NavigationContainer>
)
The only thing which pep
actually changes here is the default screen background colors, which it sets to white because React Navigation defaults screen background colors to a slightly off-gray color which I find myself also consistently looking back in the docs for how to switch that back to white.
I hope this peppy little automation saves you some time, too!