⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 65 additions & 26 deletions challenges/ecosystem/04.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

## 📡 What you will learn

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

## 👾 Before we start the exercise
Expand All @@ -16,7 +16,7 @@
- [ ] Setup Storybook

```console
npx sb@latest init --type react_native
npm create storybook@latest
```

**🔭 Hint:** Read the prompt, the setup is NOT 100% automated.
Expand All @@ -28,10 +28,19 @@ First create metro config file if you don't have it yet.
```console
npx expo customize metro.config.js
```
Enable transformer.unstable_allowRequireContext in your metro config

Then wrap your config with the `withStorybook` function and add the `enabled` option:

```js
config.transformer.unstable_allowRequireContext = true;
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const { withStorybook } = require('@storybook/react-native/metro/withStorybook');

const config = getDefaultConfig(__dirname);

module.exports = withStorybook(config, {
enabled: process.env.EXPO_PUBLIC_STORYBOOK_ENABLED === 'true',
});
```

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

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

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

```javascript
```tsx
// App.tsx

...

// eslint-disable-next-line import/no-default-export
// export default App; <------ comment this for now

// return Storybook's UI
export { default } from "./.storybook";
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, Text, View } from 'react-native';

function App() {
return (
<View style={styles.container}>
<Text>Open up App.tsx to start working on your app!</Text>
<StatusBar style="auto" />
</View>
);
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});

let AppEntryPoint = App;

if (process.env.EXPO_PUBLIC_STORYBOOK_ENABLED === 'true') {
AppEntryPoint = require('./.rnstorybook').default;
}

export default AppEntryPoint;
```

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

```diff
"storybook-generate": "sb-rn-get-stories",
"storybook-watch": "sb-rn-watcher",
++ "storybook": "sb-rn-get-stories && expo start"
"scripts": {
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
++ "storybook": "EXPO_PUBLIC_STORYBOOK_ENABLED=true expo start",
"storybook-generate": "sb-rn-get-stories"
},
```

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

<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" />

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

> Place code as close to where it's relevant as possible

- [ ] Delete generated `Button.stories.js` files:
- [ ] Delete generated example stories:

```console
rm -rf .storybook/stories/
rm -rf .rnstorybook/stories/
```

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

```diff
module.exports = {
-- // stories: ['./stories/**/*.stories.?(ts|tsx|js|jsx)'],
++ stories: ["../src/components/**/*.stories.?(ts|tsx|js|jsx)"],
// .rnstorybook/main.ts
import type { StorybookConfig } from '@storybook/react-native';

const main: StorybookConfig = {
-- stories: ['./stories/**/*.stories.?(ts|tsx|js|jsx)'],
++ stories: ['../src/components/**/*.stories.?(ts|tsx|js|jsx)'],
addons: [],
};

export default main;
```

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

## 👽 Bonus

- [ ] 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).
- [ ] You can [watch me live coding with Dany](https://www.youtube.com/watch?v=QgYPgDxJRkU) the maintainer of Storybook React Native.
168 changes: 168 additions & 0 deletions challenges/expo-router/01.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# File-based Routing

## 📡 What you will learn

- Organise your routes using file-based routing.
- How to use the `expo-router` library with the `Link` component and `useRouter` hook.

## 👾 Before we start the exercise

- There are others routing solutions available, keep in mind **we are using [`expo-router` library](https://docs.expo.dev/router/introduction/)**.
- Expo Router is built on top of React Navigation, so you get the same navigation primitives with a file-based approach.
- TypeScript is fully supported and routes are automatically typed.

Here is a preview of our application user flow:

![expo-router](https://raw.githubusercontent.com/flexbox/react-native-workshop/main/challenges/react-navigation/react-navigation.png)

## 👨‍🚀 Exercise 1

### Installation

- [ ] Read the [Getting started](https://docs.expo.dev/router/installation/) guide to:
1. Install `expo-router` in your React Native project.
2. Configure the entry point in `package.json` and `app.json`.

**🔭 Hint:** With Expo Router, routes are automatically generated based on the file structure in the `app/` directory.

### Create your first routes

- [ ] Create a new `app/` directory at the root of your project.
- [ ] Create an `app/_layout.tsx` file to define your root layout:

```javascript
// app/_layout.tsx
import { Stack } from "expo-router";

export default function RootLayout() {
return (
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="index" />
<Stack.Screen name="terms" />
</Stack>
);
}
```

- [ ] Create `app/index.tsx` for your `LoginScreen`.
- [ ] Create `app/terms.tsx` for your `TermsScreen`.

**🔭 Hint:** In Expo Router, `index.tsx` is the default route (like `index.html` on the web).

### Navigate to another screen

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.

![expo-router](https://raw.githubusercontent.com/flexbox/react-native-workshop/main/challenges/react-navigation/react-navigation-focus.png)

- [ ] Use the `Link` component from `expo-router`:

```javascript
import { Link } from "expo-router";

// Option 1: Using Link component (recommended)
<Link href="/terms">
<Text>Terms and Conditions</Text>
</Link>

// Option 2: Using useRouter hook
import { useRouter } from "expo-router";

function LoginScreen() {
const router = useRouter();

function navigateToTerms() {
router.push("/terms");
}

return (
<TouchableOpacity onPress={navigateToTerms}>
<Text>Terms and Conditions</Text>
</TouchableOpacity>
);
}
```

- [ ] Add a `goBack` behavior on `terms.tsx`:

```javascript
import { useRouter } from "expo-router";

function TermsScreen() {
const router = useRouter();

return (
<Button title="Go Back" onPress={() => router.back()} />
);
}
```

### Options for screens

- [ ] On the `TermsScreen`, we have an issue with the double header, we can fix it with some options within the layout:

```javascript
// app/_layout.tsx
<Stack screenOptions={{ headerShown: false }}>
...
</Stack>
```

Or configure specific screen options:

```javascript
// app/_layout.tsx
<Stack.Screen
name="terms"
options={{
headerShown: true,
title: "Terms & Conditions",
}}
/>
```

## 👽 Bonus

### Typed routes

Expo Router automatically generates types for your routes. Enable typed routes in your `app.json`:

```json
{
"expo": {
"experiments": {
"typedRoutes": true
}
}
}
```

Now your routes are fully typed:

```javascript
import { Link } from "expo-router";

// TypeScript will autocomplete and validate your routes
<Link href="/terms">Terms</Link>
<Link href="/starships">Starships</Link>
```

- [ ] Enable typed routes in your project.
- [ ] Verify that TypeScript autocompletes your route paths.

### Organize with route groups

You can organize routes using groups (folders with parentheses):

```
app/
(auth)/
_layout.tsx
index.tsx → /
terms.tsx → /terms
(app)/
_layout.tsx
starships.tsx → /starships
```

- [ ] Create route groups to separate auth and app routes.
Loading