├── .gitignore ├── README.md └── examples ├── 2024-06-24-complex-navigation-expo ├── .prettier.config.js ├── .prettierignore ├── README.md ├── app.json ├── app │ ├── (login) │ │ ├── (auth) │ │ │ ├── _layout.tsx │ │ │ ├── index.tsx │ │ │ └── register.tsx │ │ ├── _layout.tsx │ │ └── forgot-password.tsx │ ├── (main) │ │ ├── (home) │ │ │ ├── _layout.tsx │ │ │ ├── details.tsx │ │ │ ├── index.tsx │ │ │ └── options.tsx │ │ ├── _layout.tsx │ │ └── settings.tsx │ ├── +html.tsx │ ├── +not-found.tsx │ └── _layout.tsx ├── assets │ ├── fonts │ │ └── SpaceMono-Regular.ttf │ └── images │ │ ├── adaptive-icon.png │ │ ├── favicon.png │ │ ├── icon.png │ │ ├── partial-react-logo.png │ │ ├── react-logo.png │ │ ├── react-logo@2x.png │ │ ├── react-logo@3x.png │ │ └── splash.png ├── babel.config.js ├── components │ ├── Collapsible.tsx │ ├── ExternalLink.tsx │ ├── HelloWave.tsx │ ├── ParallaxScrollView.tsx │ ├── ThemedText.tsx │ ├── ThemedView.tsx │ ├── __tests__ │ │ ├── ThemedText-test.tsx │ │ └── __snapshots__ │ │ │ └── ThemedText-test.tsx.snap │ └── navigation │ │ └── TabBarIcon.tsx ├── constants │ └── Colors.ts ├── eslint.config.mjs ├── hooks │ ├── useColorScheme.ts │ ├── useColorScheme.web.ts │ └── useThemeColor.ts ├── package-lock.json ├── package.json ├── scripts │ └── reset-project.js └── tsconfig.json ├── 2024-07-04-master-higher-order-components ├── layout.jsx ├── with-layout.jsx └── with-layout.test.jsx ├── 2024-07-16-set-up-next-js-production ├── .github │ └── workflows │ │ └── pull-request.yml ├── .gitignore ├── .husky │ ├── pre-commit │ └── prepare-commit-msg ├── README.md ├── components.json ├── eslint.config.mjs ├── next.config.mjs ├── package-lock.json ├── package.json ├── playwright.config.ts ├── playwright │ └── example.spec.ts ├── postcss.config.mjs ├── prettier.config.js ├── prisma │ ├── schema.prisma │ └── seed.ts ├── public │ ├── next.svg │ └── vercel.svg ├── src │ ├── app │ │ ├── [lang] │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── favicon.ico │ │ ├── globals.css │ │ └── login │ │ │ ├── counter-component.tsx │ │ │ └── page.tsx │ ├── features │ │ ├── internationalization │ │ │ ├── dictionaries │ │ │ │ └── en-US.json │ │ │ ├── get-dictionaries.ts │ │ │ ├── i18n-config.ts │ │ │ ├── localization-middleware.ts │ │ │ └── use-switch-locale-href.ts │ │ ├── user-authentication │ │ │ └── example.test.tsx │ │ └── user-profile │ │ │ └── user-profile-model.ts │ ├── lib │ │ ├── prisma.ts │ │ └── utils.ts │ ├── middleware.ts │ └── tests │ │ ├── react-test-utils.tsx │ │ └── setup-test-environment.ts ├── tailwind.config.ts ├── tsconfig.json └── vitest.config.ts ├── 2024-08-09-redux-basics ├── .eslintrc.json ├── .gitignore ├── README.md ├── eslint.config.mjs ├── jsconfig.json ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── prettier.config.js ├── public │ ├── next.svg │ └── vercel.svg ├── src │ └── app │ │ ├── example-reducer.js │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── hooks.js │ │ ├── layout.js │ │ ├── page-2.js │ │ ├── page-3.js │ │ ├── page.js │ │ ├── store-2.js │ │ ├── store-3.js │ │ ├── store-provider.js │ │ ├── store.js │ │ ├── user-profile-component.js │ │ ├── user-profile-container.js │ │ └── user-profile.js └── tailwind.config.js ├── 2024-08-17-memoization ├── .gitignore ├── example-1.js ├── example-2.js ├── example-3.js ├── example-4.ts ├── example-5.ts ├── package-lock.json ├── package.json └── tsconfig.json ├── 2024-08-20-redux-saga ├── .eslintrc.json ├── .gitignore ├── README.md ├── components.json ├── jsconfig.json ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── prettier.config.js ├── public │ ├── next.svg │ └── vercel.svg ├── src │ ├── app │ │ ├── dashboard │ │ │ └── page.js │ │ ├── favicon.ico │ │ ├── globals.css │ │ ├── layout.js │ │ ├── login │ │ │ └── page.js │ │ └── page.js │ ├── components │ │ └── ui │ │ │ ├── button.jsx │ │ │ ├── card.jsx │ │ │ ├── input.jsx │ │ │ └── label.jsx │ ├── features │ │ ├── dashboard │ │ │ ├── dashboard-page-component.js │ │ │ └── dashboard-page-container.js │ │ ├── example │ │ │ ├── example-reducer.js │ │ │ └── example-saga.js │ │ ├── user-authentication │ │ │ ├── user-authentication-page-component.js │ │ │ └── user-authentication-page-container.js │ │ └── user-profiles │ │ │ ├── user-profile-api.js │ │ │ ├── user-profile-reducer.js │ │ │ └── user-profile-saga.js │ ├── hocs │ │ └── with-router.js │ ├── lib │ │ └── utils.js │ └── redux │ │ ├── effects.js │ │ ├── root-reducer.js │ │ ├── root-saga.js │ │ ├── saga-middleware.js │ │ ├── store-provider.js │ │ └── store.js ├── tailwind.config.js └── tsconfig.json └── 2024-09-01-redux-for-production ├── .eslintrc.json ├── .gitignore ├── README.md ├── components.json ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── prettier.config.js ├── public ├── next.svg └── vercel.svg ├── src ├── app │ ├── dashboard │ │ └── page.ts │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── login │ │ └── page.ts │ ├── page.tsx │ └── posts │ │ └── page.ts ├── components │ ├── spinner.tsx │ └── ui │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── input.tsx │ │ └── label.tsx ├── features │ ├── app-loading │ │ ├── app-loading-component.tsx │ │ ├── app-loading-container.tsx │ │ ├── app-loading-reducer.ts │ │ └── app-loading-saga.ts │ ├── dashboard │ │ ├── dashboard-page-component.tsx │ │ └── dashboard-page-container.tsx │ ├── posts │ │ ├── add-post-component.tsx │ │ ├── posts-api.ts │ │ ├── posts-list-component.tsx │ │ ├── posts-page-component.tsx │ │ └── posts-types.ts │ ├── user-authentication │ │ ├── user-authentication-api.ts │ │ ├── user-authentication-component.tsx │ │ ├── user-authentication-container.ts │ │ ├── user-authentication-reducer.ts │ │ └── user-authentication-saga.ts │ └── user-profiles │ │ ├── user-profiles-api.ts │ │ ├── user-profiles-reducer.ts │ │ ├── user-profiles-saga.ts │ │ └── user-profiles-types.ts ├── hocs │ ├── authenticated-page.ts │ ├── hoist-statics.ts │ ├── public-page.ts │ ├── redirect-if-logged-in.ts │ ├── redirect.tsx │ ├── requires-permission │ │ ├── index.ts │ │ └── requires-permission-component.tsx │ ├── with-auth.ts │ └── with-loading.ts ├── lib │ └── utils.ts └── redux │ ├── clear.ts │ ├── hooks.ts │ ├── root-reducer.ts │ ├── root-saga.ts │ ├── store-provider.tsx │ └── store.ts ├── tailwind.config.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | examples/2024-06-24-complex-navigation-expo/.gitignore -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jan Hesters & ReactSquad Tutorials 2 | 3 | In this repository, you can find all code examples and resources for the Jan Hesters' and ReactSquad's tutorials on YouTube, X, LinkedIn etc. 4 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/.prettier.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable unicorn/prefer-module */ 2 | module.exports = { 3 | arrowParens: "avoid", 4 | bracketSameLine: false, 5 | bracketSpacing: true, 6 | htmlWhitespaceSensitivity: "css", 7 | insertPragma: false, 8 | jsxSingleQuote: false, 9 | plugins: ["prettier-plugin-tailwindcss"], 10 | printWidth: 80, 11 | proseWrap: "always", 12 | quoteProps: "as-needed", 13 | requirePragma: false, 14 | semi: true, 15 | singleQuote: true, 16 | tabWidth: 2, 17 | trailingComma: "all", 18 | useTabs: false, 19 | }; 20 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | yarn.lock 4 | package-lock.json 5 | public 6 | coverage 7 | templates 8 | build 9 | playwright-report 10 | test-results 11 | .expo -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/README.md: -------------------------------------------------------------------------------- 1 | # Buliding a Recat Native App with Complex Navigation in 2024 2 | 3 | This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app). 4 | 5 | ## Get started 6 | 7 | 1. Install dependencies 8 | 9 | ```bash 10 | npm install 11 | ``` 12 | 13 | 2. Start the app 14 | 15 | ```bash 16 | npx expo start 17 | ``` 18 | 19 | In the output, you'll find options to open the app in a 20 | 21 | - [development build](https://docs.expo.dev/develop/development-builds/introduction/) 22 | - [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/) 23 | - [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/) 24 | - [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo 25 | 26 | You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction). 27 | 28 | ## Get a fresh project 29 | 30 | When you're ready, run: 31 | 32 | ```bash 33 | npm run reset-project 34 | ``` 35 | 36 | This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing. 37 | 38 | ## Learn more 39 | 40 | To learn more about developing your project with Expo, look at the following resources: 41 | 42 | - [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides). 43 | - [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web. 44 | 45 | ## Join the community 46 | 47 | Join our community of developers creating universal apps. 48 | 49 | - [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute. 50 | - [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions. 51 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "complex-navigation-expo", 4 | "slug": "complex-navigation-expo", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/images/icon.png", 8 | "scheme": "myapp", 9 | "userInterfaceStyle": "automatic", 10 | "splash": { 11 | "image": "./assets/images/splash.png", 12 | "resizeMode": "contain", 13 | "backgroundColor": "#ffffff" 14 | }, 15 | "ios": { 16 | "supportsTablet": true 17 | }, 18 | "android": { 19 | "adaptiveIcon": { 20 | "foregroundImage": "./assets/images/adaptive-icon.png", 21 | "backgroundColor": "#ffffff" 22 | } 23 | }, 24 | "web": { 25 | "bundler": "metro", 26 | "output": "static", 27 | "favicon": "./assets/images/favicon.png" 28 | }, 29 | "plugins": [ 30 | "expo-router" 31 | ], 32 | "experiments": { 33 | "typedRoutes": true 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/app/(login)/(auth)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Tabs } from "expo-router"; 2 | import React from "react"; 3 | 4 | import { TabBarIcon } from "@/components/navigation/TabBarIcon"; 5 | import { Colors } from "@/constants/Colors"; 6 | import { useColorScheme } from "@/hooks/useColorScheme"; 7 | 8 | export default function AuthLayout() { 9 | const colorScheme = useColorScheme(); 10 | 11 | return ( 12 | 18 | ( 23 | 27 | ), 28 | }} 29 | /> 30 | ( 35 | 39 | ), 40 | }} 41 | /> 42 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/app/(login)/(auth)/index.tsx: -------------------------------------------------------------------------------- 1 | import { ThemedText } from "@/components/ThemedText"; 2 | import { ThemedView } from "@/components/ThemedView"; 3 | import { StyleSheet } from "react-native"; 4 | import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; 5 | import { Button } from "@rneui/themed"; 6 | import { Link } from "expo-router"; 7 | import { router } from "expo-router"; 8 | 9 | export default function LoginView() { 10 | return ( 11 | 12 | 13 | 14 | Hello from the Login view 15 | 16 | 17 | Forgot password 18 | 19 | 20 | 27 | 28 | 29 | 30 | ); 31 | } 32 | 33 | const styles = StyleSheet.create({ 34 | container: { 35 | flex: 1, 36 | }, 37 | innerContainer: { 38 | flex: 1, 39 | justifyContent: "space-around", 40 | alignItems: "center", 41 | }, 42 | link: { 43 | lineHeight: 30, 44 | fontSize: 16, 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/app/(login)/(auth)/register.tsx: -------------------------------------------------------------------------------- 1 | import { ThemedText } from "@/components/ThemedText"; 2 | import { ThemedView } from "@/components/ThemedView"; 3 | import { StyleSheet } from "react-native"; 4 | import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; 5 | 6 | export default function RegisterView() { 7 | return ( 8 | 9 | 10 | 11 | Register view 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | const styles = StyleSheet.create({ 19 | container: { 20 | flex: 1, 21 | }, 22 | innerContainer: { 23 | flex: 1, 24 | justifyContent: "space-around", 25 | alignItems: "center", 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/app/(login)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Stack } from "expo-router"; 2 | import "react-native-reanimated"; 3 | 4 | export default function LoginLayout() { 5 | return ( 6 | 7 | 8 | 9 | 10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/app/(login)/forgot-password.tsx: -------------------------------------------------------------------------------- 1 | import { ThemedText } from "@/components/ThemedText"; 2 | import { ThemedView } from "@/components/ThemedView"; 3 | import { StyleSheet } from "react-native"; 4 | import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; 5 | import { Link } from "expo-router"; 6 | 7 | export default function ForgotPasswordView() { 8 | return ( 9 | 10 | 11 | 12 | Forgot password view 13 | 14 | 15 | Back to Login 16 | 17 | 18 | 19 | 20 | ); 21 | } 22 | 23 | const styles = StyleSheet.create({ 24 | container: { 25 | flex: 1, 26 | }, 27 | innerContainer: { 28 | flex: 1, 29 | justifyContent: "space-around", 30 | alignItems: "center", 31 | }, 32 | link: { 33 | lineHeight: 30, 34 | fontSize: 16, 35 | }, 36 | }); 37 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/app/(main)/(home)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { Stack } from "expo-router"; 2 | import "react-native-reanimated"; 3 | 4 | export default function HomeLayout() { 5 | return ( 6 | 7 | 11 | 15 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/app/(main)/(home)/details.tsx: -------------------------------------------------------------------------------- 1 | import { ThemedText } from "@/components/ThemedText"; 2 | import { ThemedView } from "@/components/ThemedView"; 3 | import { StyleSheet } from "react-native"; 4 | import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; 5 | 6 | export default function DetailsView() { 7 | return ( 8 | 9 | 10 | 11 | Details view 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | const styles = StyleSheet.create({ 19 | container: { 20 | flex: 1, 21 | }, 22 | innerContainer: { 23 | flex: 1, 24 | justifyContent: "space-around", 25 | alignItems: "center", 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/app/(main)/(home)/index.tsx: -------------------------------------------------------------------------------- 1 | import { ThemedText } from "@/components/ThemedText"; 2 | import { ThemedView } from "@/components/ThemedView"; 3 | import { Link } from "expo-router"; 4 | import { StyleSheet } from "react-native"; 5 | import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; 6 | 7 | export default function HomeView() { 8 | return ( 9 | 10 | 11 | 12 | Home view 13 | 14 | 15 | Options 16 | 17 | 18 | 19 | Details 20 | 21 | 22 | 23 | 24 | ); 25 | } 26 | 27 | const styles = StyleSheet.create({ 28 | container: { 29 | flex: 1, 30 | }, 31 | innerContainer: { 32 | flex: 1, 33 | justifyContent: "space-around", 34 | alignItems: "center", 35 | }, 36 | link: { 37 | lineHeight: 30, 38 | fontSize: 16, 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/app/(main)/(home)/options.tsx: -------------------------------------------------------------------------------- 1 | import { ThemedText } from "@/components/ThemedText"; 2 | import { ThemedView } from "@/components/ThemedView"; 3 | import { StyleSheet } from "react-native"; 4 | import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; 5 | 6 | export default function OptionsView() { 7 | return ( 8 | 9 | 10 | 11 | Options view 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | const styles = StyleSheet.create({ 19 | container: { 20 | flex: 1, 21 | }, 22 | innerContainer: { 23 | flex: 1, 24 | justifyContent: "space-around", 25 | alignItems: "center", 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/app/(main)/_layout.tsx: -------------------------------------------------------------------------------- 1 | import { router, Tabs, usePathname } from "expo-router"; 2 | import { Drawer } from "expo-router/drawer"; 3 | import React from "react"; 4 | import { Pressable, Platform, StyleSheet } from "react-native"; 5 | import { GestureHandlerRootView } from "react-native-gesture-handler"; 6 | 7 | import { TabBarIcon } from "@/components/navigation/TabBarIcon"; 8 | import { Colors } from "@/constants/Colors"; 9 | import { useColorScheme } from "@/hooks/useColorScheme"; 10 | 11 | export default function MainLayout() { 12 | const colorScheme = useColorScheme(); 13 | const pathname = usePathname(); 14 | const isHome = pathname === "/"; 15 | 16 | if (Platform.OS === "android") { 17 | return ( 18 | 19 | 20 | 28 | ( 34 | { 37 | // In the real world, you should use a logout function here 38 | // and then auto redirect using the root layout ❗️ 39 | router.replace("(login)"); 40 | }} 41 | > 42 | 43 | 44 | ), 45 | }} 46 | /> 47 | 48 | 49 | ); 50 | } 51 | 52 | return ( 53 | 58 | ( 64 | 68 | ), 69 | }} 70 | /> 71 | ( 76 | 77 | ), 78 | headerLeft: () => ( 79 | { 82 | // In the real world, you should use a logout function here 83 | // and then auto redirect using the root layout ❗️ 84 | router.replace("(login)"); 85 | }} 86 | > 87 | 88 | 89 | ), 90 | }} 91 | /> 92 | 93 | ); 94 | } 95 | 96 | const styles = StyleSheet.create({ 97 | headerButton: { 98 | paddingHorizontal: 16, 99 | }, 100 | }); 101 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/app/(main)/settings.tsx: -------------------------------------------------------------------------------- 1 | import { ThemedText } from "@/components/ThemedText"; 2 | import { ThemedView } from "@/components/ThemedView"; 3 | import { StyleSheet } from "react-native"; 4 | import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context"; 5 | 6 | export default function SettingsView() { 7 | return ( 8 | 9 | 10 | 11 | Settings view 12 | 13 | 14 | 15 | ); 16 | } 17 | 18 | const styles = StyleSheet.create({ 19 | container: { 20 | flex: 1, 21 | }, 22 | innerContainer: { 23 | flex: 1, 24 | justifyContent: "space-around", 25 | alignItems: "center", 26 | }, 27 | }); 28 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/app/+html.tsx: -------------------------------------------------------------------------------- 1 | import { ScrollViewStyleReset } from "expo-router/html"; 2 | import { type PropsWithChildren } from "react"; 3 | 4 | /** 5 | * This file is web-only and used to configure the root HTML for every web page during static rendering. 6 | * The contents of this function only run in Node.js environments and do not have access to the DOM or browser APIs. 7 | */ 8 | export default function Root({ children }: PropsWithChildren) { 9 | return ( 10 | 11 | 12 | 13 | 14 | 18 | 19 | {/* 20 | Disable body scrolling on web. This makes ScrollView components work closer to how they do on native. 21 | However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line. 22 | */} 23 | 24 | 25 | {/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */} 26 |