Skip to content

Commit 77a6899

Browse files
committed
feat: 🎸 add expo router challenges
1 parent 4b14364 commit 77a6899

File tree

5 files changed

+848
-42
lines changed

5 files changed

+848
-42
lines changed

challenges/ecosystem/04.md

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
## 📡 What you will learn
66

7-
- Setup Storybook with React Native.
7+
- Setup Storybook v10 with React Native.
88
- Writing `.stories` to debug your application with isolated components.
99

1010
## 👾 Before we start the exercise
@@ -16,7 +16,7 @@
1616
- [ ] Setup Storybook
1717

1818
```console
19-
npx sb@latest init --type react_native
19+
npm create storybook@latest
2020
```
2121

2222
**🔭 Hint:** Read the prompt, the setup is NOT 100% automated.
@@ -28,10 +28,19 @@ First create metro config file if you don't have it yet.
2828
```console
2929
npx expo customize metro.config.js
3030
```
31-
Enable transformer.unstable_allowRequireContext in your metro config
31+
32+
Then wrap your config with the `withStorybook` function and add the `enabled` option:
3233

3334
```js
34-
config.transformer.unstable_allowRequireContext = true;
35+
// metro.config.js
36+
const { getDefaultConfig } = require('expo/metro-config');
37+
const { withStorybook } = require('@storybook/react-native/metro/withStorybook');
38+
39+
const config = getDefaultConfig(__dirname);
40+
41+
module.exports = withStorybook(config, {
42+
enabled: process.env.EXPO_PUBLIC_STORYBOOK_ENABLED === 'true',
43+
});
3544
```
3645

3746
For a more detailed guide go to:
@@ -42,29 +51,54 @@ https://github.com/storybookjs/react-native#existing-project
4251

4352
![Change App.tsx for Storybook in React Native](https://raw.githubusercontent.com/flexbox/react-native-workshop/main/challenges/ecosystem/storybook-booting.png)
4453

45-
- [ ] Change the entry point of your application and comment your `default export` to return Storybook's UI.
54+
- [ ] Change the entry point of your application to conditionally load Storybook based on the environment variable:
4655

47-
```javascript
56+
```tsx
4857
// App.tsx
49-
50-
...
51-
52-
// eslint-disable-next-line import/no-default-export
53-
// export default App; <------ comment this for now
54-
55-
// return Storybook's UI
56-
export { default } from "./.storybook";
58+
import { StatusBar } from 'expo-status-bar';
59+
import { StyleSheet, Text, View } from 'react-native';
60+
61+
function App() {
62+
return (
63+
<View style={styles.container}>
64+
<Text>Open up App.tsx to start working on your app!</Text>
65+
<StatusBar style="auto" />
66+
</View>
67+
);
68+
}
69+
70+
const styles = StyleSheet.create({
71+
container: {
72+
flex: 1,
73+
backgroundColor: '#fff',
74+
alignItems: 'center',
75+
justifyContent: 'center',
76+
},
77+
});
78+
79+
let AppEntryPoint = App;
80+
81+
if (process.env.EXPO_PUBLIC_STORYBOOK_ENABLED === 'true') {
82+
AppEntryPoint = require('./.rnstorybook').default;
83+
}
84+
85+
export default AppEntryPoint;
5786
```
5887

59-
- [ ] Update your `package.json` to run storybook with `npm run storybook`:
88+
- [ ] Update your `package.json` to add the storybook command:
6089

6190
```diff
62-
"storybook-generate": "sb-rn-get-stories",
63-
"storybook-watch": "sb-rn-watcher",
64-
++ "storybook": "sb-rn-get-stories && expo start"
91+
"scripts": {
92+
"start": "expo start",
93+
"android": "expo start --android",
94+
"ios": "expo start --ios",
95+
"web": "expo start --web",
96+
++ "storybook": "EXPO_PUBLIC_STORYBOOK_ENABLED=true expo start",
97+
"storybook-generate": "sb-rn-get-stories"
98+
},
6599
```
66100

67-
- [ ] Run your app, you should have the Storybook displayed like this:
101+
- [ ] Run Storybook with `npm run storybook`, you should have the Storybook displayed like this:
68102

69103
<img src="https://raw.githubusercontent.com/flexbox/react-native-workshop/main/challenges/ecosystem/storybook-first-launch.gif" width="50%" height="50%" alt="Storybook React Native first launch" />
70104

@@ -74,19 +108,25 @@ Right now, storybook display components created for testing purposes. We want to
74108

75109
> Place code as close to where it's relevant as possible
76110
77-
- [ ] Delete generated `Button.stories.js` files:
111+
- [ ] Delete generated example stories:
78112

79113
```console
80-
rm -rf .storybook/stories/
114+
rm -rf .rnstorybook/stories/
81115
```
82116

83-
- [ ] Update `.storybook/main.ts` file to load stories from our components folder:
117+
- [ ] Update `.rnstorybook/main.ts` file to load stories from our components folder:
84118

85119
```diff
86-
module.exports = {
87-
-- // stories: ['./stories/**/*.stories.?(ts|tsx|js|jsx)'],
88-
++ stories: ["../src/components/**/*.stories.?(ts|tsx|js|jsx)"],
120+
// .rnstorybook/main.ts
121+
import type { StorybookConfig } from '@storybook/react-native';
122+
123+
const main: StorybookConfig = {
124+
-- stories: ['./stories/**/*.stories.?(ts|tsx|js|jsx)'],
125+
++ stories: ['../src/components/**/*.stories.?(ts|tsx|js|jsx)'],
126+
addons: [],
89127
};
128+
129+
export default main;
90130
```
91131

92132
- [ ] Create a new file `./src/components/Text.stories.tsx`:
@@ -155,5 +195,4 @@ Default.story = {
155195

156196
## 👽 Bonus
157197

158-
- [ ] Update your `package.json` with `STORYBOOK_ENABLED` to [swap between React Native Storybook and your app](https://dev.to/dannyhw/how-to-swap-between-react-native-storybook-and-your-app-p3o).
159198
- [ ] You can [watch me live coding with Dany](https://www.youtube.com/watch?v=QgYPgDxJRkU) the maintainer of Storybook React Native.

challenges/expo-router/01.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# File-based Routing
2+
3+
## 📡 What you will learn
4+
5+
- Organise your routes using file-based routing.
6+
- How to use the `expo-router` library with the `Link` component and `useRouter` hook.
7+
8+
## 👾 Before we start the exercise
9+
10+
- There are others routing solutions available, keep in mind **we are using [`expo-router` library](https://docs.expo.dev/router/introduction/)**.
11+
- Expo Router is built on top of React Navigation, so you get the same navigation primitives with a file-based approach.
12+
- TypeScript is fully supported and routes are automatically typed.
13+
14+
Here is a preview of our application user flow:
15+
16+
![expo-router](https://raw.githubusercontent.com/flexbox/react-native-workshop/main/challenges/react-navigation/react-navigation.png)
17+
18+
## 👨‍🚀 Exercise 1
19+
20+
### Installation
21+
22+
- [ ] Read the [Getting started](https://docs.expo.dev/router/installation/) guide to:
23+
1. Install `expo-router` in your React Native project.
24+
2. Configure the entry point in `package.json` and `app.json`.
25+
26+
**🔭 Hint:** With Expo Router, routes are automatically generated based on the file structure in the `app/` directory.
27+
28+
### Create your first routes
29+
30+
- [ ] Create a new `app/` directory at the root of your project.
31+
- [ ] Create an `app/_layout.tsx` file to define your root layout:
32+
33+
```javascript
34+
// app/_layout.tsx
35+
import { Stack } from "expo-router";
36+
37+
export default function RootLayout() {
38+
return (
39+
<Stack screenOptions={{ headerShown: false }}>
40+
<Stack.Screen name="index" />
41+
<Stack.Screen name="terms" />
42+
</Stack>
43+
);
44+
}
45+
```
46+
47+
- [ ] Create `app/index.tsx` for your `LoginScreen`.
48+
- [ ] Create `app/terms.tsx` for your `TermsScreen`.
49+
50+
**🔭 Hint:** In Expo Router, `index.tsx` is the default route (like `index.html` on the web).
51+
52+
### Navigate to another screen
53+
54+
Do you remember the `<Text>` "by login you accept the Terms and Conditions."? We will use the `Link` component or `router.push()` to go to another screen.
55+
56+
![expo-router](https://raw.githubusercontent.com/flexbox/react-native-workshop/main/challenges/react-navigation/react-navigation-focus.png)
57+
58+
- [ ] Use the `Link` component from `expo-router`:
59+
60+
```javascript
61+
import { Link } from "expo-router";
62+
63+
// Option 1: Using Link component (recommended)
64+
<Link href="/terms">
65+
<Text>Terms and Conditions</Text>
66+
</Link>
67+
68+
// Option 2: Using useRouter hook
69+
import { useRouter } from "expo-router";
70+
71+
function LoginScreen() {
72+
const router = useRouter();
73+
74+
function navigateToTerms() {
75+
router.push("/terms");
76+
}
77+
78+
return (
79+
<TouchableOpacity onPress={navigateToTerms}>
80+
<Text>Terms and Conditions</Text>
81+
</TouchableOpacity>
82+
);
83+
}
84+
```
85+
86+
- [ ] Add a `goBack` behavior on `terms.tsx`:
87+
88+
```javascript
89+
import { useRouter } from "expo-router";
90+
91+
function TermsScreen() {
92+
const router = useRouter();
93+
94+
return (
95+
<Button title="Go Back" onPress={() => router.back()} />
96+
);
97+
}
98+
```
99+
100+
### Options for screens
101+
102+
- [ ] On the `TermsScreen`, we have an issue with the double header, we can fix it with some options within the layout:
103+
104+
```javascript
105+
// app/_layout.tsx
106+
<Stack screenOptions={{ headerShown: false }}>
107+
...
108+
</Stack>
109+
```
110+
111+
Or configure specific screen options:
112+
113+
```javascript
114+
// app/_layout.tsx
115+
<Stack.Screen
116+
name="terms"
117+
options={{
118+
headerShown: true,
119+
title: "Terms & Conditions",
120+
}}
121+
/>
122+
```
123+
124+
## 👽 Bonus
125+
126+
### Typed routes
127+
128+
Expo Router automatically generates types for your routes. Enable typed routes in your `app.json`:
129+
130+
```json
131+
{
132+
"expo": {
133+
"experiments": {
134+
"typedRoutes": true
135+
}
136+
}
137+
}
138+
```
139+
140+
Now your routes are fully typed:
141+
142+
```javascript
143+
import { Link } from "expo-router";
144+
145+
// TypeScript will autocomplete and validate your routes
146+
<Link href="/terms">Terms</Link>
147+
<Link href="/starships">Starships</Link>
148+
```
149+
150+
- [ ] Enable typed routes in your project.
151+
- [ ] Verify that TypeScript autocompletes your route paths.
152+
153+
### Organize with route groups
154+
155+
You can organize routes using groups (folders with parentheses):
156+
157+
```
158+
app/
159+
(auth)/
160+
_layout.tsx
161+
index.tsx → /
162+
terms.tsx → /terms
163+
(app)/
164+
_layout.tsx
165+
starships.tsx → /starships
166+
```
167+
168+
- [ ] Create route groups to separate auth and app routes.

0 commit comments

Comments
 (0)