├── examples ├── 2024-08-17-memoization │ ├── .gitignore │ ├── package.json │ ├── example-1.js │ ├── example-2.js │ ├── example-3.js │ ├── package-lock.json │ ├── example-5.ts │ └── example-4.ts ├── 2024-08-09-redux-basics │ ├── .eslintrc.json │ ├── jsconfig.json │ ├── next.config.mjs │ ├── src │ │ └── app │ │ │ ├── favicon.ico │ │ │ ├── hooks.js │ │ │ ├── store-provider.js │ │ │ ├── layout.js │ │ │ ├── user-profile-container.js │ │ │ ├── user-profile-component.js │ │ │ ├── store.js │ │ │ ├── globals.css │ │ │ ├── example-reducer.js │ │ │ ├── page.js │ │ │ ├── page-2.js │ │ │ ├── user-profile.js │ │ │ ├── page-3.js │ │ │ └── store-2.js │ ├── postcss.config.mjs │ ├── prettier.config.js │ ├── .gitignore │ ├── tailwind.config.js │ ├── public │ │ ├── vercel.svg │ │ └── next.svg │ ├── package.json │ ├── README.md │ └── eslint.config.mjs ├── 2024-06-24-complex-navigation-expo │ ├── hooks │ │ ├── useColorScheme.ts │ │ ├── useColorScheme.web.ts │ │ └── useThemeColor.ts │ ├── assets │ │ ├── images │ │ │ ├── icon.png │ │ │ ├── favicon.png │ │ │ ├── splash.png │ │ │ ├── react-logo.png │ │ │ ├── adaptive-icon.png │ │ │ ├── react-logo@2x.png │ │ │ ├── react-logo@3x.png │ │ │ └── partial-react-logo.png │ │ └── fonts │ │ │ └── SpaceMono-Regular.ttf │ ├── babel.config.js │ ├── .prettierignore │ ├── tsconfig.json │ ├── app │ │ ├── (login) │ │ │ ├── _layout.tsx │ │ │ ├── (auth) │ │ │ │ ├── register.tsx │ │ │ │ ├── _layout.tsx │ │ │ │ └── index.tsx │ │ │ └── forgot-password.tsx │ │ ├── (main) │ │ │ ├── (home) │ │ │ │ ├── _layout.tsx │ │ │ │ ├── details.tsx │ │ │ │ ├── options.tsx │ │ │ │ └── index.tsx │ │ │ ├── settings.tsx │ │ │ └── _layout.tsx │ │ ├── +not-found.tsx │ │ ├── +html.tsx │ │ └── _layout.tsx │ ├── components │ │ ├── __tests__ │ │ │ ├── ThemedText-test.tsx │ │ │ └── __snapshots__ │ │ │ │ └── ThemedText-test.tsx.snap │ │ ├── navigation │ │ │ └── TabBarIcon.tsx │ │ ├── ThemedView.tsx │ │ ├── ExternalLink.tsx │ │ ├── HelloWave.tsx │ │ ├── Collapsible.tsx │ │ ├── ThemedText.tsx │ │ └── ParallaxScrollView.tsx │ ├── .prettier.config.js │ ├── eslint.config.mjs │ ├── constants │ │ └── Colors.ts │ ├── app.json │ ├── README.md │ ├── package.json │ └── scripts │ │ └── reset-project.js ├── 2024-08-20-redux-saga │ ├── src │ │ ├── app │ │ │ ├── dashboard │ │ │ │ └── page.js │ │ │ ├── login │ │ │ │ └── page.js │ │ │ ├── favicon.ico │ │ │ ├── layout.js │ │ │ └── globals.css │ │ ├── lib │ │ │ └── utils.js │ │ ├── features │ │ │ ├── dashboard │ │ │ │ ├── dashboard-page-component.js │ │ │ │ └── dashboard-page-container.js │ │ │ ├── user-profiles │ │ │ │ ├── user-profile-api.js │ │ │ │ ├── user-profile-saga.js │ │ │ │ └── user-profile-reducer.js │ │ │ ├── example │ │ │ │ ├── example-saga.js │ │ │ │ └── example-reducer.js │ │ │ └── user-authentication │ │ │ │ ├── user-authentication-page-container.js │ │ │ │ └── user-authentication-page-component.js │ │ ├── redux │ │ │ ├── root-saga.js │ │ │ ├── store-provider.js │ │ │ ├── root-reducer.js │ │ │ ├── store.js │ │ │ ├── effects.js │ │ │ └── saga-middleware.js │ │ ├── hocs │ │ │ └── with-router.js │ │ └── components │ │ │ └── ui │ │ │ ├── label.jsx │ │ │ ├── input.jsx │ │ │ ├── card.jsx │ │ │ └── button.jsx │ ├── jsconfig.json │ ├── next.config.mjs │ ├── postcss.config.mjs │ ├── components.json │ ├── prettier.config.js │ ├── .gitignore │ ├── public │ │ ├── vercel.svg │ │ └── next.svg │ ├── tsconfig.json │ ├── .eslintrc.json │ ├── package.json │ ├── README.md │ └── tailwind.config.js ├── 2024-07-16-set-up-next-js-production │ ├── .husky │ │ ├── pre-commit │ │ └── prepare-commit-msg │ ├── next.config.mjs │ ├── src │ │ ├── app │ │ │ ├── favicon.ico │ │ │ ├── [lang] │ │ │ │ ├── page.tsx │ │ │ │ └── layout.tsx │ │ │ ├── login │ │ │ │ ├── page.tsx │ │ │ │ └── counter-component.tsx │ │ │ └── globals.css │ │ ├── features │ │ │ ├── internationalization │ │ │ │ ├── i18n-config.ts │ │ │ │ ├── dictionaries │ │ │ │ │ └── en-US.json │ │ │ │ ├── use-switch-locale-href.ts │ │ │ │ ├── get-dictionaries.ts │ │ │ │ └── localization-middleware.ts │ │ │ ├── user-authentication │ │ │ │ └── example.test.tsx │ │ │ └── user-profile │ │ │ │ └── user-profile-model.ts │ │ ├── lib │ │ │ ├── utils.ts │ │ │ └── prisma.ts │ │ ├── tests │ │ │ ├── setup-test-environment.ts │ │ │ └── react-test-utils.tsx │ │ └── middleware.ts │ ├── postcss.config.mjs │ ├── playwright │ │ └── example.spec.ts │ ├── components.json │ ├── prettier.config.js │ ├── prisma │ │ ├── schema.prisma │ │ └── seed.ts │ ├── public │ │ ├── vercel.svg │ │ └── next.svg │ ├── .gitignore │ ├── tsconfig.json │ ├── vitest.config.ts │ ├── README.md │ ├── eslint.config.mjs │ ├── playwright.config.ts │ ├── tailwind.config.ts │ ├── package.json │ └── .github │ │ └── workflows │ │ └── pull-request.yml ├── 2024-09-01-redux-for-production │ ├── next.config.mjs │ ├── src │ │ ├── redux │ │ │ ├── clear.ts │ │ │ ├── hooks.ts │ │ │ ├── root-saga.ts │ │ │ ├── store-provider.tsx │ │ │ ├── root-reducer.ts │ │ │ └── store.ts │ │ ├── app │ │ │ ├── favicon.ico │ │ │ ├── dashboard │ │ │ │ └── page.ts │ │ │ ├── posts │ │ │ │ └── page.ts │ │ │ ├── login │ │ │ │ └── page.ts │ │ │ ├── layout.tsx │ │ │ └── globals.css │ │ ├── features │ │ │ ├── posts │ │ │ │ ├── posts-types.ts │ │ │ │ ├── posts-page-component.tsx │ │ │ │ ├── posts-api.ts │ │ │ │ ├── posts-list-component.tsx │ │ │ │ └── add-post-component.tsx │ │ │ ├── user-profiles │ │ │ │ ├── user-profiles-types.ts │ │ │ │ ├── user-profiles-api.ts │ │ │ │ ├── user-profiles-saga.ts │ │ │ │ └── user-profiles-reducer.ts │ │ │ ├── user-authentication │ │ │ │ ├── user-authentication-api.ts │ │ │ │ ├── user-authentication-container.ts │ │ │ │ ├── user-authentication-reducer.ts │ │ │ │ ├── user-authentication-saga.ts │ │ │ │ └── user-authentication-component.tsx │ │ │ ├── app-loading │ │ │ │ ├── app-loading-component.tsx │ │ │ │ ├── app-loading-saga.ts │ │ │ │ ├── app-loading-reducer.ts │ │ │ │ └── app-loading-container.tsx │ │ │ └── dashboard │ │ │ │ ├── dashboard-page-container.tsx │ │ │ │ └── dashboard-page-component.tsx │ │ ├── hocs │ │ │ ├── public-page.ts │ │ │ ├── authenticated-page.ts │ │ │ ├── redirect-if-logged-in.ts │ │ │ ├── with-loading.ts │ │ │ ├── with-auth.ts │ │ │ ├── requires-permission │ │ │ │ ├── requires-permission-component.tsx │ │ │ │ └── index.ts │ │ │ ├── hoist-statics.ts │ │ │ └── redirect.tsx │ │ ├── lib │ │ │ └── utils.ts │ │ └── components │ │ │ ├── spinner.tsx │ │ │ └── ui │ │ │ ├── label.tsx │ │ │ ├── input.tsx │ │ │ ├── button.tsx │ │ │ └── card.tsx │ ├── postcss.config.mjs │ ├── prettier.config.js │ ├── components.json │ ├── .gitignore │ ├── public │ │ ├── vercel.svg │ │ └── next.svg │ ├── tsconfig.json │ ├── .eslintrc.json │ ├── README.md │ ├── package.json │ └── tailwind.config.ts └── 2024-07-04-master-higher-order-components │ ├── with-layout.jsx │ ├── layout.jsx │ └── with-layout.test.jsx ├── .gitignore └── README.md /examples/2024-08-17-memoization/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | examples/2024-06-24-complex-navigation-expo/.gitignore -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/hooks/useColorScheme.ts: -------------------------------------------------------------------------------- 1 | export { useColorScheme } from "react-native"; 2 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/app/dashboard/page.js: -------------------------------------------------------------------------------- 1 | export { default } from '../../features/dashboard/dashboard-page-container'; 2 | -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/app/login/page.js: -------------------------------------------------------------------------------- 1 | export { default } from '../../features/user-authentication/user-authentication-page-container'; 2 | -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EarlyNode/tutorials/HEAD/examples/2024-08-20-redux-saga/src/app/favicon.ico -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EarlyNode/tutorials/HEAD/examples/2024-08-09-redux-basics/src/app/favicon.ico -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run lint && npm run type-check 5 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/redux/clear.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit'; 2 | 3 | export const clear = createAction('all/clear'); 4 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EarlyNode/tutorials/HEAD/examples/2024-09-01-redux-for-production/src/app/favicon.ico -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/.husky/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | exec < /dev/tty && npx cz --hook || true 5 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EarlyNode/tutorials/HEAD/examples/2024-06-24-complex-navigation-expo/assets/images/icon.png -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EarlyNode/tutorials/HEAD/examples/2024-07-16-set-up-next-js-production/src/app/favicon.ico -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/posts/posts-types.ts: -------------------------------------------------------------------------------- 1 | export type Post = { 2 | userId: number; 3 | id: number; 4 | title: string; 5 | body: string; 6 | }; 7 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EarlyNode/tutorials/HEAD/examples/2024-06-24-complex-navigation-expo/assets/images/favicon.png -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EarlyNode/tutorials/HEAD/examples/2024-06-24-complex-navigation-expo/assets/images/splash.png -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function (api) { 2 | api.cache(true); 3 | return { 4 | presets: ["babel-preset-expo"], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /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/assets/images/react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EarlyNode/tutorials/HEAD/examples/2024-06-24-complex-navigation-expo/assets/images/react-logo.png -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/assets/images/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EarlyNode/tutorials/HEAD/examples/2024-06-24-complex-navigation-expo/assets/images/adaptive-icon.png -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/assets/images/react-logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EarlyNode/tutorials/HEAD/examples/2024-06-24-complex-navigation-expo/assets/images/react-logo@2x.png -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/assets/images/react-logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EarlyNode/tutorials/HEAD/examples/2024-06-24-complex-navigation-expo/assets/images/react-logo@3x.png -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/assets/fonts/SpaceMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EarlyNode/tutorials/HEAD/examples/2024-06-24-complex-navigation-expo/assets/fonts/SpaceMono-Regular.ttf -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/lib/utils.js: -------------------------------------------------------------------------------- 1 | import { clsx } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | export function cn(...inputs) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/hocs/public-page.ts: -------------------------------------------------------------------------------- 1 | import { compose } from '@reduxjs/toolkit'; 2 | 3 | import withLoading from './with-loading'; 4 | 5 | export default compose(withLoading); 6 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/assets/images/partial-react-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EarlyNode/tutorials/HEAD/examples/2024-06-24-complex-navigation-expo/assets/images/partial-react-logo.png -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /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-09-01-redux-for-production/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/features/internationalization/i18n-config.ts: -------------------------------------------------------------------------------- 1 | export const i18n = { 2 | defaultLocale: 'en-US', 3 | locales: ['en-US'], 4 | } as const; 5 | 6 | export type Locale = (typeof i18n)['locales'][number]; 7 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from 'clsx'; 2 | import { twMerge } from 'tailwind-merge'; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/features/internationalization/dictionaries/en-US.json: -------------------------------------------------------------------------------- 1 | { 2 | "counter": { 3 | "decrement": "Decrement", 4 | "increment": "Increment" 5 | }, 6 | "landing": { 7 | "welcome": "Welcome" 8 | } 9 | } -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/features/dashboard/dashboard-page-component.js: -------------------------------------------------------------------------------- 1 | export function DashboardPageComponent({ currentUsersEmail }) { 2 | return ( 3 |
4 |

Welcome back, {currentUsersEmail}!

5 |
6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/hocs/authenticated-page.ts: -------------------------------------------------------------------------------- 1 | import { compose } from '@reduxjs/toolkit'; 2 | 3 | import withAuth from './with-auth'; 4 | import withLoading from './with-loading'; 5 | 6 | export default compose(withLoading, withAuth); 7 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/hocs/redirect-if-logged-in.ts: -------------------------------------------------------------------------------- 1 | import { selectIsAuthenticated } from '@/features/user-profiles/user-profiles-reducer'; 2 | 3 | import redirect from './redirect'; 4 | 5 | export default redirect(selectIsAuthenticated); 6 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/app/dashboard/page.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import DashboardPage from '@/features/dashboard/dashboard-page-container'; 3 | import authenticatedPage from '@/hocs/authenticated-page'; 4 | 5 | export default authenticatedPage(DashboardPage); 6 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/app/posts/page.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { PostsPageComponent } from '@/features/posts/posts-page-component'; 3 | import authenticatedPage from '@/hocs/authenticated-page'; 4 | 5 | export default authenticatedPage(PostsPageComponent); 6 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/redux/root-saga.js: -------------------------------------------------------------------------------- 1 | import { all } from 'redux-saga/effects'; 2 | 3 | import { watchHandleLoginClicked } from '../../features/user-profiles/user-profile-saga'; 4 | 5 | export function* rootSaga() { 6 | yield all([watchHandleLoginClicked()]); 7 | } 8 | -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/src/app/hooks.js: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector, useStore } from 'react-redux'; 2 | 3 | export const useAppDispatch = useDispatch.withTypes(); 4 | export const useAppSelector = useSelector.withTypes(); 5 | export const useAppStore = useStore.withTypes(); 6 | -------------------------------------------------------------------------------- /examples/2024-07-04-master-higher-order-components/with-layout.jsx: -------------------------------------------------------------------------------- 1 | import { Layout } from './layout'; 2 | 3 | export default ({ showHeader = true } = {}) => 4 | Component => 5 | props => ( 6 | 7 | 8 | 9 | ); 10 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/tests/setup-test-environment.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/vitest'; 2 | 3 | // See https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html#configuring-your-testing-environment. 4 | // @ts-ignore 5 | globalThis.IS_REACT_ACT_ENVIRONMENT = true; 6 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/user-profiles/user-profiles-types.ts: -------------------------------------------------------------------------------- 1 | export type UserProfile = { 2 | /** 3 | * Email of the user. 4 | */ 5 | email: string; 6 | /** 7 | * The users ID. 8 | */ 9 | id: string; 10 | /** 11 | * Name of the user. 12 | */ 13 | name: string; 14 | }; 15 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/hocs/with-router.js: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useRouter } from 'next/navigation'; 3 | 4 | export const withRouter = Component => { 5 | function WithRouter(props) { 6 | const router = useRouter(); 7 | 8 | return ; 9 | } 10 | 11 | return WithRouter; 12 | }; 13 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true, 5 | "paths": { 6 | "@/*": [ 7 | "./*" 8 | ] 9 | } 10 | }, 11 | "include": [ 12 | "**/*.ts", 13 | "**/*.tsx", 14 | ".expo/types/**/*.ts", 15 | "expo-env.d.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/hocs/with-loading.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import AppLoading from '@/features/app-loading/app-loading-container'; 3 | import { selectAppFinishedLoading } from '@/features/app-loading/app-loading-reducer'; 4 | 5 | import requiresPermission from './requires-permission'; 6 | 7 | export default requiresPermission(AppLoading, selectAppFinishedLoading); 8 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/hocs/with-auth.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import UserAuthentication from '@/features/user-authentication/user-authentication-container'; 3 | import { selectIsAuthenticated } from '@/features/user-profiles/user-profiles-reducer'; 4 | 5 | import requiresPermission from './requires-permission'; 6 | 7 | export default requiresPermission(UserAuthentication, selectIsAuthenticated); 8 | -------------------------------------------------------------------------------- /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-08-17-memoization/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "2024-08-17-memoization", 3 | "version": "1.0.0", 4 | "main": "example-1.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "description": "", 11 | "devDependencies": { 12 | "@types/node": "22.4.0", 13 | "typescript": "5.5.4" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/components/__tests__/ThemedText-test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import renderer from "react-test-renderer"; 3 | 4 | import { ThemedText } from "../ThemedText"; 5 | 6 | it(`renders correctly`, () => { 7 | const tree = renderer 8 | .create(Snapshot test!) 9 | .toJSON(); 10 | 11 | expect(tree).toMatchSnapshot(); 12 | }); 13 | -------------------------------------------------------------------------------- /examples/2024-07-04-master-higher-order-components/layout.jsx: -------------------------------------------------------------------------------- 1 | export function Layout({ children, showHeader = true }) { 2 | return ( 3 |
4 | {showHeader && ( 5 |
6 |

Some Title

7 |
8 | )} 9 | 10 |
{children}
11 | 12 |
13 |

Some footer

14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/playwright/example.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | test.describe('landing page', () => { 4 | test('given any user: shows the test user', async ({ page }) => { 5 | await page.goto('/'); 6 | 7 | await expect(page.getByText('Jan Hesters')).toBeVisible(); 8 | await expect(page.getByText('jan@reactsquad.io')).toBeVisible(); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/features/user-profiles/user-profile-api.js: -------------------------------------------------------------------------------- 1 | export const fetchUserById = async id => { 2 | const response = await fetch( 3 | `https://jsonplaceholder.typicode.com/users/${id}`, 4 | ); 5 | return await response.json(); 6 | }; 7 | 8 | export const fetchUsers = async () => { 9 | const response = await fetch('https://jsonplaceholder.typicode.com/users'); 10 | return await response.json(); 11 | }; 12 | -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/src/app/store-provider.js: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useRef } from 'react'; 3 | import { Provider } from 'react-redux'; 4 | 5 | import { makeStore } from './store'; 6 | 7 | export function StoreProvider({ children }) { 8 | const storeRef = useRef(); 9 | 10 | if (!storeRef.current) { 11 | storeRef.current = makeStore(); 12 | } 13 | 14 | return {children}; 15 | } 16 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/redux/store-provider.js: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useRef } from 'react'; 3 | import { Provider } from 'react-redux'; 4 | 5 | import { makeStore } from './store'; 6 | 7 | export function StoreProvider({ children }) { 8 | const storeRef = useRef(); 9 | 10 | if (!storeRef.current) { 11 | storeRef.current = makeStore(); 12 | } 13 | 14 | return {children}; 15 | } 16 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest } from 'next/server'; 2 | 3 | import { localizationMiddleware } from './features/internationalization/localization-middleware'; 4 | 5 | // Matcher ignoring `/_next/` and `/api/` and svg files. 6 | export const config = { matcher: ['/((?!api|_next|.*.svg$).*)'] }; 7 | 8 | export function middleware(request: NextRequest) { 9 | return localizationMiddleware(request); 10 | } 11 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": false, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/app/login/page.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { compose } from 'redux'; 3 | 4 | import UserAuthentication from '@/features/user-authentication/user-authentication-container'; 5 | import withPublicPage from '@/hocs/public-page'; 6 | import redirectIfLoggedIn from '@/hocs/redirect-if-logged-in'; 7 | 8 | export default compose( 9 | redirectIfLoggedIn('/dashboard'), 10 | withPublicPage, 11 | )(UserAuthentication); 12 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/redux/hooks.ts: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector, useStore } from 'react-redux'; 2 | 3 | import type { AppDispatch, AppStore, RootState } from './store'; 4 | 5 | // Use throughout your app instead of plain `useDispatch` and `useSelector`. 6 | export const useAppDispatch = useDispatch.withTypes(); 7 | export const useAppSelector = useSelector.withTypes(); 8 | export const useAppStore = useStore.withTypes(); 9 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/features/dashboard/dashboard-page-container.js: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { connect } from 'react-redux'; 3 | 4 | import { selectCurrentUsersEmail } from '../user-profiles/user-profile-reducer'; 5 | import { DashboardPageComponent } from './dashboard-page-component'; 6 | 7 | const mapStateToProps = state => ({ 8 | currentUsersEmail: selectCurrentUsersEmail(state), 9 | }); 10 | 11 | export default connect(mapStateToProps)(DashboardPageComponent); 12 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/lib/prisma.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client'; 2 | 3 | declare global { 4 | var __database__: PrismaClient; 5 | } 6 | 7 | let prisma: PrismaClient; 8 | 9 | if (process.env.NODE_ENV === 'production') { 10 | prisma = new PrismaClient(); 11 | } else { 12 | if (!global.__database__) { 13 | global.__database__ = new PrismaClient(); 14 | } 15 | prisma = global.__database__; 16 | } 17 | 18 | export default prisma; 19 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/user-authentication/user-authentication-api.ts: -------------------------------------------------------------------------------- 1 | export const loginRequest = (email: string, password: string) => { 2 | return new Promise((resolve, reject) => { 3 | setTimeout(() => { 4 | resolve('token'); 5 | }, 2000); 6 | }); 7 | }; 8 | 9 | export const logoutRequest = () => { 10 | return new Promise((resolve, reject) => { 11 | setTimeout(() => { 12 | resolve(); 13 | }, 2000); 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/redux/root-reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import { 4 | reducer as exampleReducer, 5 | slice as exampleSlice, 6 | } from '../../features/example/example-reducer'; 7 | import { 8 | reducer as userProfileReducer, 9 | slice as userProfileSlice, 10 | } from '../../features/user-profiles/user-profile-reducer'; 11 | 12 | export const rootReducer = combineReducers({ 13 | [exampleSlice]: exampleReducer, 14 | [userProfileSlice]: userProfileReducer, 15 | }); 16 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/posts/posts-page-component.tsx: -------------------------------------------------------------------------------- 1 | import { AddPostComponent } from './add-post-component'; 2 | import { PostsListComponent } from './posts-list-component'; 3 | 4 | export function PostsPageComponent() { 5 | return ( 6 |
7 |
8 | 9 |
10 | 11 |
12 | 13 |
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSameLine: false, 4 | bracketSpacing: true, 5 | htmlWhitespaceSensitivity: 'css', 6 | insertPragma: false, 7 | jsxSingleQuote: false, 8 | plugins: ['prettier-plugin-tailwindcss'], 9 | printWidth: 80, 10 | proseWrap: 'always', 11 | quoteProps: 'as-needed', 12 | requirePragma: false, 13 | semi: true, 14 | singleQuote: true, 15 | tabWidth: 2, 16 | trailingComma: 'all', 17 | useTabs: false, 18 | }; 19 | -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSameLine: false, 4 | bracketSpacing: true, 5 | htmlWhitespaceSensitivity: 'css', 6 | insertPragma: false, 7 | jsxSingleQuote: false, 8 | plugins: ['prettier-plugin-tailwindcss'], 9 | printWidth: 80, 10 | proseWrap: 'always', 11 | quoteProps: 'as-needed', 12 | requirePragma: false, 13 | semi: true, 14 | singleQuote: true, 15 | tabWidth: 2, 16 | trailingComma: 'all', 17 | useTabs: false, 18 | }; 19 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/features/user-authentication/example.test.tsx: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | 3 | import { render, screen } from '@/tests/react-test-utils'; 4 | 5 | function MyReactComponent() { 6 | return
My React Component
; 7 | } 8 | 9 | describe('MyReactComponent', () => { 10 | test('given no props: renders a text', () => { 11 | render(); 12 | 13 | expect(screen.getByText('My React Component')).toBeInTheDocument(); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSameLine: false, 4 | bracketSpacing: true, 5 | htmlWhitespaceSensitivity: 'css', 6 | insertPragma: false, 7 | jsxSingleQuote: false, 8 | plugins: ['prettier-plugin-tailwindcss'], 9 | printWidth: 80, 10 | proseWrap: 'always', 11 | quoteProps: 'as-needed', 12 | requirePragma: false, 13 | semi: true, 14 | singleQuote: true, 15 | tabWidth: 2, 16 | trailingComma: 'all', 17 | useTabs: false, 18 | }; 19 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/components/__tests__/__snapshots__/ThemedText-test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`renders correctly 1`] = ` 4 | 22 | Snapshot test! 23 | 24 | `; 25 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSameLine: false, 4 | bracketSpacing: true, 5 | htmlWhitespaceSensitivity: 'css', 6 | insertPragma: false, 7 | jsxSingleQuote: false, 8 | plugins: ['prettier-plugin-tailwindcss'], 9 | printWidth: 80, 10 | proseWrap: 'always', 11 | quoteProps: 'as-needed', 12 | requirePragma: false, 13 | semi: true, 14 | singleQuote: true, 15 | tabWidth: 2, 16 | trailingComma: 'all', 17 | useTabs: false, 18 | }; 19 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/features/internationalization/use-switch-locale-href.ts: -------------------------------------------------------------------------------- 1 | import { usePathname } from 'next/navigation'; 2 | 3 | import { Locale } from './i18n-config'; 4 | 5 | export function useSwitchLocaleHref() { 6 | const pathName = usePathname(); 7 | 8 | const getSwitchLocaleHref = (locale: Locale) => { 9 | if (!pathName) return '/'; 10 | const segments = pathName.split('/'); 11 | segments[1] = locale; 12 | return segments.join('/'); 13 | }; 14 | 15 | return getSwitchLocaleHref; 16 | } 17 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/hooks/useColorScheme.web.ts: -------------------------------------------------------------------------------- 1 | // NOTE: The default React Native styling doesn't support server rendering. 2 | // Server rendered styles should not change between the first render of the HTML 3 | // and the first render on the client. Typically, web developers will use CSS media queries 4 | // to render different styles on the client and server, these aren't directly supported in React Native 5 | // but can be achieved using a styling library like Nativewind. 6 | export function useColorScheme() { 7 | return "light"; 8 | } 9 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/components/spinner.tsx: -------------------------------------------------------------------------------- 1 | import { SVGProps } from 'react'; 2 | 3 | export const Spinner = (props: SVGProps) => ( 4 | 19 | ); 20 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/features/internationalization/get-dictionaries.ts: -------------------------------------------------------------------------------- 1 | import 'server-only'; 2 | 3 | import type { Locale } from './i18n-config'; 4 | 5 | // We enumerate all dictionaries here for better linting and typescript support 6 | // We also get the default import for cleaner types 7 | const dictionaries = { 8 | 'en-US': () => 9 | import('./dictionaries/en-US.json').then(module => module.default), 10 | }; 11 | 12 | export const getDictionary = async (locale: Locale) => 13 | dictionaries[locale]?.() ?? dictionaries['en-US'](); 14 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/components/navigation/TabBarIcon.tsx: -------------------------------------------------------------------------------- 1 | // You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/ 2 | 3 | import Ionicons from "@expo/vector-icons/Ionicons"; 4 | import { type IconProps } from "@expo/vector-icons/build/createIconSet"; 5 | import { type ComponentProps } from "react"; 6 | 7 | export function TabBarIcon({ 8 | style, 9 | ...rest 10 | }: IconProps["name"]>) { 11 | return ; 12 | } 13 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/hocs/requires-permission/requires-permission-component.tsx: -------------------------------------------------------------------------------- 1 | interface Props { 2 | NotPermittedComponent: React.ComponentType; 3 | PermittedComponent: React.ComponentType; 4 | isPermitted: boolean; 5 | } 6 | 7 | export const RequiresPermission = (props: Props & A & B) => { 8 | const { NotPermittedComponent, PermittedComponent, isPermitted } = props; 9 | 10 | return isPermitted ? ( 11 | 12 | ) : ( 13 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/redux/root-saga.ts: -------------------------------------------------------------------------------- 1 | import { all } from 'redux-saga/effects'; 2 | 3 | import { watchLoadApp } from '@/features/app-loading/app-loading-saga'; 4 | import { 5 | watchLogin, 6 | watchLogout, 7 | } from '@/features/user-authentication/user-authentication-saga'; 8 | import { watchFetchUserProfiles } from '@/features/user-profiles/user-profiles-saga'; 9 | 10 | export function* rootSaga() { 11 | yield all([ 12 | watchFetchUserProfiles(), 13 | watchLoadApp(), 14 | watchLogin(), 15 | watchLogout(), 16 | ]); 17 | } 18 | -------------------------------------------------------------------------------- /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-08-20-redux-saga/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 5 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 6 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 7 | ], 8 | theme: { 9 | extend: { 10 | backgroundImage: { 11 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 12 | 'gradient-conic': 13 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | }; 19 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 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-08-09-redux-basics/src/app/layout.js: -------------------------------------------------------------------------------- 1 | import './globals.css'; 2 | 3 | import { Inter } from 'next/font/google'; 4 | 5 | import { StoreProvider } from './store-provider'; 6 | 7 | const inter = Inter({ subsets: ['latin'] }); 8 | 9 | export const metadata = { 10 | title: 'Create Next App', 11 | description: 'Generated by create next app', 12 | }; 13 | 14 | export default function RootLayout({ children }) { 15 | return ( 16 | 17 | 18 | {children} 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/src/app/user-profile-container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | 3 | import { 4 | getCurrentUsersEmail, 5 | loginSuccess, 6 | selectIsLoggedIn, 7 | } from './user-profile'; 8 | import { UserProfileComponent } from './user-profile-component'; 9 | 10 | const mapStateToProps = state => ({ 11 | email: getCurrentUsersEmail(state), 12 | isLoggedIn: selectIsLoggedIn(state), 13 | }); 14 | 15 | const mapDispatchToProps = { onLoginClicked: loginSuccess }; 16 | 17 | export default connect( 18 | mapStateToProps, 19 | mapDispatchToProps, 20 | )(UserProfileComponent); 21 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/app/[lang]/page.tsx: -------------------------------------------------------------------------------- 1 | import { retrieveUserProfileFromDatabaseByEmail } from '@/features/user-profile/user-profile-model'; 2 | 3 | export default async function Dashboard() { 4 | const user = 5 | await retrieveUserProfileFromDatabaseByEmail('jan@reactsquad.io'); 6 | 7 | return ( 8 |
9 |

User Profile

10 | {user ? ( 11 |
    12 |
  • Name: {user.name}
  • 13 |
  • Email: {user.email}
  • 14 |
15 | ) : ( 16 |

User not found.

17 | )} 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /examples/2024-08-17-memoization/example-1.js: -------------------------------------------------------------------------------- 1 | function memoize(fn) { 2 | const cache = new Map(); 3 | 4 | return function(...args) { 5 | const key = JSON.stringify(args); 6 | 7 | if (cache.has(key)) { 8 | return cache.get(key); 9 | } 10 | 11 | const result = fn.apply(this, args); 12 | cache.set(key, result); 13 | 14 | return result; 15 | }; 16 | } 17 | 18 | function add(a, b) { 19 | return a + b; 20 | } 21 | 22 | const memoizedAdd = memoize(add); 23 | 24 | console.log(memoizedAdd(2, 3)); // Calculates and caches result. 25 | console.log(memoizedAdd(2, 3)); // Returns cached result. 26 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "postgresql" 7 | // Uses connection pooling 8 | url = env("DATABASE_URL") 9 | } 10 | 11 | model UserProfile { 12 | id String @id @default(cuid()) 13 | createdAt DateTime @default(now()) 14 | updatedAt DateTime @updatedAt 15 | email String @unique 16 | name String @default("") 17 | acceptedTermsAndConditions Boolean @default(false) 18 | } 19 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/app/layout.js: -------------------------------------------------------------------------------- 1 | import './globals.css'; 2 | 3 | import { Inter } from 'next/font/google'; 4 | 5 | import { StoreProvider } from './redux/store-provider'; 6 | 7 | const inter = Inter({ subsets: ['latin'] }); 8 | 9 | export const metadata = { 10 | title: 'Jan Hesters Redux Tutorial', 11 | description: 'Part one of three to master Redux.', 12 | }; 13 | 14 | export default function RootLayout({ children }) { 15 | return ( 16 | 17 | 18 | {children} 19 | 20 | 21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/redux/store-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useRef } from 'react'; 3 | import { Provider } from 'react-redux'; 4 | 5 | import { AppStore, makeStore } from './store'; 6 | 7 | export default function StoreProvider({ 8 | children, 9 | }: { 10 | children: React.ReactNode; 11 | }) { 12 | const storeRef = useRef(); 13 | 14 | if (!storeRef.current) { 15 | // Creates the store instance the first time this renders. 16 | const store = makeStore(); 17 | storeRef.current = store; 18 | } 19 | 20 | return {children}; 21 | } 22 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/components/ThemedView.tsx: -------------------------------------------------------------------------------- 1 | import { View, type ViewProps } from "react-native"; 2 | 3 | import { useThemeColor } from "@/hooks/useThemeColor"; 4 | 5 | export type ThemedViewProps = ViewProps & { 6 | lightColor?: string; 7 | darkColor?: string; 8 | }; 9 | 10 | export function ThemedView({ 11 | style, 12 | lightColor, 13 | darkColor, 14 | ...otherProps 15 | }: ThemedViewProps) { 16 | const backgroundColor = useThemeColor( 17 | { light: lightColor, dark: darkColor }, 18 | "background", 19 | ); 20 | 21 | return ; 22 | } 23 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/app-loading/app-loading-component.tsx: -------------------------------------------------------------------------------- 1 | import { Spinner } from '@/components/spinner'; 2 | import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; 3 | 4 | export function AppLoadingComponent() { 5 | return ( 6 |
7 | 8 | 9 | Loading ... 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/features/example/example-saga.js: -------------------------------------------------------------------------------- 1 | import { call, put, select, take } from '../../app/redux/effects'; 2 | import { increment, incrementBy, selectCount } from './example-reducer'; 3 | 4 | const fetchUser = async id => { 5 | const response = await fetch( 6 | `https://jsonplaceholder.typicode.com/users/${id}`, 7 | ); 8 | return await response.json(); 9 | }; 10 | 11 | export function* exampleSaga() { 12 | yield take('init'); 13 | yield put(increment()); 14 | const currentCount = yield select(selectCount); 15 | const user = yield call(fetchUser, currentCount); 16 | yield put(incrementBy(user.name.length)); 17 | } 18 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/tests/react-test-utils.tsx: -------------------------------------------------------------------------------- 1 | import type { RenderOptions } from '@testing-library/react'; 2 | import { render } from '@testing-library/react'; 3 | import type { ReactElement } from 'react'; 4 | 5 | const customRender = ( 6 | ui: ReactElement, 7 | options?: Omit, 8 | ) => 9 | render(ui, { 10 | wrapper: ({ children }) => <>{children}, 11 | ...options, 12 | }); 13 | 14 | // re-export everything 15 | export * from '@testing-library/react'; 16 | 17 | // override render method 18 | export { customRender as render }; 19 | export { default as userEvent } from '@testing-library/user-event'; 20 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/app-loading/app-loading-saga.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit'; 2 | import { call, put, takeLeading } from 'redux-saga/effects'; 3 | 4 | import { handleFetchCurrentUsersProfile } from '@/features/user-profiles/user-profiles-saga'; 5 | 6 | import { finishedAppLoading, name } from './app-loading-reducer'; 7 | 8 | export const loadApp = createAction(`${name}/loadApp`); 9 | 10 | function* handleLoadApp() { 11 | yield call(handleFetchCurrentUsersProfile); 12 | yield put(finishedAppLoading()); 13 | } 14 | 15 | export function* watchLoadApp() { 16 | yield takeLeading(loadApp.type, handleLoadApp); 17 | } 18 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/hooks/useThemeColor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Learn more about light and dark modes: 3 | * https://docs.expo.dev/guides/color-schemes/ 4 | */ 5 | 6 | import { useColorScheme } from "react-native"; 7 | 8 | import { Colors } from "@/constants/Colors"; 9 | 10 | export function useThemeColor( 11 | props: { light?: string; dark?: string }, 12 | colorName: keyof typeof Colors.light & keyof typeof Colors.dark, 13 | ) { 14 | const theme = useColorScheme() ?? "light"; 15 | const colorFromProps = props[theme]; 16 | 17 | if (colorFromProps) { 18 | return colorFromProps; 19 | } else { 20 | return Colors[theme][colorName]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/src/app/user-profile-component.js: -------------------------------------------------------------------------------- 1 | export const UserProfileComponent = ({ isLoggedIn, email, onLoginClicked }) => ( 2 |
3 | {isLoggedIn ? ( 4 |

Email: {email}

5 | ) : ( 6 | 14 | )} 15 |
16 | ); 17 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | /test-results/ 38 | /playwright-report/ 39 | /blob-report/ 40 | /playwright/.cache/ 41 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/hocs/hoist-statics.ts: -------------------------------------------------------------------------------- 1 | import hoistNonReactStatics from 'hoist-non-react-statics'; 2 | 3 | type HOC = ( 4 | Component: React.ComponentType, 5 | ) => React.ComponentType; 6 | 7 | const hoistStatics = 8 | ( 9 | higherOrderComponent: HOC, 10 | ): HOC => 11 | (BaseComponent: React.ComponentType) => { 12 | const NewComponent = higherOrderComponent(BaseComponent); 13 | hoistNonReactStatics(NewComponent, BaseComponent); 14 | return NewComponent; 15 | }; 16 | 17 | export default hoistStatics; 18 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/components/ui/label.jsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as LabelPrimitive from '@radix-ui/react-label'; 4 | import { cva } from 'class-variance-authority'; 5 | import * as React from 'react'; 6 | 7 | import { cn } from '../../lib/utils'; 8 | 9 | const labelVariants = cva( 10 | 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70', 11 | ); 12 | 13 | const Label = React.forwardRef(({ className, ...props }, ref) => ( 14 | 19 | )); 20 | Label.displayName = LabelPrimitive.Root.displayName; 21 | 22 | export { Label }; 23 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/app/login/page.tsx: -------------------------------------------------------------------------------- 1 | import { getDictionary } from '@/features/internationalization/get-dictionaries'; 2 | import { Locale } from '@/features/internationalization/i18n-config'; 3 | 4 | import { CounterComponent } from './counter-component'; 5 | 6 | export default async function IndexPage({ 7 | params: { lang }, 8 | }: { 9 | params: { lang: Locale }; 10 | }) { 11 | const dictionary = await getDictionary(lang); 12 | 13 | return ( 14 |
15 |

Current locale: {lang}

16 |

This text is rendered on the server: {dictionary.landing.welcome}

17 | 18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./src/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/features/user-profiles/user-profile-saga.js: -------------------------------------------------------------------------------- 1 | import { call, put, takeLeading } from 'redux-saga/effects'; 2 | 3 | import { fetchUserById, fetchUsers } from './user-profile-api'; 4 | import { 5 | fetchedUsers, 6 | loginClicked, 7 | loginSucceeded, 8 | } from './user-profile-reducer'; 9 | 10 | function* handleLoginClicked({ payload: { id, router } }) { 11 | const user = yield call(fetchUserById, id); 12 | yield put(loginSucceeded(user)); 13 | const users = yield call(fetchUsers); 14 | yield put(fetchedUsers(users)); 15 | yield call(router.push, '/dashboard'); 16 | } 17 | 18 | export function* watchHandleLoginClicked() { 19 | yield takeLeading(loginClicked().type, handleLoginClicked); 20 | } 21 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/user-profiles/user-profiles-api.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | import { UserProfile } from './user-profiles-types'; 4 | 5 | export const getCurrentUserRequest = ( 6 | token: string, 7 | ): Promise => 8 | token 9 | ? axios 10 | .get(`https://jsonplaceholder.typicode.com/users/1`) 11 | .then(({ data }) => data) 12 | : new Promise(resolve => { 13 | setTimeout(() => { 14 | resolve(null); 15 | }, 1000); 16 | }); 17 | 18 | export const getUsersRequest = (token: string) => 19 | axios 20 | .get('https://jsonplaceholder.typicode.com/users') 21 | .then(({ data }) => data); 22 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import prettier from "eslint-plugin-prettier"; 2 | import path from "node:path"; 3 | import { fileURLToPath } from "node:url"; 4 | import js from "@eslint/js"; 5 | import { FlatCompat } from "@eslint/eslintrc"; 6 | 7 | const __filename = fileURLToPath(import.meta.url); 8 | const __dirname = path.dirname(__filename); 9 | const compat = new FlatCompat({ 10 | baseDirectory: __dirname, 11 | recommendedConfig: js.configs.recommended, 12 | allConfig: js.configs.all, 13 | }); 14 | 15 | export default [ 16 | ...compat.extends("expo", "prettier"), 17 | { 18 | plugins: { 19 | prettier, 20 | }, 21 | rules: { 22 | "prettier/prettier": "error", 23 | }, 24 | }, 25 | ]; 26 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "esnext" 7 | ], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": false, 11 | "noEmit": true, 12 | "incremental": true, 13 | "module": "esnext", 14 | "esModuleInterop": true, 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "preserve", 19 | "plugins": [ 20 | { 21 | "name": "next" 22 | } 23 | ] 24 | }, 25 | "include": [ 26 | "next-env.d.ts", 27 | ".next/types/**/*.ts", 28 | "**/*.ts", 29 | "**/*.tsx" 30 | ], 31 | "exclude": [ 32 | "node_modules" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css'; 2 | 3 | import type { Metadata } from 'next'; 4 | import { Inter } from 'next/font/google'; 5 | 6 | import StoreProvider from '@/redux/store-provider'; 7 | 8 | const inter = Inter({ subsets: ['latin'] }); 9 | 10 | export const metadata: Metadata = { 11 | title: 'Jan Hesters Production Redux Tutorial', 12 | description: 'Part three of five to master Redux.', 13 | }; 14 | 15 | export default function RootLayout({ 16 | children, 17 | }: Readonly<{ 18 | children: React.ReactNode; 19 | }>) { 20 | return ( 21 | 22 | 23 | {children} 24 | 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/app/[lang]/layout.tsx: -------------------------------------------------------------------------------- 1 | import '../globals.css'; 2 | 3 | import type { Metadata } from 'next'; 4 | import { Inter } from 'next/font/google'; 5 | 6 | import { Locale } from '@/features/internationalization/i18n-config'; 7 | 8 | const inter = Inter({ subsets: ['latin'] }); 9 | 10 | export const metadata: Metadata = { 11 | title: 'Create Next App', 12 | description: 'Generated by create next app', 13 | }; 14 | 15 | export default function RootLayout({ 16 | children, 17 | params, 18 | }: Readonly<{ 19 | children: React.ReactNode; 20 | params: { lang: Locale }; 21 | }>) { 22 | return ( 23 | 24 | {children} 25 | 26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { applyMiddleware, legacy_createStore as createStore } from 'redux'; 2 | import createSagaMiddleware from 'redux-saga'; 3 | 4 | import { rootReducer } from './root-reducer'; 5 | import { rootSaga } from './root-saga'; 6 | 7 | const logger = store => next => action => { 8 | console.log('dispatching', action); 9 | let result = next(action); 10 | console.log('next state', store.getState()); 11 | return result; 12 | }; 13 | 14 | export const makeStore = () => { 15 | const sagaMiddleware = createSagaMiddleware(); 16 | const store = createStore( 17 | rootReducer, 18 | rootReducer(), 19 | applyMiddleware(logger, sagaMiddleware), 20 | ); 21 | sagaMiddleware.run(rootSaga); 22 | return store; 23 | }; 24 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/app-loading/app-loading-reducer.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import { not, pipe, prop } from 'ramda'; 3 | 4 | const initialState = { appIsLoading: true }; 5 | 6 | export const { 7 | actions: { finishedAppLoading }, 8 | name, 9 | reducer, 10 | selectors: { selectAppIsLoading }, 11 | } = createSlice({ 12 | name: 'appLoading', 13 | initialState, 14 | reducers: { 15 | finishedAppLoading: state => { 16 | state.appIsLoading = false; 17 | }, 18 | }, 19 | selectors: { 20 | selectAppIsLoading: prop<'appIsLoading'>('appIsLoading'), 21 | }, 22 | }); 23 | 24 | /** 25 | * SELECTORS 26 | */ 27 | 28 | export const selectAppFinishedLoading = pipe(selectAppIsLoading, not); 29 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/hocs/requires-permission/index.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { curry } from 'ramda'; 3 | import { connect } from 'react-redux'; 4 | 5 | import { RootState } from '@/redux/store'; 6 | 7 | import { RequiresPermission } from './requires-permission-component'; 8 | 9 | function requiresPermission( 10 | NotPermittedComponent: React.ComponentType, 11 | selector: (state: RootState) => boolean, 12 | PermittedComponent: React.ComponentType, 13 | ) { 14 | const mapStateToProps = (state: RootState) => ({ 15 | NotPermittedComponent, 16 | PermittedComponent, 17 | isPermitted: selector(state), 18 | }); 19 | 20 | return connect(mapStateToProps)(RequiresPermission); 21 | } 22 | 23 | export default curry(requiresPermission); 24 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/app/login/counter-component.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | 5 | import type { getDictionary } from '@/features/internationalization/get-dictionaries'; 6 | 7 | export function CounterComponent({ 8 | dictionary, 9 | }: { 10 | dictionary: Awaited>['counter']; 11 | }) { 12 | const [count, setCount] = useState(0); 13 | return ( 14 |

15 | This component is rendered on client: 16 | 19 | {count} 20 | 23 |

24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/src/app/store.js: -------------------------------------------------------------------------------- 1 | import { 2 | applyMiddleware, 3 | combineReducers, 4 | legacy_createStore as createStore, 5 | } from 'redux'; 6 | import logger from 'redux-logger'; 7 | import { thunk } from 'redux-thunk'; 8 | 9 | import { 10 | reducer as exampleReducer, 11 | slice as exampleSlice, 12 | } from './example-reducer'; 13 | import { 14 | reducer as userProfileReducer, 15 | slice as userProfileSlice, 16 | } from './user-profile'; 17 | 18 | const rootReducer = combineReducers({ 19 | [exampleSlice]: exampleReducer, 20 | [userProfileSlice]: userProfileReducer, 21 | }); 22 | 23 | export const makeStore = () => { 24 | return createStore( 25 | rootReducer, 26 | rootReducer(), 27 | applyMiddleware(thunk, logger), 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/features/user-authentication/user-authentication-page-container.js: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { connect } from 'react-redux'; 4 | import { compose } from 'redux'; 5 | 6 | import { withRouter } from '../../hocs/with-router'; 7 | import { 8 | loginClicked, 9 | selectIsLoading, 10 | } from '../user-profiles/user-profile-reducer'; 11 | import { UserAuthenticationPageComponent } from './user-authentication-page-component'; 12 | 13 | const mapStateToProps = state => ({ 14 | isLoading: selectIsLoading(state), 15 | }); 16 | 17 | const mapDispatchToProps = { 18 | onLoginClicked: loginClicked, 19 | }; 20 | 21 | export default compose( 22 | withRouter, 23 | connect(mapStateToProps, mapDispatchToProps), 24 | )(UserAuthenticationPageComponent); 25 | -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --foreground-rgb: 0, 0, 0; 7 | --background-start-rgb: 214, 219, 220; 8 | --background-end-rgb: 255, 255, 255; 9 | } 10 | 11 | @media (prefers-color-scheme: dark) { 12 | :root { 13 | --foreground-rgb: 255, 255, 255; 14 | --background-start-rgb: 0, 0, 0; 15 | --background-end-rgb: 0, 0, 0; 16 | } 17 | } 18 | 19 | body { 20 | color: rgb(var(--foreground-rgb)); 21 | background: linear-gradient( 22 | to bottom, 23 | transparent, 24 | rgb(var(--background-end-rgb)) 25 | ) 26 | rgb(var(--background-start-rgb)); 27 | } 28 | 29 | @layer utilities { 30 | .text-balance { 31 | text-wrap: balance; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/components/ui/input.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '../../lib/utils'; 4 | 5 | const Input = React.forwardRef(({ className, type, ...props }, ref) => { 6 | return ( 7 | 16 | ); 17 | }); 18 | Input.displayName = 'Input'; 19 | 20 | export { Input }; 21 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/app-loading/app-loading-container.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useEffect } from 'react'; 3 | import type { ConnectedProps } from 'react-redux'; 4 | import { connect } from 'react-redux'; 5 | 6 | import { AppLoadingComponent } from './app-loading-component'; 7 | import { loadApp } from './app-loading-saga'; 8 | 9 | const mapDispatchToProps = { loadApp }; 10 | 11 | const connector = connect(undefined, mapDispatchToProps); 12 | 13 | type AppLoadingPropsFromRedux = ConnectedProps; 14 | 15 | function AppLoadingContainer({ loadApp }: AppLoadingPropsFromRedux) { 16 | useEffect(() => { 17 | loadApp(); 18 | }, [loadApp]); 19 | 20 | return ; 21 | } 22 | 23 | export default connector(AppLoadingContainer); 24 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react'; 2 | import tsconfigPaths from 'vite-tsconfig-paths'; 3 | import { defineConfig } from 'vitest/config'; 4 | 5 | export default defineConfig({ 6 | plugins: [react(), tsconfigPaths()], 7 | server: { 8 | port: 3000, 9 | }, 10 | test: { 11 | environment: 'happy-dom', 12 | globals: true, 13 | setupFiles: ['./src/tests/setup-test-environment.ts'], 14 | include: ['./src/**/*.{spec,test}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], 15 | // watch: { 16 | // ignored:[ 17 | // '.*\\/node_modules\\/.*', 18 | // '.*\\/build\\/.*', 19 | // '.*\\/postgres-data\\/.*', 20 | // ], 21 | // }, 22 | coverage: { 23 | reporter: ['text', 'json', 'html'], 24 | }, 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/user-authentication/user-authentication-container.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import type { ConnectedProps } from 'react-redux'; 3 | import { connect } from 'react-redux'; 4 | 5 | import { RootState } from '@/redux/store'; 6 | 7 | import { UserAuthenticationComponent } from './user-authentication-component'; 8 | import { login, selectIsAuthenticating } from './user-authentication-reducer'; 9 | 10 | const mapStateToProps = (state: RootState) => ({ 11 | isLoading: selectIsAuthenticating(state), 12 | }); 13 | 14 | const mapDispatchToProps = { onLogin: login }; 15 | 16 | const connector = connect(mapStateToProps, mapDispatchToProps); 17 | 18 | export type UserAuthenticationPropsFromRedux = ConnectedProps; 19 | 20 | export default connector(UserAuthenticationComponent); 21 | -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/src/app/example-reducer.js: -------------------------------------------------------------------------------- 1 | export const increment = () => ({ type: 'INCREMENT' }); 2 | export const incrementBy = payload => ({ type: 'INCREMENT_BY', payload }); 3 | export const reset = () => ({ type: 'RESET' }); 4 | 5 | export const slice = 'example'; 6 | const initialState = { count: 0 }; 7 | 8 | export const reducer = (state = initialState, { type, payload } = {}) => { 9 | switch (type) { 10 | case increment().type: { 11 | return { ...state, count: state.count + 1 }; 12 | } 13 | case incrementBy().type: { 14 | return { ...state, count: state.count + payload }; 15 | } 16 | case reset().type: { 17 | return initialState; 18 | } 19 | default: { 20 | return state; 21 | } 22 | } 23 | }; 24 | 25 | export const selectCount = state => state[slice].count; 26 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/components/ExternalLink.tsx: -------------------------------------------------------------------------------- 1 | import { Link } from "expo-router"; 2 | import { openBrowserAsync } from "expo-web-browser"; 3 | import { type ComponentProps } from "react"; 4 | import { Platform } from "react-native"; 5 | 6 | type Props = Omit, "href"> & { href: string }; 7 | 8 | export function ExternalLink({ href, ...rest }: Props) { 9 | return ( 10 | { 15 | if (Platform.OS !== "web") { 16 | // Prevent the default behavior of linking to the default browser on native. 17 | event.preventDefault(); 18 | // Open the link in an in-app browser. 19 | await openBrowserAsync(href); 20 | } 21 | }} 22 | /> 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /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/(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)/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-09-01-redux-for-production/src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /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-08-20-redux-saga/src/features/example/example-reducer.js: -------------------------------------------------------------------------------- 1 | import { pipe, prop } from 'ramda'; 2 | 3 | export const slice = 'example'; 4 | 5 | export const increment = () => ({ type: `${slice}/increment` }); 6 | export const incrementBy = payload => ({ 7 | type: `${slice}/incrementBy`, 8 | payload, 9 | }); 10 | 11 | const initialState = { 12 | count: 0, 13 | }; 14 | 15 | export const reducer = (state = initialState, { type, payload } = {}) => { 16 | switch (type) { 17 | case increment().type: { 18 | return { ...state, count: state.count + 1 }; 19 | } 20 | case incrementBy().type: { 21 | return { ...state, count: state.count + payload }; 22 | } 23 | default: { 24 | return state; 25 | } 26 | } 27 | }; 28 | 29 | export const selectExampleState = prop(slice); 30 | 31 | export const selectCount = pipe(selectExampleState, prop('count')); 32 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/constants/Colors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Below are the colors that are used in the app. The colors are defined in the light and dark mode. 3 | * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc. 4 | */ 5 | 6 | const tintColorLight = "#0a7ea4"; 7 | const tintColorDark = "#fff"; 8 | 9 | export const Colors = { 10 | light: { 11 | text: "#11181C", 12 | background: "#fff", 13 | tint: tintColorLight, 14 | icon: "#687076", 15 | tabIconDefault: "#687076", 16 | tabIconSelected: tintColorLight, 17 | }, 18 | dark: { 19 | text: "#ECEDEE", 20 | background: "#151718", 21 | tint: tintColorDark, 22 | icon: "#9BA1A6", 23 | tabIconDefault: "#9BA1A6", 24 | tabIconSelected: tintColorDark, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = "Input" 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/redux/root-reducer.ts: -------------------------------------------------------------------------------- 1 | import { combineReducers } from '@reduxjs/toolkit'; 2 | 3 | import { 4 | name as appLoadingSliceName, 5 | reducer as appLoadingReducer, 6 | } from '@/features/app-loading/app-loading-reducer'; 7 | import { 8 | reducer as postsReducer, 9 | reducerPath as postsSliceName, 10 | } from '@/features/posts/posts-api'; 11 | import { 12 | name as userAuthenticationSliceName, 13 | reducer as userAuthenticationReducer, 14 | } from '@/features/user-authentication/user-authentication-reducer'; 15 | import { 16 | name as userProfileSliceName, 17 | reducer as userProfileReducer, 18 | } from '@/features/user-profiles/user-profiles-reducer'; 19 | 20 | export const rootReducer = combineReducers({ 21 | [appLoadingSliceName]: appLoadingReducer, 22 | [postsSliceName]: postsReducer, 23 | [userAuthenticationSliceName]: userAuthenticationReducer, 24 | [userProfileSliceName]: userProfileReducer, 25 | }); 26 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/app/+not-found.tsx: -------------------------------------------------------------------------------- 1 | import { Link, Stack } from "expo-router"; 2 | import { StyleSheet } from "react-native"; 3 | 4 | import { ThemedText } from "@/components/ThemedText"; 5 | import { ThemedView } from "@/components/ThemedView"; 6 | 7 | export default function NotFoundScreen() { 8 | return ( 9 | <> 10 | 11 | 12 | This screen doesn't exist. 13 | 14 | Go to home screen! 15 | 16 | 17 | 18 | ); 19 | } 20 | 21 | const styles = StyleSheet.create({ 22 | container: { 23 | flex: 1, 24 | alignItems: "center", 25 | justifyContent: "center", 26 | padding: 20, 27 | }, 28 | link: { 29 | marginTop: 15, 30 | paddingVertical: 15, 31 | }, 32 | }); 33 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/prisma/seed.ts: -------------------------------------------------------------------------------- 1 | import { exit } from 'node:process'; 2 | 3 | import { PrismaClient } from '@prisma/client'; 4 | import dotenv from 'dotenv'; 5 | 6 | dotenv.config({ path: '.env.local' }); 7 | 8 | const prisma = new PrismaClient(); 9 | 10 | const prettyPrint = (object: any) => 11 | console.log(JSON.stringify(object, undefined, 2)); 12 | 13 | async function seed() { 14 | const user = await prisma.userProfile.create({ 15 | data: { 16 | email: 'jan@reactsquad.io', 17 | name: 'Jan Hesters', 18 | acceptedTermsAndConditions: true, 19 | }, 20 | }); 21 | 22 | console.log('========= 🌱 result of seed: ========='); 23 | prettyPrint({ user }); 24 | } 25 | 26 | seed() 27 | .then(async () => { 28 | await prisma.$disconnect(); 29 | }) 30 | // eslint-disable-next-line unicorn/prefer-top-level-await 31 | .catch(async error => { 32 | console.error(error); 33 | await prisma.$disconnect(); 34 | exit(1); 35 | }); 36 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/redux/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import createSagaMiddleware from 'redux-saga'; 3 | 4 | import { middleware as postsMiddleware } from '@/features/posts/posts-api'; 5 | 6 | import { rootReducer } from './root-reducer'; 7 | import { rootSaga } from './root-saga'; 8 | 9 | export const makeStore = () => { 10 | const sagaMiddleware = createSagaMiddleware(); 11 | const store = configureStore({ 12 | reducer: rootReducer, 13 | middleware: getDefaultMiddleware => 14 | getDefaultMiddleware().concat(sagaMiddleware, postsMiddleware), 15 | }); 16 | sagaMiddleware.run(rootSaga); 17 | return store; 18 | }; 19 | 20 | // Infer the type of makeStore's store. 21 | export type AppStore = ReturnType; 22 | 23 | // Infer the `RootState` and `AppDispatch` types from the store itself. 24 | export type RootState = ReturnType; 25 | export type AppDispatch = AppStore['dispatch']; 26 | -------------------------------------------------------------------------------- /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-08-20-redux-saga/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "plugin:unicorn/recommended", 5 | "plugin:import/recommended", 6 | "plugin:playwright/recommended", 7 | "plugin:prettier/recommended" 8 | ], 9 | "plugins": ["simple-import-sort"], 10 | "rules": { 11 | "simple-import-sort/exports": "error", 12 | "simple-import-sort/imports": "error", 13 | "unicorn/no-array-callback-reference": "off", 14 | "unicorn/no-array-for-each": "off", 15 | "unicorn/no-array-reduce": "off", 16 | "unicorn/no-null": "off", 17 | "unicorn/prevent-abbreviations": [ 18 | "error", 19 | { 20 | "allowList": { 21 | "e2e": true 22 | }, 23 | "replacements": { 24 | "props": false, 25 | "ref": false, 26 | "params": false 27 | } 28 | } 29 | ] 30 | }, 31 | "overrides": [ 32 | { 33 | "files": ["*.js"], 34 | "rules": { 35 | "unicorn/prefer-module": "off" 36 | } 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/components/HelloWave.tsx: -------------------------------------------------------------------------------- 1 | import { StyleSheet } from "react-native"; 2 | import Animated, { 3 | useSharedValue, 4 | useAnimatedStyle, 5 | withTiming, 6 | withRepeat, 7 | withSequence, 8 | } from "react-native-reanimated"; 9 | 10 | import { ThemedText } from "@/components/ThemedText"; 11 | 12 | export function HelloWave() { 13 | const rotationAnimation = useSharedValue(0); 14 | 15 | rotationAnimation.value = withRepeat( 16 | withSequence( 17 | withTiming(25, { duration: 150 }), 18 | withTiming(0, { duration: 150 }), 19 | ), 20 | 4, // Run the animation 4 times 21 | ); 22 | 23 | const animatedStyle = useAnimatedStyle(() => ({ 24 | transform: [{ rotate: `${rotationAnimation.value}deg` }], 25 | })); 26 | 27 | return ( 28 | 29 | 👋 30 | 31 | ); 32 | } 33 | 34 | const styles = StyleSheet.create({ 35 | text: { 36 | fontSize: 28, 37 | lineHeight: 32, 38 | marginTop: -6, 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "plugin:unicorn/recommended", 5 | "plugin:import/recommended", 6 | "plugin:playwright/recommended", 7 | "plugin:prettier/recommended" 8 | ], 9 | "plugins": ["simple-import-sort"], 10 | "rules": { 11 | "simple-import-sort/exports": "error", 12 | "simple-import-sort/imports": "error", 13 | "unicorn/no-array-callback-reference": "off", 14 | "unicorn/no-array-for-each": "off", 15 | "unicorn/no-array-reduce": "off", 16 | "unicorn/prefer-spread": "off", 17 | "unicorn/no-null": "off", 18 | "unicorn/prevent-abbreviations": [ 19 | "error", 20 | { 21 | "allowList": { 22 | "e2e": true 23 | }, 24 | "replacements": { 25 | "props": false, 26 | "ref": false, 27 | "params": false 28 | } 29 | } 30 | ] 31 | }, 32 | "overrides": [ 33 | { 34 | "files": ["*.js"], 35 | "rules": { 36 | "unicorn/prefer-module": "off" 37 | } 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/src/app/page.js: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { increment, selectCount } from './example-reducer'; 4 | import { useAppDispatch, useAppSelector } from './hooks'; 5 | import UserProfileContainer from './user-profile-container'; 6 | 7 | export default function Home() { 8 | const count = useAppSelector(selectCount); 9 | const dispatch = useAppDispatch(); 10 | 11 | return ( 12 |
13 |

Redux Basics

14 | 15 |
16 |

Count: {count}

17 | 18 | 26 |
27 | 28 | 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/src/redux/effects.js: -------------------------------------------------------------------------------- 1 | export const take = actionType => ({ 2 | '@@redux-saga/IO': true, 3 | type: 'TAKE', 4 | payload: { actionType }, 5 | }); 6 | 7 | export const put = action => ({ 8 | '@@redux-saga/IO': true, 9 | type: 'PUT', 10 | payload: { action }, 11 | }); 12 | 13 | export const select = (selector, ...arguments_) => ({ 14 | '@@redux-saga/IO': true, 15 | type: 'SELECT', 16 | payload: { 17 | selector, 18 | args: arguments_, 19 | }, 20 | }); 21 | 22 | export const call = (functionOrContextAndFunction, ...arguments_) => 23 | Array.isArray(functionOrContextAndFunction) 24 | ? { 25 | '@@redux-saga/IO': true, 26 | type: 'CALL', 27 | payload: { 28 | fn: functionOrContextAndFunction[1], 29 | args: arguments_, 30 | context: functionOrContextAndFunction[0], 31 | }, 32 | } 33 | : { 34 | '@@redux-saga/IO': true, 35 | type: 'CALL', 36 | payload: { 37 | fn: functionOrContextAndFunction, 38 | args: arguments_, 39 | context: null, 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /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-08-09-redux-basics/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "next": "14.2.5", 4 | "ramda": "0.30.1", 5 | "react": "^18", 6 | "react-dom": "^18", 7 | "react-redux": "9.1.2", 8 | "redux": "5.0.1", 9 | "redux-logger": "3.0.6", 10 | "redux-thunk": "3.1.0" 11 | }, 12 | "devDependencies": { 13 | "@eslint/compat": "1.1.1", 14 | "eslint": "8.57.0", 15 | "eslint-config-next": "14.2.5", 16 | "eslint-config-prettier": "9.1.0", 17 | "eslint-plugin-import": "2.29.1", 18 | "eslint-plugin-playwright": "1.6.2", 19 | "eslint-plugin-prettier": "5.2.1", 20 | "eslint-plugin-simple-import-sort": "12.1.1", 21 | "eslint-plugin-unicorn": "55.0.0", 22 | "postcss": "^8", 23 | "prettier": "3.3.3", 24 | "prettier-plugin-tailwindcss": "0.6.5", 25 | "tailwindcss": "^3.4.1" 26 | }, 27 | "name": "2024-08-09-redux-basics", 28 | "private": true, 29 | "scripts": { 30 | "build": "next build", 31 | "dev": "next dev", 32 | "format": "prettier --write .", 33 | "lint": "npx eslint ./src", 34 | "lint:fix": "npm run lint -- --fix", 35 | "start": "next start" 36 | }, 37 | "version": "0.1.0" 38 | } 39 | -------------------------------------------------------------------------------- /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-09-01-redux-for-production/src/features/user-authentication/user-authentication-reducer.ts: -------------------------------------------------------------------------------- 1 | import type { PayloadAction } from '@reduxjs/toolkit'; 2 | import { createSlice } from '@reduxjs/toolkit'; 3 | import { prop } from 'ramda'; 4 | 5 | import { clear } from '@/redux/clear'; 6 | 7 | const initialState = { isAuthenticating: false, token: '' }; 8 | 9 | export const { 10 | actions: { login, loginSucceeded, stopAuthenticating }, 11 | name, 12 | reducer, 13 | selectors: { selectIsAuthenticating, selectAuthenticationToken }, 14 | } = createSlice({ 15 | name: 'userAuthentication', 16 | initialState, 17 | reducers: { 18 | login: ( 19 | state, 20 | { payload }: PayloadAction<{ email: string; password: string }>, 21 | ) => { 22 | state.isAuthenticating = true; 23 | }, 24 | loginSucceeded: (state, { payload }: PayloadAction<{ token: string }>) => { 25 | state.token = payload.token; 26 | }, 27 | stopAuthenticating: state => { 28 | state.isAuthenticating = false; 29 | }, 30 | }, 31 | extraReducers: builder => { 32 | builder.addCase(clear, () => initialState); 33 | }, 34 | selectors: { 35 | selectIsAuthenticating: prop<'isAuthenticating'>('isAuthenticating'), 36 | selectAuthenticationToken: prop<'token'>('token'), 37 | }, 38 | }); 39 | -------------------------------------------------------------------------------- /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-07-16-set-up-next-js-production/src/features/internationalization/localization-middleware.ts: -------------------------------------------------------------------------------- 1 | import { match } from '@formatjs/intl-localematcher'; 2 | import Negotiator from 'negotiator'; 3 | import { type NextRequest, NextResponse } from 'next/server'; 4 | 5 | import { i18n } from './i18n-config'; 6 | 7 | function getLocale(request: NextRequest) { 8 | const headers = { 9 | 'accept-language': request.headers.get('accept-language') ?? '', 10 | }; 11 | const languages = new Negotiator({ headers }).languages(); 12 | return match(languages, i18n.locales, i18n.defaultLocale); 13 | } 14 | 15 | export function localizationMiddleware(request: NextRequest) { 16 | const { pathname } = request.nextUrl; 17 | const pathnameHasLocale = i18n.locales.some( 18 | locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`, 19 | ); 20 | 21 | // // `/_next/` and `/api/` are ignored by the watcher, but we need to 22 | // ignore files in `public` manually. 23 | if ( 24 | pathnameHasLocale || 25 | [ 26 | '/manifest.json', 27 | '/favicon.ico', 28 | // Your other files in `public`. 29 | ].includes(pathname) 30 | ) { 31 | return; 32 | } 33 | 34 | const locale = getLocale(request); 35 | request.nextUrl.pathname = `/${locale}${pathname}`; 36 | return NextResponse.redirect(request.nextUrl); 37 | } 38 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/user-authentication/user-authentication-saga.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit'; 2 | import { call, put, takeLeading } from 'redux-saga/effects'; 3 | 4 | import { clear } from '@/redux/clear'; 5 | 6 | import { handleFetchCurrentUsersProfile } from '../user-profiles/user-profiles-saga'; 7 | import { loginRequest, logoutRequest } from './user-authentication-api'; 8 | import { 9 | login, 10 | loginSucceeded, 11 | name, 12 | stopAuthenticating, 13 | } from './user-authentication-reducer'; 14 | 15 | function* handleLogin({ 16 | payload: { email, password }, 17 | }: ReturnType) { 18 | try { 19 | const token: Awaited> = yield call( 20 | loginRequest, 21 | email, 22 | password, 23 | ); 24 | yield put(loginSucceeded({ token })); 25 | yield call(handleFetchCurrentUsersProfile); 26 | } finally { 27 | yield put(stopAuthenticating()); 28 | } 29 | } 30 | 31 | export function* watchLogin() { 32 | yield takeLeading(login.type, handleLogin); 33 | } 34 | 35 | export const logout = createAction(`${name}/logout`); 36 | 37 | export function* handleLogout() { 38 | yield put(clear()); 39 | yield call(logoutRequest); 40 | } 41 | 42 | export function* watchLogout() { 43 | yield takeLeading(logout.type, handleLogout); 44 | } 45 | -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/2024-08-17-memoization/example-2.js: -------------------------------------------------------------------------------- 1 | function memoize(fn) { 2 | const cache = new Map(); 3 | 4 | function memoizedFunction(...args) { 5 | const key = JSON.stringify(args); 6 | 7 | if (cache.has(key)) { 8 | return cache.get(key); 9 | } 10 | 11 | const result = fn.apply(this, args); 12 | cache.set(key, result); 13 | 14 | return result; 15 | } 16 | 17 | Object.defineProperty(memoizedFunction, 'name', { 18 | value: `memoized_${fn.name}`, 19 | configurable: true 20 | }); 21 | 22 | return memoizedFunction; 23 | } 24 | 25 | function fibonacci(n) { 26 | if (n <= 1) { 27 | return n; 28 | } 29 | 30 | return fibonacci(n - 1) + fibonacci(n - 2); 31 | } 32 | 33 | const memoizedFibonacci = memoize(fibonacci); 34 | 35 | function measurePerformance(func, arg) { 36 | const startTime = process.hrtime.bigint(); 37 | const result = func(arg); 38 | const endTime = process.hrtime.bigint(); 39 | // Convert nanoseconds to milliseconds. 40 | const duration = (endTime - startTime) / BigInt(1000000); 41 | console.log(`${func.name}(${arg}) = ${result}, Time: ${duration}ms`); 42 | } 43 | 44 | const n = 42; 45 | 46 | console.log("Starting performance measurement:"); 47 | measurePerformance(fibonacci, n); 48 | measurePerformance(memoizedFibonacci, n); 49 | // Second call to show caching effect. 50 | measurePerformance(memoizedFibonacci, n); 51 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/posts/posts-api.ts: -------------------------------------------------------------------------------- 1 | import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; 2 | 3 | import { Post } from './posts-types'; 4 | 5 | export const { 6 | middleware, 7 | reducer, 8 | reducerPath, 9 | useAddPostMutation, 10 | useGetPostsQuery, 11 | util, 12 | } = createApi({ 13 | reducerPath: 'postsApi', 14 | baseQuery: fetchBaseQuery({ 15 | baseUrl: 'https://jsonplaceholder.typicode.com/', 16 | // Can have more options, such as prepareHeaders, credentials etc. 17 | }), 18 | tagTypes: ['Posts'], 19 | endpoints: builder => ({ 20 | getPosts: builder.query({ 21 | query: () => 'posts', 22 | providesTags: ['Posts'], 23 | }), 24 | addPost: builder.mutation>({ 25 | query: body => ({ 26 | url: 'posts', 27 | method: 'POST', 28 | body, 29 | }), 30 | invalidatesTags: ['Posts'], 31 | async onQueryStarted(argument, { dispatch, queryFulfilled }) { 32 | const patchResult = dispatch( 33 | util.updateQueryData('getPosts', undefined, draft => { 34 | draft.unshift({ id: Date.now(), ...argument } as Post); 35 | }), 36 | ); 37 | try { 38 | await queryFulfilled; 39 | } catch { 40 | patchResult.undo(); 41 | } 42 | }, 43 | }), 44 | }), 45 | }); 46 | -------------------------------------------------------------------------------- /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-08-09-redux-basics/src/app/page-2.js: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { connect } from 'react-redux'; 4 | 5 | import { increment, selectCount } from './example-reducer'; 6 | 7 | function Home({ count, increment }) { 8 | return ( 9 |
10 |

Redux Basics

11 | 12 |
13 |

Count: {count}

14 | 15 | 23 |
24 |
25 | ); 26 | } 27 | 28 | const mapStateToProps = state => ({ count: selectCount(state) }); 29 | 30 | const mapDispatchToProps = { increment }; 31 | 32 | export default connect(mapStateToProps, mapDispatchToProps)(Home); 33 | 34 | const mergeProps = (stateProps, dispatchProps, ownProps) => ({ 35 | ...stateProps, // from mapStateToProps 36 | ...dispatchProps, // from mapDispatchToProps 37 | ...ownProps, // passed in to the component wrapped by connect from its parent 38 | }); 39 | 40 | connect(mapStateToProps, mapDispatchToProps, mergeProps); 41 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/hocs/redirect.tsx: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'next/navigation'; 2 | import { curry } from 'ramda'; 3 | import { PropsWithChildren, useEffect } from 'react'; 4 | import type { ConnectedProps } from 'react-redux'; 5 | import { connect } from 'react-redux'; 6 | 7 | import { RootState } from '@/redux/store'; 8 | 9 | function redirect(predicate: (state: RootState) => boolean, path: string) { 10 | const isExternal = path.startsWith('http'); 11 | 12 | const mapStateToProps = ( 13 | state: RootState, 14 | ): { shouldRedirect: boolean } & Record => ({ 15 | shouldRedirect: predicate(state), 16 | }); 17 | 18 | const connector = connect(mapStateToProps); 19 | 20 | return function ( 21 | Component: React.ComponentType>, 22 | ) { 23 | function Redirect({ 24 | shouldRedirect, 25 | ...props 26 | }: PropsWithChildren>): JSX.Element { 27 | const router = useRouter(); 28 | 29 | useEffect(() => { 30 | if (shouldRedirect) { 31 | if (isExternal && window) { 32 | window.location.assign(path); 33 | } else { 34 | router.push(path); 35 | } 36 | } 37 | }, [shouldRedirect, router]); 38 | 39 | return ; 40 | } 41 | 42 | return connector(Redirect); 43 | }; 44 | } 45 | 46 | export default curry(redirect); 47 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/dashboard/dashboard-page-container.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { useEffect } from 'react'; 3 | import type { ConnectedProps } from 'react-redux'; 4 | import { connect } from 'react-redux'; 5 | 6 | import { RootState } from '@/redux/store'; 7 | 8 | import { logout } from '../user-authentication/user-authentication-saga'; 9 | import { 10 | selectCurrentUsersName, 11 | selectUserProfilesAreLoading, 12 | selectUsersList, 13 | } from '../user-profiles/user-profiles-reducer'; 14 | import { fetchUserProfiles } from '../user-profiles/user-profiles-saga'; 15 | import { DashBoardPageComponent } from './dashboard-page-component'; 16 | 17 | const mapStateToProps = (state: RootState) => ({ 18 | currentUsersName: selectCurrentUsersName(state), 19 | isLoading: selectUserProfilesAreLoading(state), 20 | users: selectUsersList(state), 21 | }); 22 | 23 | const mapDispatchToProps = { 24 | fetchUserProfiles, 25 | onLogout: logout, 26 | }; 27 | 28 | const connector = connect(mapStateToProps, mapDispatchToProps); 29 | 30 | export type DashboardPagePropsFromRedux = ConnectedProps; 31 | 32 | function DashboardContainer({ 33 | fetchUserProfiles, 34 | ...props 35 | }: DashboardPagePropsFromRedux) { 36 | useEffect(() => { 37 | fetchUserProfiles(); 38 | }, [fetchUserProfiles]); 39 | 40 | return ; 41 | } 42 | 43 | export default connector(DashboardContainer); 44 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@radix-ui/react-icons": "1.3.0", 4 | "@radix-ui/react-label": "2.1.0", 5 | "@radix-ui/react-slot": "1.1.0", 6 | "class-variance-authority": "0.7.0", 7 | "clsx": "2.1.1", 8 | "next": "14.2.6", 9 | "ramda": "0.30.1", 10 | "react": "^18", 11 | "react-dom": "^18", 12 | "react-redux": "9.1.2", 13 | "redux": "5.0.1", 14 | "redux-saga": "1.3.0", 15 | "tailwind-merge": "2.5.2", 16 | "tailwindcss-animate": "1.0.7" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "22.5.1", 20 | "@types/react": "18.3.4", 21 | "@typescript-eslint/parser": "8.2.0", 22 | "eslint": "^8", 23 | "eslint-config-next": "14.2.6", 24 | "eslint-config-prettier": "9.1.0", 25 | "eslint-plugin-import": "2.29.1", 26 | "eslint-plugin-playwright": "1.6.2", 27 | "eslint-plugin-prettier": "5.2.1", 28 | "eslint-plugin-simple-import-sort": "12.1.1", 29 | "eslint-plugin-unicorn": "55.0.0", 30 | "postcss": "^8", 31 | "prettier": "3.3.3", 32 | "prettier-plugin-tailwindcss": "0.6.6", 33 | "tailwindcss": "^3.4.1" 34 | }, 35 | "name": "2024-08-20-redux-saga", 36 | "private": true, 37 | "scripts": { 38 | "build": "next build", 39 | "dev": "next dev", 40 | "format": "prettier --write .", 41 | "lint": "next lint", 42 | "lint:fix": "next lint --fix", 43 | "start": "next start" 44 | }, 45 | "version": "0.1.0" 46 | } 47 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/components/Collapsible.tsx: -------------------------------------------------------------------------------- 1 | import Ionicons from "@expo/vector-icons/Ionicons"; 2 | import { PropsWithChildren, useState } from "react"; 3 | import { StyleSheet, TouchableOpacity, useColorScheme } from "react-native"; 4 | 5 | import { ThemedText } from "@/components/ThemedText"; 6 | import { ThemedView } from "@/components/ThemedView"; 7 | import { Colors } from "@/constants/Colors"; 8 | 9 | export function Collapsible({ 10 | children, 11 | title, 12 | }: PropsWithChildren & { title: string }) { 13 | const [isOpen, setIsOpen] = useState(false); 14 | const theme = useColorScheme() ?? "light"; 15 | 16 | return ( 17 | 18 | setIsOpen((value) => !value)} 21 | activeOpacity={0.8} 22 | > 23 | 28 | {title} 29 | 30 | {isOpen && {children}} 31 | 32 | ); 33 | } 34 | 35 | const styles = StyleSheet.create({ 36 | heading: { 37 | flexDirection: "row", 38 | alignItems: "center", 39 | gap: 6, 40 | }, 41 | content: { 42 | marginTop: 6, 43 | marginLeft: 24, 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/user-profiles/user-profiles-saga.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from '@reduxjs/toolkit'; 2 | import { call, put, select, takeLeading } from 'redux-saga/effects'; 3 | 4 | import { selectAuthenticationToken } from '../user-authentication/user-authentication-reducer'; 5 | import { 6 | currentUserProfileFetched, 7 | name, 8 | usersListFetched, 9 | } from '../user-profiles/user-profiles-reducer'; 10 | import { getCurrentUserRequest, getUsersRequest } from './user-profiles-api'; 11 | 12 | export function* handleFetchCurrentUsersProfile() { 13 | const token: ReturnType = yield select( 14 | selectAuthenticationToken, 15 | ); 16 | const user: Awaited> = yield call( 17 | getCurrentUserRequest, 18 | token, 19 | ); 20 | 21 | if (user) { 22 | yield put(currentUserProfileFetched(user)); 23 | } 24 | } 25 | 26 | function* handleFetchUserProfiles() { 27 | const token: ReturnType = yield select( 28 | selectAuthenticationToken, 29 | ); 30 | const users: Awaited> = yield call( 31 | getUsersRequest, 32 | token, 33 | ); 34 | 35 | yield put(usersListFetched(users)); 36 | } 37 | 38 | export const fetchUserProfiles = createAction(`${name}/fetchUserProfiles`); 39 | 40 | export function* watchFetchUserProfiles() { 41 | yield takeLeading(fetchUserProfiles.type, handleFetchUserProfiles); 42 | } 43 | -------------------------------------------------------------------------------- /examples/2024-08-20-redux-saga/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/dashboard/dashboard-page-component.tsx: -------------------------------------------------------------------------------- 1 | import { Spinner } from '@/components/spinner'; 2 | import { Button } from '@/components/ui/button'; 3 | import { 4 | Card, 5 | CardContent, 6 | CardDescription, 7 | CardFooter, 8 | CardHeader, 9 | CardTitle, 10 | } from '@/components/ui/card'; 11 | 12 | import { DashboardPagePropsFromRedux } from './dashboard-page-container'; 13 | 14 | export function DashBoardPageComponent({ 15 | currentUsersName, 16 | isLoading, 17 | users, 18 | onLogout, 19 | }: Omit) { 20 | return ( 21 |
22 | 23 | 24 | Dashboard 25 | Welcome back, {currentUsersName}! 26 | 27 | 28 | 29 | {isLoading ? ( 30 | 31 | ) : ( 32 |
    33 | {users.map(user => ( 34 |
  • {user.name}
  • 35 | ))} 36 |
37 | )} 38 |
39 | 40 | 41 | 50 | 51 |
52 |
53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /examples/2024-06-24-complex-navigation-expo/components/ThemedText.tsx: -------------------------------------------------------------------------------- 1 | import { Text, type TextProps, StyleSheet } from "react-native"; 2 | 3 | import { useThemeColor } from "@/hooks/useThemeColor"; 4 | 5 | export type ThemedTextProps = TextProps & { 6 | lightColor?: string; 7 | darkColor?: string; 8 | type?: "default" | "title" | "defaultSemiBold" | "subtitle" | "link"; 9 | }; 10 | 11 | export function ThemedText({ 12 | style, 13 | lightColor, 14 | darkColor, 15 | type = "default", 16 | ...rest 17 | }: ThemedTextProps) { 18 | const color = useThemeColor({ light: lightColor, dark: darkColor }, "text"); 19 | 20 | return ( 21 | 33 | ); 34 | } 35 | 36 | const styles = StyleSheet.create({ 37 | default: { 38 | fontSize: 16, 39 | lineHeight: 24, 40 | }, 41 | defaultSemiBold: { 42 | fontSize: 16, 43 | lineHeight: 24, 44 | fontWeight: "600", 45 | }, 46 | title: { 47 | fontSize: 32, 48 | fontWeight: "bold", 49 | lineHeight: 32, 50 | }, 51 | subtitle: { 52 | fontSize: 20, 53 | fontWeight: "bold", 54 | }, 55 | link: { 56 | lineHeight: 30, 57 | fontSize: 16, 58 | color: "#0a7ea4", 59 | }, 60 | }); 61 | -------------------------------------------------------------------------------- /examples/2024-08-17-memoization/example-3.js: -------------------------------------------------------------------------------- 1 | function memoize(fn) { 2 | const cache = new Map(); 3 | 4 | function memoizedFunction(...args) { 5 | const key = JSON.stringify(args); 6 | 7 | if (cache.has(key)) { 8 | return cache.get(key); 9 | } 10 | 11 | const result = fn.apply(this, args); 12 | cache.set(key, result); 13 | 14 | return result; 15 | } 16 | 17 | memoizedFunction.clear = function clear() { 18 | cache.clear(); 19 | }; 20 | 21 | Object.defineProperty(memoizedFunction, 'name', { 22 | value: `memoized_${fn.name}`, 23 | configurable: true 24 | }); 25 | 26 | return memoizedFunction; 27 | } 28 | 29 | function fibonacci(n) { 30 | if (n <= 1) { 31 | return n; 32 | } 33 | 34 | return fibonacci(n - 1) + fibonacci(n - 2); 35 | } 36 | 37 | const memoizedFibonacci = memoize(fibonacci); 38 | 39 | function measurePerformance(func, arg) { 40 | const startTime = process.hrtime.bigint(); 41 | const result = func(arg); 42 | const endTime = process.hrtime.bigint(); 43 | // Convert nanoseconds to milliseconds. 44 | const duration = (endTime - startTime) / BigInt(1000000); 45 | console.log(`${func.name}(${arg}) = ${result}, Time: ${duration}ms`); 46 | } 47 | 48 | const n = 42; 49 | 50 | // Measure memoized Fibonacci. 51 | measurePerformance(memoizedFibonacci, n); 52 | 53 | // Measure memoized Fibonacci second call. 54 | measurePerformance(memoizedFibonacci, n); 55 | 56 | // Clear cache and measure again. 57 | console.log('Clearing cache and measuring again:'); 58 | memoizedFibonacci.clear(); 59 | measurePerformance(memoizedFibonacci, n); 60 | -------------------------------------------------------------------------------- /examples/2024-08-09-redux-basics/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with 2 | [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 3 | 4 | ## Getting Started 5 | 6 | First, run the development server: 7 | 8 | ```bash 9 | npm run dev 10 | # or 11 | yarn dev 12 | # or 13 | pnpm dev 14 | # or 15 | bun dev 16 | ``` 17 | 18 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the 19 | result. 20 | 21 | You can start editing the page by modifying `app/page.js`. The page auto-updates 22 | as you edit the file. 23 | 24 | This project uses 25 | [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to 26 | automatically optimize and load Inter, a custom Google Font. 27 | 28 | ## Learn More 29 | 30 | To learn more about Next.js, take a look at the following resources: 31 | 32 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js 33 | features and API. 34 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 35 | 36 | You can check out 37 | [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your 38 | feedback and contributions are welcome! 39 | 40 | ## Deploy on Vercel 41 | 42 | The easiest way to deploy your Next.js app is to use the 43 | [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) 44 | from the creators of Next.js. 45 | 46 | Check out our 47 | [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more 48 | details. 49 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/src/features/user-profile/user-profile-model.ts: -------------------------------------------------------------------------------- 1 | import { UserProfile } from '@prisma/client'; 2 | 3 | import prisma from '@/lib/prisma'; 4 | 5 | export type PartialUserProfileParameters = Pick< 6 | Parameters[0]['data'], 7 | 'acceptedTermsAndConditions' | 'email' | 'id' | 'name' 8 | >; 9 | 10 | // CREATE 11 | 12 | /** 13 | * Saves a new user profile to the database. 14 | * 15 | * @param user profile - Parameters of the user profile that should be created. 16 | * @returns The newly created user profile. 17 | */ 18 | export async function saveUserProfileToDatabase( 19 | userProfile: PartialUserProfileParameters, 20 | ) { 21 | return prisma.userProfile.create({ data: userProfile }); 22 | } 23 | 24 | // READ 25 | 26 | /** 27 | * Returns the first user profile that exists in the database with the given 28 | * email. 29 | * 30 | * @param email - The email of the user profile to retrieve. 31 | * @returns The user profile with the given email, or null if it wasn't found. 32 | */ 33 | export async function retrieveUserProfileFromDatabaseByEmail( 34 | email: UserProfile['email'], 35 | ) { 36 | return await prisma.userProfile.findUnique({ where: { email } }); 37 | } 38 | 39 | // DELETE 40 | 41 | /** 42 | * Removes a user profile from the database. 43 | * 44 | * @param id - The id of the user profile you want to delete. 45 | * @returns The user profile that was deleted. 46 | */ 47 | export async function deleteUserProfileFromDatabaseById(id: UserProfile['id']) { 48 | return prisma.userProfile.delete({ where: { id } }); 49 | } 50 | -------------------------------------------------------------------------------- /examples/2024-07-16-set-up-next-js-production/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with 2 | [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 3 | 4 | ## Getting Started 5 | 6 | First, run the development server: 7 | 8 | ```bash 9 | npm run dev 10 | # or 11 | yarn dev 12 | # or 13 | pnpm dev 14 | # or 15 | bun dev 16 | ``` 17 | 18 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the 19 | result. 20 | 21 | You can start editing the page by modifying `app/page.tsx`. The page 22 | auto-updates as you edit the file. 23 | 24 | This project uses 25 | [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to 26 | automatically optimize and load Inter, a custom Google Font. 27 | 28 | ## Learn More 29 | 30 | To learn more about Next.js, take a look at the following resources: 31 | 32 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js 33 | features and API. 34 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 35 | 36 | You can check out 37 | [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your 38 | feedback and contributions are welcome! 39 | 40 | ## Deploy on Vercel 41 | 42 | The easiest way to deploy your Next.js app is to use the 43 | [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) 44 | from the creators of Next.js. 45 | 46 | Check out our 47 | [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more 48 | details. 49 | -------------------------------------------------------------------------------- /examples/2024-09-01-redux-for-production/src/features/posts/posts-list-component.tsx: -------------------------------------------------------------------------------- 1 | import { Spinner } from '@/components/spinner'; 2 | import { Button } from '@/components/ui/button'; 3 | import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; 4 | 5 | import { useGetPostsQuery } from './posts-api'; 6 | 7 | export function PostsListComponent() { 8 | const { data: posts, error, isLoading, refetch } = useGetPostsQuery(); 9 | 10 | if (isLoading) { 11 | return ; 12 | } 13 | 14 | if (error) { 15 | // Determine the error message based on the type of error 16 | let errorMessage = 'An unknown error occurred'; 17 | if ('status' in error) { 18 | errorMessage = `An error occurred: ${error.status} ${error.data ? JSON.stringify(error.data) : 'Unknown error'}`; 19 | } else if ('message' in error) { 20 | errorMessage = `An error occurred: ${error.message}`; 21 | } 22 | 23 | return ( 24 |
25 | {errorMessage} 26 | 27 |
28 | ); 29 | } 30 | 31 | return ( 32 | 33 | 34 | Latest Posts 35 | 36 | 37 | 38 |
    39 | {posts?.slice(0, 5).map(post => ( 40 |
  • 41 | {post.title} 42 |

    {post.body}

    43 |
  • 44 | ))} 45 |
46 |
47 |
48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /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 |