diff --git a/challenges/ecosystem/04.md b/challenges/ecosystem/04.md index 091ff5c0..0342dd80 100644 --- a/challenges/ecosystem/04.md +++ b/challenges/ecosystem/04.md @@ -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 @@ -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. @@ -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: @@ -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 ( + + Open up App.tsx to start working on your app! + + + ); +} + +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: Storybook React Native first launch @@ -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`: @@ -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. diff --git a/challenges/expo-router/01.md b/challenges/expo-router/01.md new file mode 100644 index 00000000..235c0bbb --- /dev/null +++ b/challenges/expo-router/01.md @@ -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 ( + + + + + ); +} +``` + +- [ ] 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 `` "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) + + Terms and Conditions + + +// Option 2: Using useRouter hook +import { useRouter } from "expo-router"; + +function LoginScreen() { + const router = useRouter(); + + function navigateToTerms() { + router.push("/terms"); + } + + return ( + + Terms and Conditions + + ); +} +``` + +- [ ] Add a `goBack` behavior on `terms.tsx`: + +```javascript +import { useRouter } from "expo-router"; + +function TermsScreen() { + const router = useRouter(); + + return ( +