├── .eslintrc.json ├── .gitignore ├── @types ├── lodash │ └── index.d.ts └── plausible │ └── index.d.ts ├── README.md ├── app.d.ts ├── components └── RandomFox.tsx ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.tsx ├── globals.css └── index.tsx ├── postcss.config.js ├── public ├── favicon.ico └── vercel.svg ├── tailwind.config.js └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "rules": { 4 | "@next/next/no-img-element": "off", 5 | "jsx-a11y/alt-text": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.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 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | -------------------------------------------------------------------------------- /@types/lodash/index.d.ts: -------------------------------------------------------------------------------- 1 | // Utilizado como ejemplo para el curso de Platzi 2 | // Lodash cuenta con sus propios tipos comunitarios 3 | // Se pueden instalar con `npm install @types/lodash --save-dev` 4 | declare module "lodash" { 5 | export function random( 6 | lower?: number, 7 | upper?: number, 8 | floating?: boolean 9 | ): number; 10 | } 11 | -------------------------------------------------------------------------------- /@types/plausible/index.d.ts: -------------------------------------------------------------------------------- 1 | type Options = { 2 | /** 3 | * called once the event is logged successfully. 4 | */ 5 | callback?: () => void; 6 | /** 7 | * custom properties for the event 8 | */ 9 | props?: Record; 10 | }; 11 | 12 | interface Window { 13 | /** 14 | * Track Custom event goals using Plausible 15 | * 16 | * ⚠️ This function is not really implemented in our codebase. 17 | * This is just an example of how to use TypeScript types. 18 | * 19 | * @see https://plausible.io/docs/custom-event-goals 20 | * 21 | * @example 22 | * registerForm.addEventListener('submit', function(e) { 23 | * e.preventDefault(); 24 | * setTimeout(submitForm, 1000); 25 | * var formSubmitted = false; 26 | * 27 | * function submitForm() { 28 | * if (!formSubmitted) { 29 | * formSubmitted = true; 30 | * registerForm.submit(); 31 | * } 32 | * } 33 | * 34 | * plausible('Signup', {callback: submitForm}); 35 | * }) 36 | */ 37 | plausible: (event: string, options?: Options) => void; 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Curso de Platzi de React con TypeScript 4 | 5 | 6 | Curso de Next.js con GraphQL 7 | 8 |

9 |

10 | Curso de Platzi de React con TypeScript 11 |

12 | 13 | Este repositorio contiene el proyecto realizado durante el Curso de Platzi de React con TypeScript, dictado por [@jonalvarezz](https://twitter.com/jonalvarezz) para [Platzi](https://platzi.com). 14 | 15 | ## 🚗 Dependencias 16 | 17 | 1. Node.js 18 | 19 | Recomendamos instalarlo a través de [Fast Node Manager (fnm)](https://github.com/Schniz/fnm). Versiones soportadas: 14+. 20 | 21 | ## 🤖 Guía Para Desarrollo Local 22 | 23 | 1. Clona el repositorio 24 | 25 | ```sh 26 | git clone git@github.com:jonalvarezz/platzi-react-typescript.git 27 | ``` 28 | 29 | 1. Instala dependencias 30 | 31 | ```sh 32 | # Instala desde la raíz del proyecto 33 | cd platzi-react-typescript 34 | npm install 35 | ``` 36 | 37 | 1. Inicia la aplicación 38 | 39 | ```sh 40 | # Desde la raíz del proyecto: 41 | npm run dev 42 | ``` 43 | 44 | 🚀 La app estará disponible en http://localhost:3000 45 | 46 | ## 🐞 Encontraste un error o mejora? 47 | 48 | Ayuda a otros estudiantes con eso que acabas de descubrir que haría este curso y respositorio mucho mejor. 49 | 50 | - En [Issues](https://github.com/jonalvarezz/react-typescript/issues/new) puedes reportar errores, agregar sugerencias y comentarios. 51 | - Por su parte, los [Pull Request](https://github.com/jonalvarezz/react-typescript/pulls) siempre estarán abiertos para recibir mejoras puntuales. 52 | 53 | Happy hacking! 54 | -------------------------------------------------------------------------------- /app.d.ts: -------------------------------------------------------------------------------- 1 | type IFoxImageItem = { 2 | id: string; 3 | url: string; 4 | }; 5 | -------------------------------------------------------------------------------- /components/RandomFox.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect, useState } from "react"; 2 | import type { ImgHTMLAttributes } from "react"; 3 | 4 | type LazyImageProps = { 5 | src: string; 6 | onLazyLoad?: (img: HTMLImageElement) => void; 7 | }; 8 | 9 | type Props = ImgHTMLAttributes & LazyImageProps; 10 | 11 | export function LazyImage({ 12 | src, 13 | onLazyLoad, 14 | ...imgProps 15 | }: Props): JSX.Element { 16 | const node = useRef(null); 17 | const [isLazyLoaded, setIsLazyLoaded] = useState(false); 18 | const [currentSrc, setCurrentSrc] = useState( 19 | "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIwIiBoZWlnaHQ9IjMyMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2ZXJzaW9uPSIxLjEiLz4=" 20 | ); 21 | 22 | useEffect(() => { 23 | if (isLazyLoaded) { 24 | return; 25 | } 26 | 27 | const observer = new IntersectionObserver((entries) => { 28 | entries.forEach((entry) => { 29 | if (!entry.isIntersecting || !node.current) { 30 | return; 31 | } 32 | 33 | setCurrentSrc(src); 34 | observer.disconnect(); 35 | setIsLazyLoaded(true); 36 | 37 | if (typeof onLazyLoad === "function") { 38 | onLazyLoad(node.current); 39 | } 40 | 41 | // Ejemplo de extension de Window con Plausible 42 | // window.plausible("lazyload", { props: { src } }); 43 | }); 44 | }); 45 | 46 | if (node.current) { 47 | observer.observe(node.current); 48 | } 49 | 50 | return () => { 51 | observer.disconnect(); 52 | }; 53 | }, [src, onLazyLoad, isLazyLoaded]); 54 | 55 | return ; 56 | } 57 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | } 6 | 7 | module.exports = nextConfig 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "platzi-react-typescript", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "lodash": "^4.17.21", 13 | "next": "12.2.3", 14 | "react": "18.2.0", 15 | "react-dom": "18.2.0" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "18.6.2", 19 | "@types/react": "18.0.15", 20 | "@types/react-dom": "18.0.6", 21 | "autoprefixer": "^10.4.8", 22 | "eslint": "8.20.0", 23 | "eslint-config-next": "12.2.3", 24 | "postcss": "^8.4.16", 25 | "tailwindcss": "^3.1.8", 26 | "typescript": "4.7.4" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import "./globals.css"; 2 | import type { AppProps } from "next/app"; 3 | 4 | function MyApp({ Component, pageProps }: AppProps) { 5 | return ; 6 | } 7 | 8 | export default MyApp; 9 | -------------------------------------------------------------------------------- /pages/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import type { MouseEventHandler } from "react"; 3 | import type { NextPage } from "next"; 4 | import Head from "next/head"; 5 | 6 | // Ejemplo utilizando una librería sin tipos. 7 | // Realmente no hace falta su uso. 8 | import { random } from "lodash"; 9 | 10 | import { LazyImage } from "@/components/RandomFox"; 11 | 12 | // generate simple unique id 13 | const generateId = (): string => { 14 | return ( 15 | Math.random().toString(36).substring(2, 15) + 16 | Math.random().toString(36).substring(2, 15) 17 | ); 18 | }; 19 | 20 | // random number from 1 to 122 21 | const myRandom = () => random(1, 122); 22 | 23 | const Home: NextPage = () => { 24 | const [images, setImages] = useState>([]); 25 | 26 | const addNewFox: MouseEventHandler = () => { 27 | const id = generateId(); 28 | const url = `https://randomfox.ca/images/${myRandom()}.jpg`; 29 | setImages([...images, { id, url }]); 30 | }; 31 | 32 | return ( 33 |
34 | 35 | 36 | Curso de Platzi de React con TypeScript por @jonalvarezz 🦑 37 | 38 | 42 | 46 | 47 | 48 |
49 | 50 |
51 | 57 |
58 | {images.map(({ id, url }, index) => ( 59 |
60 | { 66 | console.log("holi!"); 67 | }} 68 | onLazyLoad={(img) => { 69 | console.log(`Image #${index + 1} cargada. Nodo:`, img); 70 | }} 71 | /> 72 |
73 | ))} 74 |
75 | 76 | 87 |
88 | ); 89 | }; 90 | 91 | function PageContent() { 92 | return ( 93 |
94 |

95 | Curso React con TypeScript 96 |

97 |

98 | Componente Lazy Image 99 |

100 |
101 |

102 | Un componente genérico de React para cargar imágenes con lazy loading. 103 |

104 |

✨✨

105 |

106 | Las imágenes agregadas no se descargarán hasta que sean visibles en la 107 | pantalla 108 |

109 |

✨✨

110 |
111 |
112 | ); 113 | } 114 | 115 | export default Home; 116 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jonalvarezz/platzi-react-typescript/a16ba75c149eb4977c455e47e6eed67a00621a28/public/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./pages/**/*.{js,ts,jsx,tsx}", 5 | "./components/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | }; 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "noImplicitAny": true, 11 | "noImplicitReturns": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "node", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "preserve", 18 | "incremental": true, 19 | "baseUrl": ".", 20 | "paths": { 21 | "@/components/*": ["components/*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 25 | "exclude": ["node_modules"] 26 | } 27 | --------------------------------------------------------------------------------