├── .gitignore ├── sesion-1-conceptos-basicos └── rick-morty-app │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── components │ │ └── CharacterCard.tsx │ ├── index.css │ ├── index.tsx │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ ├── setupTests.ts │ └── types.ts │ └── tsconfig.json ├── sesion-2-usestate-useeffect └── taller-twitch │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── components │ │ ├── MessageComponent.tsx │ │ ├── MiniCodeComplexState.tsx │ │ ├── MiniCodeEffectMount.tsx │ │ ├── MiniCodeEffectUnmount.tsx │ │ ├── MiniCodeInput.tsx │ │ └── MiniCodeStateCallback.tsx │ ├── index.css │ ├── index.tsx │ ├── react-app-env.d.ts │ └── setupTests.ts │ └── tsconfig.json ├── sesion-3-hooks ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── components │ │ ├── MiniCodeInterval.tsx │ │ ├── MiniCodeTaxCalculator.tsx │ │ ├── MiniCodeUseRef.tsx │ │ ├── MiniCodeUseRefCss .tsx │ │ └── UseRefExamples.tsx │ ├── index.css │ ├── index.tsx │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ └── setupTests.ts └── tsconfig.json ├── sesion-4-hooks-usememo-usecallback ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── components │ │ ├── BasicUseCallback.tsx │ │ ├── BasicUseMemo.tsx │ │ ├── MiniCodePosts.tsx │ │ ├── MiniCodePostsAsync.tsx │ │ ├── MiniCodeTabManager.tsx │ │ ├── MiniCodeUseCallback.tsx │ │ └── MiniCodeUseMemo.tsx │ ├── index.css │ ├── index.tsx │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ └── setupTests.ts └── tsconfig.json ├── sesion-5-custom-hooks ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── components │ │ ├── MiniCodeCounter.tsx │ │ ├── MiniCodeDebounce.tsx │ │ ├── MiniCodeTabManager.tsx │ │ └── ThirdCount.tsx │ ├── hooks │ │ ├── useCount.ts │ │ └── useDebounceString.ts │ ├── index.css │ ├── index.tsx │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ └── setupTests.ts └── tsconfig.json ├── sesion-6-react-router ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.tsx │ ├── data │ │ └── heroes.ts │ ├── index.css │ ├── index.tsx │ ├── pages │ │ ├── Hero.tsx │ │ ├── Heroes.tsx │ │ └── Home.tsx │ └── react-app-env.d.ts └── tsconfig.json ├── sesion-7-context ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ ├── robots.txt │ └── schema.PNG ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── components │ │ ├── Header.tsx │ │ └── ThemeSwitcher.tsx │ ├── context │ │ └── ThemeContext.tsx │ ├── index.css │ ├── index.tsx │ └── react-app-env.d.ts └── tsconfig.json └── sesion-8-forms ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.css ├── App.tsx ├── components │ ├── ErrorMessage.tsx │ ├── HookForm.tsx │ ├── HookFormRefactored.tsx │ ├── Input.tsx │ ├── RefForm.tsx │ └── StateForm.tsx ├── index.css ├── index.tsx ├── react-app-env.d.ts └── types.ts └── tsconfig.json /.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 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/.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 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/README.md: -------------------------------------------------------------------------------- 1 | # MiniCodeLab - Taller de React desde 0 Parte 1 2 | 3 | Repositorio creado en el primer Taller de React desde Cero de MiniCodeLab 🦄. 4 | 5 | Aquí te dejamos el vídeo resubido del taller para ver cómo hemos desarrollado este código: https://youtu.be/iXZYPN3Nilc 6 | 7 | Y los artículos de la web donde podrás repasar todo lo que hemos trabajado juntos: 8 | 9 | - https://www.minicodelab.dev/feed/react-0-basicos-1 10 | - https://www.minicodelab.dev/feed/react-0-basicos-2 11 | 12 | Gracias por participar en esta comunidad ♥ 13 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taller-0-minicodelab", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.2", 7 | "@testing-library/react": "^12.1.2", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.4.0", 10 | "@types/node": "^16.11.22", 11 | "@types/react": "^17.0.39", 12 | "@types/react-dom": "^17.0.11", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "5.0.0", 16 | "typescript": "^4.5.5", 17 | "web-vitals": "^2.1.4" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test" 23 | }, 24 | "eslintConfig": { 25 | "extends": [ 26 | "react-app", 27 | "react-app/jest" 28 | ] 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.2%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-1-conceptos-basicos/rick-morty-app/public/favicon.ico -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Minicode React 0 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-1-conceptos-basicos/rick-morty-app/public/logo192.png -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-1-conceptos-basicos/rick-morty-app/public/logo512.png -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | background-color: #282c34; 3 | color: white; 4 | min-height: 100vh; 5 | padding: 1rem; 6 | text-align: center; 7 | } 8 | 9 | .character-list { 10 | align-items: flex-start; 11 | display: flex; 12 | flex-wrap: wrap; 13 | gap: 2rem; 14 | justify-content: center; 15 | list-style-type: none; 16 | margin: 0 auto; 17 | max-width: 1024px; 18 | } 19 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/src/App.tsx: -------------------------------------------------------------------------------- 1 | // Hola MiniCoder! Te dejamos aquí el reto de refactorización del código: 2 | // - Debes conseguir que el archivo App.tsx quede lo más limpio posible 3 | // - Esto lo conseguiremos sacando la request a la API con useEffect y el useState a un componente CharacterList 4 | // - Ese componente devolverá en su return la lista mapeada que contiene los CharacterCard 5 | // En la guía de la web tienes los ejemplos de esto: https://www.minicodelab.dev/feed/react-0-parte-2 6 | // Y el video del taller en Youtube para repasar: https://youtu.be/iXZYPN3Nilc 7 | import React from 'react'; 8 | import './App.css'; 9 | import CharacterCard from './components/CharacterCard'; 10 | import { ApiResponse, Character } from './types'; 11 | 12 | // Mock = informacion falsa para desarrollar 13 | // const charactersMock: Character[] = [ 14 | // { 15 | // id: 1, 16 | // name: 'Rick Sanchez', 17 | // status: 'Alive', 18 | // }, 19 | // { 20 | // id: 2, 21 | // name: 'Morty Smith', 22 | // status: 'Alive', 23 | // }, 24 | // ]; 25 | 26 | // useState & useEffect 27 | const App = () => { 28 | const [characters, setCharacters] = React.useState([]); 29 | 30 | React.useEffect(() => { 31 | // IIFE 32 | (async () => { 33 | const charactersResponse = (await fetch( 34 | 'https://rickandmortyapi.com/api/character/' 35 | ).then((res) => res.json())) as ApiResponse; 36 | 37 | console.log({ charactersResponse }); 38 | // Seteo en el state los personajes de la API cuando la request termine 39 | setCharacters(charactersResponse.results); 40 | })(); 41 | 42 | // Con el [] useEffect solo se ejecuta cuando el componente ha montado por primera vez 43 | }, []); 44 | 45 | console.log('Pintando componente App'); 46 | console.log({ characters }); 47 | 48 | return ( 49 |
50 |

MiniCodeLab Rick&Morty

51 | 52 | {/* Esto ya no lo usaremos en nuestro código final: */} 53 | {/* */} 60 | 61 |
    62 | {characters.map((character) => { 63 | return ; 64 | })} 65 |
66 |
67 | ); 68 | }; 69 | 70 | export default App; 71 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/src/components/CharacterCard.tsx: -------------------------------------------------------------------------------- 1 | import { Character } from '../types'; 2 | 3 | type Props = { 4 | character: Character; 5 | }; 6 | 7 | const CharacterCard: React.FC = ({ character }) => { 8 | return ( 9 |
  • 10 |

    {character.name}

    11 |

    Status: {character.status}

    12 | 13 | {character.name} 14 |
  • 15 | ); 16 | }; 17 | 18 | export default CharacterCard; 19 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | 15 | * { 16 | box-sizing: border-box; 17 | } 18 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/src/types.ts: -------------------------------------------------------------------------------- 1 | export type Character = { 2 | id: number; 3 | name: string; 4 | status: string; 5 | image: string; 6 | }; 7 | 8 | export type ApiResponse = { 9 | results: Character[]; 10 | }; 11 | -------------------------------------------------------------------------------- /sesion-1-conceptos-basicos/rick-morty-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/.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 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/README.md: -------------------------------------------------------------------------------- 1 | # MiniCodeLab - Taller de React desde 0 Parte 1 2 | 3 | Repositorio creado en el segundo Taller de React desde Cero de MiniCodeLab, donde hemos visto los Hooks `useState` y `useEffect` en profundidad 🚀. 4 | 5 | Aquí te dejamos el vídeo resubido del taller para ver cómo hemos desarrollado este código: https://youtu.be/5EiAGGHunOA 6 | 7 | Y el artículos de la web donde podrás repasar todo lo que hemos trabajado juntos: 8 | 9 | https://www.minicodelab.dev/feed/react-0-hooks-parte-1 10 | 11 | Gracias por participar en esta comunidad ♥ 12 | 13 | --- 14 | 15 | # Ejercicios propuestos 16 | 17 | Como te comentamos en el taller en directo (o en youtube si lo has visto en diferido), te proponemos una serie de ejercicios para que practiques y compartas el resultado con nosotros en Discord. Aquí te dejamos el documento con los enunciados 🧙‍♂️ 18 | 19 | ## Ejercicios UseState y UseEffect 20 | 21 | ¡Hey 👋 MiniCoder! ha llegado el momento de ponernos a prueba, después de todo lo que hemos aprendido estaría chachi hacer algunos ejercicios para comprobar si han calado los conocimientos. ¡Vamos a por ello! 22 | 23 | Recuerda que tenemos un canal de Discord en el que podrás conocer a más personas del mundo de desarrollo, anunciamos los artículos y comentamos los ejercicios 😊 Si te quieres unir revisa nuestros talleres en directo en [Twitch](https://www.twitch.tv/minicodelab) y vídeos en [Youtube](https://www.youtube.com/channel/UCN1SyK4zRHbdIO6HptDoDOA) para ver como acceder. 24 | 25 | --- 26 | 27 | ### Valor del Bitcoin 💶 28 | 29 | Vamos a realizar un conversor de euros a Bitcoin. Como el valor fluctúa tanto podemos decir que un euro son 0.01 Bitcoin. El user introduce un valor en euros y se mostrará el valor del Bitcoin. 30 | 31 | ```tsx 32 | const BitcoinConversor = (): React.FC => { 33 | // Añade los estados que necesites... 34 | 35 | return ( 36 |
    37 |

    Convierte Euros a Bitcoins 💶

    38 | 39 | {/* Aquí deberíamos tener un input! */} 40 | 41 |

    {euro} EUR

    42 |

    {bitcoin} BTC

    43 |
    44 | ); 45 | }; 46 | ``` 47 | 48 | --- 49 | 50 | ### Select Avenger 51 | 52 | Desarrollar una aplicación sencilla en la que, al pulsar sobre cada uno de los botones representados por el nombre un avenger, debe cargarse la imagen del Avenger. ¡Punto extra si consigues ocultarla y mostrarla con cada click! 🦸‍♂️ 53 | 54 | ```tsx 55 | const myAvenger = { 56 | ironMan: 57 | 'https://www.sideshow.com/storage/product-images/500846U/the-invincible-iron-man_marvel_silo_lg.png', 58 | spiderMan: 59 | 'https://pbs.twimg.com/media/EYbVjfXXgAEe2yG?format=jpg&name=4096x4096', 60 | blackPanther: 61 | 'https://www.lafuerzararuna.com/files/products/avengers-37-alex-ross-black-panther-timeless-var-7eb333d8eb012e32.jpg?width=600&quality=100' 62 | }; 63 | 64 | const AvengersPanel = (): React.FC => { 65 | // Deberíamos tener un state más complejo para manejar los botones 66 | 67 | return ( 68 |
    {/* Aquí mapearemos el objeto myAvenger convertido en array */}
    69 | ); 70 | }; 71 | ``` 72 | 73 | --- 74 | 75 | ### Cambia el nombre en el Render 76 | 77 | Crea un componente que cuando se monte cambie el valor del state inicial, es decir, tienes un useState con un valor inicial y en el effect debería cambiar por otro que queráis. ¡Recuerda el segundo argumento del effect! 78 | 79 | Ahora añade un input a través del cual al cambiar su valor, cambie el state del nombre. Y añade un useEffect que cambie ese state nuevamente a un nombre en mayúsculas 🧙‍♂️ 80 | 81 | ```tsx 82 | const ChangeName = (): React.FC => { 83 | const [name, setName] = useState('MiniCodeLab'); 84 | 85 | // Aquí cambiaremos el nombre en el render inicial, ¿como lo harás? 86 | // useEffect(() => {}); 87 | 88 | return ( 89 |

    El nombre es: {name}

    90 | 91 | // Y un input que nos permita cambiarlo... 92 | ); 93 | }; 94 | ``` 95 | 96 | --- 97 | 98 | ### Renderizado en el click 🔄 99 | 100 | Crea un componente en el que cada vez que se haga click sobre un botón su state cambie y por lo tanto se lance un repintado de nuestra aplicación. ¡Punto extra si consigues hacer un contador con las veces que lo hemos renderizado! 101 | 102 | ```tsx 103 | const RenderOnClick = (): React.FC => {}; 104 | ``` 105 | 106 | --- 107 | 108 | ### Filtrado de elementos 109 | 110 | Crea un filtro de personajes de Rick and Morty, para ello debes recuperar los valores de la API y después filtrar cada uno de los personajes (solo en el cliente, no desde la API) en base a un input. ¡Este es el más complejo, ten un poquito de paciencia y prueba a conseguirlo! 111 | 112 | ```tsx 113 | const RickMortyFiler = (): React.FC => { 114 | const [characters, setCharacters] = useState([]); 115 | const [filter, setFilter] = useState(''); 116 | 117 | useEffect(() => { 118 | // Pediremos los personajes a la API... 119 | }, []); 120 | 121 | return ( 122 |
    123 | {}} // Cambiamos el state cuando cambie el input 127 | /> 128 | 129 |

    Los personajes:

    130 | {/* Listamos los personajes... */} 131 |
    132 | ); 133 | }; 134 | ``` 135 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "taller-hooks-minicodelab", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.2", 7 | "@testing-library/react": "^12.1.2", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.4.0", 10 | "@types/node": "^16.11.24", 11 | "@types/react": "^17.0.39", 12 | "@types/react-dom": "^17.0.11", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "5.0.0", 16 | "typescript": "^4.5.5", 17 | "web-vitals": "^2.1.4" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-2-usestate-useeffect/taller-twitch/public/favicon.ico -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
    32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-2-usestate-useeffect/taller-twitch/public/logo192.png -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-2-usestate-useeffect/taller-twitch/public/logo512.png -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | background-color: #282c34; 4 | color: white; 5 | min-height: 100vh; 6 | } 7 | 8 | h1, 9 | h2, 10 | h3, 11 | h4 { 12 | margin-top: 0; 13 | padding: 0.5rem; 14 | } 15 | 16 | hr { 17 | border: 1px solid #9e9e9e70; 18 | } 19 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/src/App.tsx: -------------------------------------------------------------------------------- 1 | // import React from 'react' 2 | // Podríamos usar los hooks de esta forma también: 3 | // React.useState 4 | // React.useEffect 5 | import './App.css'; 6 | import MiniCodeComplexState from './components/MiniCodeComplexState'; 7 | import MiniCodeEffectMount from './components/MiniCodeEffectMount'; 8 | import MiniCodeEffectUnmount from './components/MiniCodeEffectUnmount'; 9 | import MiniCodeInput from './components/MiniCodeInput'; 10 | import MiniCodeStateCallback from './components/MiniCodeStateCallback'; 11 | 12 | const App: React.FC = () => { 13 | return ( 14 |
    15 |

    Ejemplos con useState

    16 | 17 | 18 | 19 |
    20 | 21 | 22 | 23 |
    24 | 25 | 26 | 27 |
    28 |
    29 | 30 |

    Ejemplos con useEffect

    31 | 32 | 33 | 34 |
    35 | 36 | 37 |
    38 | ); 39 | }; 40 | 41 | export default App; 42 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/src/components/MessageComponent.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | type Props = { 4 | name: string; 5 | }; 6 | 7 | const MessageComponent: React.FC = ({ name }) => { 8 | useEffect(() => { 9 | const interval = setInterval(() => { 10 | console.log('MessageComponent:', name); 11 | }, 1000); 12 | 13 | // Va a escuchar al componente desmontarse "cleanup" 14 | return () => { 15 | console.log('Desmontado!'); 16 | clearInterval(interval); 17 | }; 18 | }, []); 19 | 20 | return

    {name}

    ; 21 | }; 22 | 23 | export default MessageComponent; 24 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/src/components/MiniCodeComplexState.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | type Avenger = { 4 | name: string; 5 | surname: string; 6 | }; 7 | 8 | const MiniCodeComplexState: React.FC = () => { 9 | const [avenger, setAvenger] = useState({ 10 | name: 'Thor', 11 | surname: 'Odinson', 12 | }); 13 | 14 | const { name, surname } = avenger; 15 | 16 | return ( 17 | <> 18 |

    19 | Nombre: {name} | Apellido: {surname} 20 |

    21 | 22 | 23 | { 27 | const newName = e.target.value; 28 | 29 | setAvenger({ 30 | ...avenger, 31 | name: newName, 32 | }); 33 | }} 34 | /> 35 | 36 |
    37 | 38 | 39 | { 43 | const newSurname = e.target.value; 44 | 45 | setAvenger({ 46 | ...avenger, 47 | surname: newSurname, 48 | }); 49 | }} 50 | /> 51 | 52 | ); 53 | }; 54 | 55 | export default MiniCodeComplexState; 56 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/src/components/MiniCodeEffectMount.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | const MiniCodeEffectMount: React.FC = () => { 4 | const [myName, setMyName] = useState('David Bowie'); 5 | 6 | // Ocurre siempre cuando el componente se monta 7 | useEffect(() => { 8 | // console.log('El componente se ha montado!'); 9 | 10 | // Se lanza tantas veces como escribamos en el input 11 | setTimeout(() => { 12 | console.log('Cambiando a ziggy'); 13 | setMyName('Ziggy Stardust'); 14 | }, 2000); 15 | // }); 16 | }, []); 17 | 18 | // Al pasar el state por param, no hace falta llevarlo al array de deps 19 | function sayHello(name: string): void { 20 | console.log(name); 21 | } 22 | 23 | // También se lanza cuando myName cambie 24 | useEffect(() => { 25 | // console.log('Ha cambiado myName:', myName); 26 | 27 | sayHello(myName); 28 | }, [myName]); 29 | 30 | return ( 31 |
    32 |

    {myName}

    33 | 34 | setMyName(e.target.value)} 38 | /> 39 |
    40 | ); 41 | }; 42 | 43 | export default MiniCodeEffectMount; 44 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/src/components/MiniCodeEffectUnmount.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import MessageComponent from './MessageComponent'; 3 | 4 | const MiniCodeEffectUnmount: React.FC = () => { 5 | const [visible, setVisible] = useState(false); 6 | 7 | return ( 8 | <> 9 | {visible ? : null} 10 |

    Pulsa el botón para montar/desmontar un componente...

    11 | 12 | 13 | ); 14 | }; 15 | 16 | export default MiniCodeEffectUnmount; 17 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/src/components/MiniCodeInput.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | const MiniCodeInput: React.FC = () => { 4 | const [name, setName] = useState('Alberto'); 5 | 6 | return ( 7 | <> 8 |

    Mi nombre es {name}

    9 | { 12 | setName(e.target.value); 13 | 14 | // Se queda el último valor porque el render no es inmediato! 15 | setName(e.target.value.toUpperCase()); 16 | 17 | // Después de dos segundos, manda este setState 18 | setTimeout(() => { 19 | setName(e.target.value.toUpperCase()); 20 | }, 2000); 21 | }} 22 | /> 23 | 24 | ); 25 | }; 26 | 27 | export default MiniCodeInput; 28 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/src/components/MiniCodeStateCallback.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | const MiniCodeStateCallback: React.FC = () => { 4 | const [count, setCount] = useState(0); 5 | const [multiplier, setMultiplier] = useState(1); 6 | 7 | return ( 8 | <> 9 |

    Total: {count}

    10 | 11 | { 16 | setMultiplier(e.target.valueAsNumber); 17 | }} 18 | /> 19 | 20 |
    21 | 22 | 35 | 36 | ); 37 | }; 38 | 39 | export default MiniCodeStateCallback; 40 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /sesion-2-usestate-useeffect/taller-twitch/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /sesion-3-hooks/.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 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /sesion-3-hooks/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /sesion-3-hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sesion-3-hook-useref", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.2", 7 | "@testing-library/react": "^12.1.3", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.4.0", 10 | "@types/node": "^16.11.25", 11 | "@types/react": "^17.0.39", 12 | "@types/react-dom": "^17.0.11", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "5.0.0", 16 | "typescript": "^4.5.5", 17 | "web-vitals": "^2.1.4" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sesion-3-hooks/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-3-hooks/public/favicon.ico -------------------------------------------------------------------------------- /sesion-3-hooks/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
    32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /sesion-3-hooks/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-3-hooks/public/logo192.png -------------------------------------------------------------------------------- /sesion-3-hooks/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-3-hooks/public/logo512.png -------------------------------------------------------------------------------- /sesion-3-hooks/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /sesion-3-hooks/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /sesion-3-hooks/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | min-height: 100vh; 4 | background-color: #282c34; 5 | font-size: calc(10px + 2vmin); 6 | color: white; 7 | } 8 | 9 | h1, 10 | h2, 11 | h3, 12 | h4 { 13 | margin: 0; 14 | padding: 1rem; 15 | } 16 | 17 | .box { 18 | color: black; 19 | background-color: coral; 20 | } 21 | 22 | .box-modify { 23 | color: white; 24 | background-color: royalblue; 25 | } 26 | -------------------------------------------------------------------------------- /sesion-3-hooks/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /sesion-3-hooks/src/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import UseRefExamples from './components/UseRefExamples'; 3 | 4 | const App = () => { 5 | return ( 6 |
    7 | 8 |
    9 | ); 10 | }; 11 | 12 | export default App; 13 | -------------------------------------------------------------------------------- /sesion-3-hooks/src/components/MiniCodeInterval.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef, useState } from 'react'; 2 | 3 | const MiniCodeInterval = () => { 4 | const [toggle, setToggle] = useState(false); 5 | // Creo un ref para almacenar el intervalo 6 | const intervalRef = useRef(null); 7 | 8 | useEffect(() => { 9 | // Cuando activo el toggle POR PRIMERA VEZ 10 | // y NO TENGO INTERVALO guardado en intervalRef 11 | if (toggle && !intervalRef.current) { 12 | let time = 0; 13 | 14 | // Guardo el intervalo en intervalRef y por tanto, 15 | // ya no entra nunca más en el if 16 | intervalRef.current = setInterval(() => { 17 | time += 1000; 18 | 19 | console.log('Intervalo!', time); 20 | // Cuando time sea 30... puedo hacer una request 21 | }, 1000); 22 | } 23 | }, [toggle]); 24 | 25 | // Solamente LIMPIA el intervalo al desmontar el componente 26 | useEffect(() => { 27 | return () => { 28 | if (intervalRef.current) { 29 | clearInterval(intervalRef.current); 30 | } 31 | }; 32 | }, []); 33 | 34 | console.log('Renderizando MiniCodeInterval!'); 35 | 36 | return ( 37 |
    38 |

    MiniCodeInterval

    39 | 40 | 41 |
    42 | ); 43 | }; 44 | 45 | export default MiniCodeInterval; 46 | -------------------------------------------------------------------------------- /sesion-3-hooks/src/components/MiniCodeTaxCalculator.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from 'react'; 2 | 3 | const MiniCodeTaxCalculator: React.FC = () => { 4 | const grossIncomeRef = useRef(null); 5 | const taxPercentRef = useRef(null); 6 | const [total, setTotal] = useState(0); 7 | 8 | const getNetIncome = (): void => { 9 | const grossIncome = grossIncomeRef.current?.valueAsNumber as number; 10 | const taxPercent = taxPercentRef.current?.valueAsNumber as number; 11 | 12 | const totalCalc = grossIncome - grossIncome * (taxPercent / 100); 13 | console.log('The net total is:', totalCalc); 14 | setTotal(totalCalc); 15 | }; 16 | 17 | console.log('Renderizando MiniCodeTaxCalculator'); 18 | return ( 19 | <> 20 |

    Calculadora sueldo neto

    21 | 22 | 23 | 31 | 32 |
    33 | 34 | 35 | 44 | 45 |
    46 | 47 | 48 | 49 |

    El total es: {total}

    50 | 51 | ); 52 | }; 53 | 54 | export default MiniCodeTaxCalculator; 55 | -------------------------------------------------------------------------------- /sesion-3-hooks/src/components/MiniCodeUseRef.tsx: -------------------------------------------------------------------------------- 1 | import { FormEvent, useEffect, useRef, useState } from 'react'; 2 | 3 | const MiniCodeUseRef: React.FC = () => { 4 | const textInputRef = useRef(null); 5 | const [name, setName] = useState('Alberto'); 6 | const [count, setCount] = useState(0); 7 | 8 | const handleSubmit = (e: FormEvent) => { 9 | e.preventDefault(); 10 | 11 | const nameInputValue = textInputRef.current?.value; 12 | 13 | if (nameInputValue) { 14 | setName(nameInputValue); 15 | 16 | if (textInputRef.current) { 17 | textInputRef.current.value = ''; 18 | } 19 | } 20 | }; 21 | 22 | const printValue = () => { 23 | const inputValue = textInputRef.current?.value; 24 | if (inputValue) setName(inputValue); 25 | 26 | console.log('Imprime nombre:', inputValue); 27 | }; 28 | 29 | console.log('Renderizando componente MiniCodeUseRef'); 30 | console.log('La referencia:', textInputRef); 31 | 32 | useEffect(() => { 33 | console.log('Se ha montado el componente, hacemos focus al input:'); 34 | 35 | textInputRef.current?.focus(); 36 | }, []); 37 | 38 | return ( 39 |
    40 |

    Hola soy {name}

    41 | 42 |
    43 | 44 | {/* Este input number provoca rerenders al cambiar el state */} 45 | { 50 | setCount(e.target.valueAsNumber); 51 | }} 52 | /> 53 | 54 | 55 |
    56 |
    57 | ); 58 | }; 59 | 60 | export default MiniCodeUseRef; 61 | -------------------------------------------------------------------------------- /sesion-3-hooks/src/components/MiniCodeUseRefCss .tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState } from 'react'; 2 | import '../App.css'; 3 | 4 | const MiniCodeUseRefCss = () => { 5 | const [classColor, setClassColor] = useState('box'); 6 | const colorRef = useRef(null); 7 | 8 | const changeColorState = () => { 9 | setClassColor(classColor === 'box' ? 'box-modify' : 'box'); 10 | }; 11 | 12 | const changeColorRef = () => { 13 | if (colorRef.current) { 14 | const newClass = 15 | colorRef.current.className === 'box' ? 'box-modify' : 'box'; 16 | 17 | colorRef.current.className = newClass; 18 | } 19 | }; 20 | 21 | console.log('Renderizando MiniCodeUseRefCss'); 22 | 23 | return ( 24 | <> 25 |
    I´m in a Box STATE
    26 | 27 | 28 |
    29 | I´m in a Box REF 30 |
    31 | 32 | 33 | ); 34 | }; 35 | 36 | export default MiniCodeUseRefCss; 37 | -------------------------------------------------------------------------------- /sesion-3-hooks/src/components/UseRefExamples.tsx: -------------------------------------------------------------------------------- 1 | import MiniCodeInterval from './MiniCodeInterval'; 2 | import MiniCodeTaxCalculator from './MiniCodeTaxCalculator'; 3 | import MiniCodeUseRef from './MiniCodeUseRef'; 4 | import MiniCodeUseRefCss from './MiniCodeUseRefCss '; 5 | 6 | const UseRefExamples: React.FC = () => { 7 | return ( 8 |
    9 |

    Ejemplos con useRef

    10 | 11 | 12 | 13 |
    14 | 15 | 16 | 17 |
    18 | 19 | 20 | 21 |
    22 | 23 | 24 |
    25 | ); 26 | }; 27 | 28 | export default UseRefExamples; 29 | -------------------------------------------------------------------------------- /sesion-3-hooks/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /sesion-3-hooks/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | -------------------------------------------------------------------------------- /sesion-3-hooks/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /sesion-3-hooks/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /sesion-3-hooks/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /sesion-3-hooks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/.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 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sesion-4-hooks-usememo-usecallback", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.2", 7 | "@testing-library/react": "^12.1.3", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.4.1", 10 | "@types/node": "^16.11.26", 11 | "@types/react": "^17.0.39", 12 | "@types/react-dom": "^17.0.11", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "5.0.0", 16 | "typescript": "^4.5.5", 17 | "web-vitals": "^2.1.4" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-4-hooks-usememo-usecallback/public/favicon.ico -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
    32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-4-hooks-usememo-usecallback/public/logo192.png -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-4-hooks-usememo-usecallback/public/logo512.png -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | padding: 2rem; 4 | } 5 | 6 | .tab-manager { 7 | max-width: 100%; 8 | } 9 | 10 | .tab-header { 11 | display: flex; 12 | align-items: center; 13 | justify-content: center; 14 | margin-bottom: 2rem; 15 | padding: 1rem; 16 | gap: 1rem; 17 | border-bottom: 1px solid #94a1b2; 18 | } 19 | 20 | .tab-header > button { 21 | background: #7f5af0; 22 | color: black; 23 | border: 2px solid black; 24 | border-radius: 3px; 25 | font-size: 1rem; 26 | padding: 0.5rem; 27 | font-weight: bold; 28 | cursor: pointer; 29 | } 30 | 31 | .tab-header .active { 32 | background: #2cb67d; 33 | } 34 | 35 | .rows { 36 | display: flex; 37 | align-items: center; 38 | justify-content: center; 39 | gap: 2rem; 40 | } 41 | 42 | ul { 43 | list-style-type: none; 44 | } 45 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/src/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import MiniCodeTabManager from './components/MiniCodeTabManager'; 3 | import MiniCodeUseCallback from './components/MiniCodeUseCallback'; 4 | import MiniCodeUseMemo from './components/MiniCodeUseMemo'; 5 | 6 | function App() { 7 | return ( 8 |
    9 |

    MiniCodeLab - [Hooks useMemo & useCallback]

    10 | 11 | 26 |
    27 | ); 28 | } 29 | 30 | export default App; 31 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/src/components/BasicUseCallback.tsx: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useMemo, useState } from 'react'; 2 | 3 | const BasicUseCallback: React.FC = () => { 4 | const [count, setCount] = useState(0); 5 | 6 | // useCallback almacena la función que definimos siempre que cambie el contenido 7 | // del array de dependencias 8 | const resetCount = useCallback(() => { 9 | console.log('Count vale', count); 10 | 11 | setCount(0); 12 | }, []); 13 | 14 | // const resetCountNoCb = () => setCount(0); 15 | 16 | useEffect(() => { 17 | console.log('Lanzando el efecto!'); 18 | }, [resetCount]); 19 | // }, [resetCountNoCb]); 20 | 21 | return ( 22 |
    23 |

    Cuenta useCallback

    24 | 25 |

    La cuenta es: {count}

    26 | 27 | 30 | 33 | 34 | 35 |
    36 | ); 37 | }; 38 | 39 | export default BasicUseCallback; 40 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/src/components/BasicUseMemo.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo, useState } from 'react'; 2 | 3 | const numbersArray = [1, 2, 1, 4, 0, 6]; 4 | 5 | const mapScores = (scores: number[], caller: string) => { 6 | console.log('Invocamos mapScores =>', caller); 7 | 8 | return scores.map((num, index) => { 9 | const calc = (num * 3) / 2; 10 | const color = calc < 3 ? '🔴' : '🟢'; 11 | 12 | return ( 13 |

    14 | {calc} {color} 15 |

    16 | ); 17 | }); 18 | }; 19 | 20 | const BasicUseMemo: React.FC = () => { 21 | const [rerender, setRerender] = useState(false); 22 | 23 | const marksContent = mapScores(numbersArray, 'no-memo'); 24 | 25 | const marksContentMemo = useMemo(() => { 26 | return mapScores(numbersArray, 'memo'); 27 | }, []); 28 | 29 | return ( 30 |
    31 |
    32 |
    33 |

    No memo

    34 | {marksContent} 35 |
    36 | 37 |
    38 |

    Usando memo

    39 | {marksContentMemo} 40 |
    41 |
    42 | 43 | 44 |
    45 | ); 46 | }; 47 | 48 | export default BasicUseMemo; 49 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/src/components/MiniCodePosts.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo, useState } from 'react'; 2 | 3 | const posts = [ 4 | { 5 | slug: 'vue-desde-0', 6 | date: 'Fri Oct 06 2023 10:45:00 GMT+0200 (Central European Summer Time)', 7 | }, 8 | { 9 | slug: 'react-desde-0', 10 | date: 'Thu Feb 17 2022 18:15:00 GMT+0100 (Central European Standard Time)', 11 | }, 12 | { 13 | slug: 'angular-desde-0', 14 | date: 'Tue Aug 23 2022 13:21:00 GMT+0200 (Central European Summer Time)', 15 | }, 16 | ]; 17 | 18 | export const MiniCodePosts: React.FC = () => { 19 | const [rerender, setRerender] = useState(false); 20 | 21 | // Menuda locura de cálculos! 🤯 22 | const orderedPostsWithTitle = useMemo(() => { 23 | console.log('Calculando los posts con useMemo'); 24 | 25 | return posts 26 | .map((post) => { 27 | return { 28 | ...post, 29 | date: new Date(post.date), 30 | title: post.slug.split('-').join(' ').toUpperCase(), 31 | }; 32 | }) 33 | .sort((a, b) => a.date.getTime() - b.date.getTime()) 34 | .map((post) => ({ 35 | ...post, 36 | date: new Intl.DateTimeFormat('es-ES').format(post.date), 37 | })); 38 | }, []); 39 | 40 | return ( 41 |
    42 |

    Talleres destacados 😍

    43 | 44 |
      45 | {orderedPostsWithTitle.map((post) => ( 46 |
    • 47 |

      {post.title}

      {post.date} 48 |
      49 |
    • 50 | ))} 51 |
    52 | 53 | 54 |
    55 | ); 56 | }; 57 | 58 | export default MiniCodePosts; 59 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/src/components/MiniCodePostsAsync.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useMemo, useState } from 'react'; 2 | 3 | type Post = { 4 | slug: string; 5 | date: string; 6 | title?: string; 7 | }; 8 | 9 | const posts = [ 10 | { 11 | slug: 'vue-desde-0', 12 | date: 'Fri Oct 06 2023 10:45:00 GMT+0200 (Central European Summer Time)', 13 | }, 14 | { 15 | slug: 'react-desde-0', 16 | date: 'Thu Feb 17 2022 18:15:00 GMT+0100 (Central European Standard Time)', 17 | }, 18 | { 19 | slug: 'angular-desde-0', 20 | date: 'Tue Aug 23 2022 13:21:00 GMT+0200 (Central European Summer Time)', 21 | }, 22 | ]; 23 | 24 | // Imagina que esto pide info de una API 25 | const fetchPosts = (): Promise => { 26 | return new Promise((resolve) => { 27 | setTimeout(() => { 28 | resolve(posts); 29 | }, 2000); 30 | }); 31 | }; 32 | 33 | export const MiniCodePostsAsync: React.FC = () => { 34 | const [allPosts, setAllPosts] = useState([]); 35 | const [rerender, setRerender] = useState(false); 36 | 37 | useEffect(() => { 38 | // Simulamos que pedimos info a una API 39 | fetchPosts().then((postsResponse) => { 40 | console.log('Formateando posts'); 41 | const formattedPosts = postsResponse 42 | .map((post) => { 43 | return { 44 | ...post, 45 | date: new Date(post.date), 46 | title: post.slug.split('-').join(' ').toUpperCase(), 47 | }; 48 | }) 49 | .sort((a, b) => a.date.getTime() - b.date.getTime()) 50 | .map((post) => ({ 51 | ...post, 52 | date: new Intl.DateTimeFormat('es-ES').format(post.date), 53 | })); 54 | 55 | setAllPosts(formattedPosts); 56 | }); 57 | }, []); 58 | 59 | // Menuda locura de cálculos! 🤯 60 | // const orderedPostsWithTitle = useMemo(() => { 61 | // console.log('Calculando los posts con useMemo'); 62 | 63 | // return allPosts 64 | // .map((post) => { 65 | // return { 66 | // ...post, 67 | // date: new Date(post.date), 68 | // title: post.slug.split('-').join(' ').toUpperCase(), 69 | // }; 70 | // }) 71 | // .sort((a, b) => a.date.getTime() - b.date.getTime()) 72 | // .map((post) => ({ 73 | // ...post, 74 | // date: new Intl.DateTimeFormat('es-ES').format(post.date), 75 | // })); 76 | // }, [allPosts]); 77 | 78 | return ( 79 |
    80 |

    Talleres destacados 😍

    81 | 82 |
      83 | {allPosts.map((post) => ( 84 |
    • 85 |

      {post.title}

      {post.date} 86 |
      87 |
    • 88 | ))} 89 |
    90 | 91 | 92 |
    93 | ); 94 | }; 95 | 96 | export default MiniCodePostsAsync; 97 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/src/components/MiniCodeTabManager.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | const MiniCodeTabManager: React.FC = ({ 4 | tabs, 5 | defaultTab, 6 | withUnset = false, 7 | }) => { 8 | const [currentTab, setCurrentTab] = useState( 9 | defaultTab || null 10 | ); 11 | 12 | const TabToShow = tabs.find(({ slug }) => slug === currentTab)?.content; 13 | 14 | return ( 15 |
    16 |
    17 | {tabs.map((tab) => ( 18 | 25 | ))} 26 | 27 | {withUnset ? ( 28 | 34 | ) : null} 35 |
    36 | 37 |
    {TabToShow ? : null}
    38 |
    39 | ); 40 | }; 41 | 42 | export type Props = { 43 | defaultTab?: string; 44 | tabs: { 45 | content: React.FC; 46 | slug: string; 47 | title: string; 48 | }[]; 49 | withUnset?: boolean; 50 | }; 51 | 52 | export default MiniCodeTabManager; 53 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/src/components/MiniCodeUseCallback.tsx: -------------------------------------------------------------------------------- 1 | import BasicUseCallback from './BasicUseCallback'; 2 | import BasicUseMemo from './BasicUseMemo'; 3 | import MiniCodePosts from './MiniCodePosts'; 4 | import MiniCodePostsAsync from './MiniCodePostsAsync'; 5 | import MiniCodeTabManager from './MiniCodeTabManager'; 6 | 7 | function MiniCodeUseCallback() { 8 | return ( 9 |
    10 |

    Ejemplos de uso - useMemo

    11 | 12 | 21 |
    22 | ); 23 | } 24 | 25 | export default MiniCodeUseCallback; 26 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/src/components/MiniCodeUseMemo.tsx: -------------------------------------------------------------------------------- 1 | import BasicUseMemo from './BasicUseMemo'; 2 | import MiniCodePosts from './MiniCodePosts'; 3 | import MiniCodePostsAsync from './MiniCodePostsAsync'; 4 | import MiniCodeTabManager from './MiniCodeTabManager'; 5 | 6 | function MiniCodeUseMemo() { 7 | return ( 8 |
    9 |

    Ejemplos de uso - useMemo

    10 | 11 | 30 |
    31 | ); 32 | } 33 | 34 | export default MiniCodeUseMemo; 35 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap'); 2 | 3 | body { 4 | -moz-osx-font-smoothing: grayscale; 5 | -webkit-font-smoothing: antialiased; 6 | background-color: #16161a; 7 | font-family: 'Roboto', sans-serif; 8 | margin: 0; 9 | min-height: 100vh; 10 | color: white; 11 | } 12 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /sesion-4-hooks-usememo-usecallback/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/.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 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sesion-5-custom-hooks", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.2", 7 | "@testing-library/react": "^12.1.3", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.4.1", 10 | "@types/node": "^16.11.26", 11 | "@types/react": "^17.0.39", 12 | "@types/react-dom": "^17.0.13", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "5.0.0", 16 | "typescript": "^4.6.2", 17 | "web-vitals": "^2.1.4" 18 | }, 19 | "scripts": { 20 | "start": "react-scripts start", 21 | "build": "react-scripts build", 22 | "test": "react-scripts test", 23 | "eject": "react-scripts eject" 24 | }, 25 | "eslintConfig": { 26 | "extends": [ 27 | "react-app", 28 | "react-app/jest" 29 | ] 30 | }, 31 | "browserslist": { 32 | "production": [ 33 | ">0.2%", 34 | "not dead", 35 | "not op_mini all" 36 | ], 37 | "development": [ 38 | "last 1 chrome version", 39 | "last 1 firefox version", 40 | "last 1 safari version" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-5-custom-hooks/public/favicon.ico -------------------------------------------------------------------------------- /sesion-5-custom-hooks/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
    32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-5-custom-hooks/public/logo192.png -------------------------------------------------------------------------------- /sesion-5-custom-hooks/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-5-custom-hooks/public/logo512.png -------------------------------------------------------------------------------- /sesion-5-custom-hooks/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | padding: 2rem; 4 | } 5 | 6 | ul { 7 | list-style-type: none; 8 | } 9 | 10 | .tab-manager { 11 | max-width: 100%; 12 | } 13 | 14 | .tab-header { 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | margin-bottom: 2rem; 19 | padding: 1rem; 20 | gap: 1rem; 21 | border-bottom: 1px solid #94a1b2; 22 | } 23 | 24 | .tab-header > button { 25 | background: #7f5af0; 26 | color: black; 27 | border: 2px solid black; 28 | border-radius: 3px; 29 | font-size: 1rem; 30 | padding: 0.5rem; 31 | font-weight: bold; 32 | cursor: pointer; 33 | } 34 | 35 | .tab-header .active { 36 | background: #2cb67d; 37 | } 38 | 39 | .rows { 40 | display: flex; 41 | align-items: center; 42 | justify-content: center; 43 | gap: 2rem; 44 | } 45 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/src/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import MiniCodeCounter from './components/MiniCodeCounter'; 3 | import MiniCodeDebounce from './components/MiniCodeDebounce'; 4 | import MiniCodeTabManager from './components/MiniCodeTabManager'; 5 | 6 | function App() { 7 | return ( 8 |
    9 | 24 |
    25 | ); 26 | } 27 | 28 | export default App; 29 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/src/components/MiniCodeCounter.tsx: -------------------------------------------------------------------------------- 1 | import useCount from '../hooks/useCount'; 2 | import ThirdCount from './ThirdCount'; 3 | 4 | const MiniCodeCounter = () => { 5 | const firstCount = useCount(); 6 | const secondCount = useCount(); 7 | 8 | return ( 9 | <> 10 |
    11 |

    Contador 1 🚴‍♀️

    12 | 13 |
    14 |

    El contador 1 vale {firstCount.count}

    15 | 16 | 17 |
    18 | 19 |
    20 |
    21 | 22 |
    23 |

    Contador 2 🚴

    24 | 25 |
    26 |

    El contador 2 vale {secondCount.count}

    27 | 28 | 29 |
    30 | 31 |
    32 |
    33 | 34 | {firstCount.count > 5 ? : null} 35 | 36 | ); 37 | }; 38 | 39 | export default MiniCodeCounter; 40 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/src/components/MiniCodeDebounce.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import useDebounceString from '../hooks/useDebounceString'; 3 | 4 | const getStartWarsMovies = async (title: string) => { 5 | console.log('Pidiendo las pelis a la API con título', title); 6 | 7 | const response = await new Promise((resolve) => { 8 | setTimeout(() => { 9 | if (title.includes('imperio')) { 10 | resolve([{ title: 'El imperio contraataca' }]); 11 | } else { 12 | resolve([]); 13 | } 14 | }, 1000); 15 | }); 16 | 17 | return response; 18 | }; 19 | 20 | const MiniCodeDebounce = () => { 21 | const [filter, setFilter] = useState(''); 22 | const debouncedFilter = useDebounceString(filter, 250); 23 | 24 | console.log({ filter, debouncedFilter }); 25 | 26 | useEffect(() => { 27 | if (debouncedFilter) { 28 | getStartWarsMovies(debouncedFilter).then((movies) => { 29 | console.log(movies); 30 | }); 31 | } 32 | }, [debouncedFilter]); 33 | 34 | return ( 35 |
    36 |

    Star Wars API

    37 | 38 |
    39 | setFilter(e.target.value)} 43 | /> 44 |
    45 |
    46 | ); 47 | }; 48 | 49 | export default MiniCodeDebounce; 50 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/src/components/MiniCodeTabManager.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | const MiniCodeTabManager = ({ tabs, defaultTab, withUnset = false }: Props) => { 4 | const [currentTab, setCurrentTab] = useState( 5 | defaultTab || null 6 | ); 7 | 8 | const TabToShow = tabs.find(({ slug }) => slug === currentTab)?.content; 9 | 10 | return ( 11 |
    12 |
    13 | {tabs.map((tab) => ( 14 | 21 | ))} 22 | 23 | {withUnset ? ( 24 | 30 | ) : null} 31 |
    32 | 33 |
    {TabToShow ? : null}
    34 |
    35 | ); 36 | }; 37 | 38 | export type Props = { 39 | defaultTab?: string; 40 | tabs: { 41 | content: React.FC; 42 | slug: string; 43 | title: string; 44 | }[]; 45 | withUnset?: boolean; 46 | }; 47 | 48 | export default MiniCodeTabManager; 49 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/src/components/ThirdCount.tsx: -------------------------------------------------------------------------------- 1 | import useCount from '../hooks/useCount'; 2 | 3 | const ThirdCount = () => { 4 | const { count, add, substract } = useCount(); 5 | 6 | return ( 7 |
    8 |

    Contador 3 🚀

    9 | 10 |
    11 |

    El contador 3 vale {count}

    12 | 13 | 14 |
    15 | 16 |
    17 |
    18 | ); 19 | }; 20 | 21 | export default ThirdCount; 22 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/src/hooks/useCount.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useState } from 'react'; 2 | 3 | type UseCountReturn = { 4 | count: number; 5 | add: () => void; 6 | substract: () => void; 7 | }; 8 | 9 | function useCount(): UseCountReturn { 10 | const [count, setCount] = useState(0); 11 | 12 | const add = useCallback(() => setCount((prevCount) => prevCount + 1), []); 13 | const substract = useCallback( 14 | () => setCount((prevCount) => prevCount - 1), 15 | [] 16 | ); 17 | 18 | return { count, add, substract }; 19 | } 20 | 21 | export default useCount; 22 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/src/hooks/useDebounceString.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | function useDebounceString(value: string, timeout = 500): string { 4 | const [debouncedValue, setDebouncedValue] = useState(value || ''); 5 | 6 | useEffect(() => { 7 | const timeoutId = setTimeout(() => { 8 | setDebouncedValue(value); 9 | }, timeout); 10 | 11 | return () => { 12 | clearTimeout(timeoutId); 13 | }; 14 | }, [timeout, value]); 15 | 16 | return debouncedValue; 17 | } 18 | 19 | export default useDebounceString; 20 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap'); 2 | 3 | body { 4 | -moz-osx-font-smoothing: grayscale; 5 | -webkit-font-smoothing: antialiased; 6 | background-color: #16161a; 7 | font-family: 'Roboto', sans-serif; 8 | margin: 0; 9 | min-height: 100vh; 10 | color: white; 11 | } 12 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /sesion-5-custom-hooks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /sesion-6-react-router/.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 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /sesion-6-react-router/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /sesion-6-react-router/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sesion-6-react-router", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.2", 7 | "@testing-library/react": "^12.1.4", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.4.1", 10 | "@types/node": "^16.11.26", 11 | "@types/react": "^17.0.40", 12 | "@types/react-dom": "^17.0.13", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-router-dom": "^6.2.2", 16 | "react-scripts": "5.0.0", 17 | "typescript": "^4.6.2", 18 | "web-vitals": "^2.1.4" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sesion-6-react-router/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-6-react-router/public/favicon.ico -------------------------------------------------------------------------------- /sesion-6-react-router/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
    32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /sesion-6-react-router/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-6-react-router/public/logo192.png -------------------------------------------------------------------------------- /sesion-6-react-router/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-6-react-router/public/logo512.png -------------------------------------------------------------------------------- /sesion-6-react-router/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /sesion-6-react-router/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /sesion-6-react-router/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | padding: 2rem; 4 | } 5 | 6 | ul { 7 | list-style-type: none; 8 | } 9 | 10 | .hero-links { 11 | display: flex; 12 | gap: 1rem; 13 | justify-content: center; 14 | } 15 | 16 | .nav { 17 | border-bottom: 1px solid #94a1b2; 18 | display: flex; 19 | gap: 1rem; 20 | justify-content: center; 21 | padding-bottom: 1rem; 22 | } 23 | -------------------------------------------------------------------------------- /sesion-6-react-router/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { NavLink, Outlet } from 'react-router-dom'; 2 | import './App.css'; 3 | 4 | function App() { 5 | return ( 6 |
    7 |

    MiniCodeLab - React Router

    8 | 9 | 13 | 14 |
    15 | {/* Renderiza las rutas hijas escogidas por Routes */} 16 | 17 |
    18 |
    19 | ); 20 | } 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /sesion-6-react-router/src/data/heroes.ts: -------------------------------------------------------------------------------- 1 | export type Hero = { 2 | id: number; 3 | name: string; 4 | age: number; 5 | alias: string; 6 | slug: string; 7 | }; 8 | 9 | export let heroes: Hero[] = [ 10 | { 11 | id: 1, 12 | name: 'Superman', 13 | age: 65, 14 | alias: 'Clark Kent', 15 | slug: 'superman', 16 | }, 17 | { 18 | id: 2, 19 | name: 'Batman', 20 | age: 55, 21 | slug: 'batman', 22 | alias: 'Bruce Wayne', 23 | }, 24 | { 25 | id: 3, 26 | name: 'Wonder Woman', 27 | age: 1555, 28 | alias: 'Diana', 29 | slug: 'wonder-woman', 30 | }, 31 | ]; 32 | 33 | export const getHeroes = () => heroes; 34 | 35 | export const getHeroBySlug = async ( 36 | slug: string 37 | ): Promise => { 38 | return heroes.find((hero) => hero.slug === slug); 39 | }; 40 | 41 | export const deleteHeroById = async (id: number): Promise => { 42 | heroes = heroes.filter((hero) => hero.id !== id); 43 | }; 44 | -------------------------------------------------------------------------------- /sesion-6-react-router/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap'); 2 | 3 | body { 4 | -moz-osx-font-smoothing: grayscale; 5 | -webkit-font-smoothing: antialiased; 6 | background-color: #16161a; 7 | font-family: 'Roboto', sans-serif; 8 | margin: 0; 9 | min-height: 100vh; 10 | color: white; 11 | } 12 | 13 | :root { 14 | --black: #000; 15 | --green: #2cb67d; 16 | --grey: #94a1b2; 17 | --purple: #7f5af0; 18 | --red: #a31621; 19 | } 20 | 21 | a { 22 | color: white; 23 | } 24 | a:visited { 25 | color: var(--purple); 26 | } 27 | a:active, 28 | a.active { 29 | color: var(--green); 30 | } 31 | 32 | h1 { 33 | margin-top: 0; 34 | } 35 | -------------------------------------------------------------------------------- /sesion-6-react-router/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter, Route, Routes } from 'react-router-dom'; 4 | import './index.css'; 5 | import App from './App'; 6 | import Home from './pages/Home'; 7 | import Heroes from './pages/Heroes'; 8 | import Hero from './pages/Hero'; 9 | 10 | ReactDOM.render( 11 | 12 | 13 | 14 | }> 15 | } /> 16 | 17 | }> 18 | } /> 19 | 20 | 21 | 22 | 23 | , 24 | document.getElementById('root') 25 | ); 26 | -------------------------------------------------------------------------------- /sesion-6-react-router/src/pages/Hero.tsx: -------------------------------------------------------------------------------- 1 | // http://localhost:3000/heroes/superman 2 | // Un parámetro dinámico de la URL se llama URL PARAM 3 | // http://localhost:3000/heroes/:heroSlug 4 | 5 | import { useEffect, useState } from 'react'; 6 | import { useNavigate, useParams } from 'react-router-dom'; 7 | import { deleteHeroById, getHeroBySlug, Hero } from '../data/heroes'; 8 | 9 | const HeroPage = () => { 10 | const navigate = useNavigate(); 11 | const { heroSlug } = useParams(); 12 | 13 | const [hero, setHero] = useState(null); 14 | 15 | useEffect(() => { 16 | if (heroSlug) { 17 | getHeroBySlug(heroSlug).then((hero) => { 18 | if (hero) { 19 | setHero(hero); 20 | } 21 | }); 22 | } 23 | }, [heroSlug]); 24 | 25 | const handleDeleteHero = (id: number) => { 26 | deleteHeroById(id).then(() => { 27 | navigate('/'); 28 | }); 29 | }; 30 | 31 | return ( 32 | <> 33 |

    Hero Profile ✨

    34 | 35 | {hero ? ( 36 |
    37 |

    38 | {hero.name} - {hero.age} años 39 |

    40 | 43 |
    44 | ) : ( 45 |

    Este héroe no está disponible 🚔

    46 | )} 47 | 48 | ); 49 | }; 50 | 51 | export default HeroPage; 52 | -------------------------------------------------------------------------------- /sesion-6-react-router/src/pages/Heroes.tsx: -------------------------------------------------------------------------------- 1 | import { NavLink, Outlet } from 'react-router-dom'; 2 | import { heroes } from '../data/heroes'; 3 | 4 | const Heroes = () => { 5 | return ( 6 | <> 7 |

    Heroes Page 🦸

    8 | 9 |
    10 | {heroes.map((hero) => ( 11 | 12 | {hero.name} 13 | 14 | ))} 15 |
    16 | 17 |
    18 | 19 |
    20 | 21 | ); 22 | }; 23 | 24 | export default Heroes; 25 | -------------------------------------------------------------------------------- /sesion-6-react-router/src/pages/Home.tsx: -------------------------------------------------------------------------------- 1 | const Home = () => { 2 | return

    Home Page 🏠

    ; 3 | }; 4 | 5 | export default Home; 6 | -------------------------------------------------------------------------------- /sesion-6-react-router/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /sesion-6-react-router/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /sesion-7-context/.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 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /sesion-7-context/README.md: -------------------------------------------------------------------------------- 1 | # React Context & useContext - MiniCodeLab 2 | 3 | schema 4 | -------------------------------------------------------------------------------- /sesion-7-context/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sesion-7-context", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.2", 7 | "@testing-library/react": "^12.1.4", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.4.1", 10 | "@types/node": "^16.11.26", 11 | "@types/react": "^17.0.40", 12 | "@types/react-dom": "^17.0.13", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-router-dom": "^6.2.2", 16 | "react-scripts": "5.0.0", 17 | "typescript": "^4.6.2", 18 | "web-vitals": "^2.1.4" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sesion-7-context/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-7-context/public/favicon.ico -------------------------------------------------------------------------------- /sesion-7-context/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | MiniCodeLab - useContext 12 | 13 | 14 | 15 |
    16 | 17 | 18 | -------------------------------------------------------------------------------- /sesion-7-context/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-7-context/public/logo192.png -------------------------------------------------------------------------------- /sesion-7-context/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-7-context/public/logo512.png -------------------------------------------------------------------------------- /sesion-7-context/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /sesion-7-context/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /sesion-7-context/public/schema.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-7-context/public/schema.PNG -------------------------------------------------------------------------------- /sesion-7-context/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .header { 6 | align-items: center; 7 | display: flex; 8 | justify-content: space-around; 9 | padding: 0.5rem 0; 10 | border-bottom: 1px solid var(--grey); 11 | } 12 | 13 | .header, 14 | .header a { 15 | transition: all 200ms linear; 16 | } 17 | 18 | .header-light { 19 | background-color: whitesmoke; 20 | } 21 | 22 | .header-dark { 23 | background-color: var(--dark); 24 | } 25 | 26 | .header-nav { 27 | align-items: center; 28 | display: flex; 29 | gap: 1rem; 30 | } 31 | 32 | .header-nav-light a:not(.active) { 33 | color: black; 34 | } 35 | 36 | .header-content { 37 | align-items: center; 38 | display: flex; 39 | gap: 1rem; 40 | } 41 | 42 | .theme-button { 43 | background-color: transparent; 44 | border: 0; 45 | cursor: pointer; 46 | font-size: 1.25rem; 47 | } 48 | 49 | .header-search { 50 | } 51 | -------------------------------------------------------------------------------- /sesion-7-context/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /sesion-7-context/src/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import Header from './components/Header'; 3 | import { ThemeContextProvider } from './context/ThemeContext'; 4 | 5 | const App = () => { 6 | return ( 7 | 8 |
    9 |
    10 |
    11 |
    12 | ); 13 | }; 14 | 15 | export default App; 16 | -------------------------------------------------------------------------------- /sesion-7-context/src/components/Header.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { Link, NavLink } from 'react-router-dom'; 3 | import { ThemeContext } from '../context/ThemeContext'; 4 | import ThemeSwitcher from './ThemeSwitcher'; 5 | 6 | const Header = () => { 7 | const { theme } = useContext(ThemeContext); 8 | 9 | return ( 10 |
    11 | 23 | 24 |
    25 | 26 | 27 |
    28 | 29 | 30 |
    31 |
    32 |
    33 | ); 34 | }; 35 | 36 | export default Header; 37 | -------------------------------------------------------------------------------- /sesion-7-context/src/components/ThemeSwitcher.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { ThemeContext } from '../context/ThemeContext'; 3 | 4 | const ThemeSwitcher = () => { 5 | // Accedemos a los valores de ThemeContext 6 | const { theme, toggleTheme } = useContext(ThemeContext); 7 | 8 | return ( 9 | 12 | ); 13 | }; 14 | 15 | export default ThemeSwitcher; 16 | -------------------------------------------------------------------------------- /sesion-7-context/src/context/ThemeContext.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | createContext, 3 | useCallback, 4 | useEffect, 5 | useLayoutEffect, 6 | useState, 7 | } from 'react'; 8 | 9 | export type ThemeContextType = { 10 | theme: 'light' | 'dark'; 11 | toggleTheme: () => void; 12 | }; 13 | 14 | export const ThemeContext = createContext({ 15 | theme: 'dark', 16 | toggleTheme: () => null, 17 | }); 18 | 19 | export const ThemeContextProvider = ({ 20 | children, 21 | }: { 22 | children: React.ReactNode; 23 | }) => { 24 | // Definimos el control que haremos sobre los datos del Provider 25 | const [theme, setTheme] = useState( 26 | // Inicializamos el state de forma lazy para leer localStorage primero 27 | () => (localStorage.getItem('theme') as ThemeContextType['theme']) || 'dark' 28 | ); 29 | 30 | const toggleTheme = useCallback(() => { 31 | setTheme((prevTheme) => (prevTheme === 'dark' ? 'light' : 'dark')); 32 | }, []); 33 | 34 | // Cada vez que theme cambie, lo guardo actualizado en localStorage 35 | useEffect(() => { 36 | localStorage.setItem('theme', theme); 37 | }, [theme]); 38 | 39 | return ( 40 | 46 | {children} 47 | 48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /sesion-7-context/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap'); 2 | 3 | :root { 4 | --black: #000; 5 | --green: #2cb67d; 6 | --grey: #94a1b2; 7 | --purple: #7f5af0; 8 | --red: #a31621; 9 | --dark: #16161a; 10 | } 11 | 12 | body { 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-font-smoothing: antialiased; 15 | background-color: var(--dark); 16 | color: white; 17 | font-family: 'Roboto', sans-serif; 18 | margin: 0; 19 | min-height: 100vh; 20 | } 21 | 22 | * { 23 | box-sizing: border-box; 24 | } 25 | 26 | a { 27 | color: white; 28 | } 29 | a.active { 30 | color: var(--green); 31 | } 32 | 33 | ul { 34 | list-style-type: none; 35 | padding-left: 0; 36 | } 37 | -------------------------------------------------------------------------------- /sesion-7-context/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { BrowserRouter } from 'react-router-dom'; 4 | import './index.css'; 5 | import App from './App'; 6 | 7 | ReactDOM.render( 8 | 9 | 10 | 11 | 12 | , 13 | document.getElementById('root') 14 | ); 15 | -------------------------------------------------------------------------------- /sesion-7-context/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /sesion-7-context/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /sesion-8-forms/.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 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /sesion-8-forms/README.md: -------------------------------------------------------------------------------- 1 | # React Hook Form 2 | 3 | Link a la documentación https://react-hook-form.com/ 4 | -------------------------------------------------------------------------------- /sesion-8-forms/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sesion-8-forms", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.2", 7 | "@testing-library/react": "^12.1.4", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.4.1", 10 | "@types/node": "^16.11.26", 11 | "@types/react": "^17.0.40", 12 | "@types/react-dom": "^17.0.13", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-hook-form": "^7.28.0", 16 | "react-scripts": "5.0.0", 17 | "typescript": "^4.6.2", 18 | "web-vitals": "^2.1.4" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /sesion-8-forms/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-8-forms/public/favicon.ico -------------------------------------------------------------------------------- /sesion-8-forms/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | MiniCodeLab React Forms 12 | 13 | 14 | 15 |
    16 | 17 | 18 | -------------------------------------------------------------------------------- /sesion-8-forms/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-8-forms/public/logo192.png -------------------------------------------------------------------------------- /sesion-8-forms/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiniCodeLab/taller-react-desde-cero/6816c4d8971f340c1e02bb6010f7ea8f905d584a/sesion-8-forms/public/logo512.png -------------------------------------------------------------------------------- /sesion-8-forms/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /sesion-8-forms/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /sesion-8-forms/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | form { 6 | } 7 | 8 | fieldset { 9 | border: 0; 10 | display: flex; 11 | align-items: center; 12 | gap: 1rem; 13 | justify-content: center; 14 | } 15 | 16 | .error-message { 17 | color: red; 18 | } 19 | -------------------------------------------------------------------------------- /sesion-8-forms/src/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import HookForm from './components/HookForm'; 3 | // import HookFormRefactored from './components/HookFormRefactored'; 4 | // import RefForm from './components/RefForm'; 5 | // import StateForm from './components/StateForm'; 6 | 7 | const App = () => { 8 | return ( 9 |
    10 |

    MiniCodeLab - Forms

    11 | {/* */} 12 | {/* */} 13 | {/* */} 14 | 15 |
    16 | ); 17 | }; 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /sesion-8-forms/src/components/ErrorMessage.tsx: -------------------------------------------------------------------------------- 1 | export type Props = { 2 | message: string; 3 | }; 4 | 5 | export const ErrorMessage = ({ message }: Props) => { 6 | return

    {message}

    ; 7 | }; 8 | -------------------------------------------------------------------------------- /sesion-8-forms/src/components/HookForm.tsx: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | import { Form } from '../types'; 3 | import { ErrorMessage } from './ErrorMessage'; 4 | 5 | const HookForm = () => { 6 | const { 7 | handleSubmit, 8 | register, 9 | formState: { errors }, 10 | } = useForm
    (); 11 | 12 | const handleFormSubmit = (values: Form) => { 13 | console.log(values); 14 | }; 15 | 16 | console.log(errors); 17 | 18 | return ( 19 | 20 |
    21 | 22 | 36 | 37 | {errors.username?.message && ( 38 | 39 | )} 40 |
    41 | 42 |
    43 | 44 | 1234AbCd 57 | validate: { 58 | hasValidFormat: (password) => { 59 | if (password.includes(' ')) { 60 | return 'No puede tener espacios'; 61 | } 62 | 63 | if ( 64 | /[a-z]/g.test(password) && // Comprobamos que tiene minúscula 65 | /[A-Z]/g.test(password) && // Comprobamos que tiene mayúscula 66 | /[0-9]/g.test(password) // Comprobamos que tiene número 67 | ) { 68 | return true; 69 | } 70 | 71 | return 'Este campo debe tener al menos 1 mayuscula, 1 minuscula y 1 numero como mínimo'; 72 | }, 73 | }, 74 | })} 75 | /> 76 | 77 | {errors.password ? ( 78 | 79 | ) : null} 80 |
    81 | 82 | 83 |
    84 | ); 85 | }; 86 | 87 | export default HookForm; 88 | -------------------------------------------------------------------------------- /sesion-8-forms/src/components/HookFormRefactored.tsx: -------------------------------------------------------------------------------- 1 | import { useForm } from 'react-hook-form'; 2 | import { Form } from '../types'; 3 | import { Input } from './Input'; 4 | 5 | const HookFormRefactored = () => { 6 | const { 7 | handleSubmit, 8 | register, 9 | formState: { errors }, 10 | } = useForm
    (); 11 | 12 | const handleFormSubmit = (values: Form) => { 13 | console.log(values); 14 | }; 15 | 16 | return ( 17 | 18 | 36 | 37 | 1234AbCd 53 | validate: { 54 | hasValidFormat: (password: string) => { 55 | if (password.includes(' ')) { 56 | return 'No puede tener espacios'; 57 | } 58 | 59 | if ( 60 | /[a-z]/g.test(password) && // Comprobamos que tiene minúscula 61 | /[A-Z]/g.test(password) && // Comprobamos que tiene mayúscula 62 | /[0-9]/g.test(password) // Comprobamos que tiene número 63 | ) { 64 | return true; 65 | } 66 | 67 | return 'Este campo debe tener al menos 1 mayuscula, 1 minuscula y 1 numero como mínimo'; 68 | }, 69 | }, 70 | }} 71 | errorMessage={errors.password?.message} 72 | /> 73 | 74 | 75 |
    76 | ); 77 | }; 78 | 79 | export default HookFormRefactored; 80 | -------------------------------------------------------------------------------- /sesion-8-forms/src/components/Input.tsx: -------------------------------------------------------------------------------- 1 | import { UseFormRegister } from 'react-hook-form'; 2 | import { Form } from '../types'; 3 | import { ErrorMessage } from './ErrorMessage'; 4 | 5 | type Props = { 6 | type: 'text' | 'password'; 7 | id: string; 8 | label: string; 9 | name: keyof Form; 10 | register: UseFormRegister
    ; 11 | errorMessage?: string; 12 | validations: Record; 13 | }; 14 | 15 | export const Input = ({ 16 | id, 17 | name, 18 | type, 19 | label, 20 | register, 21 | errorMessage, 22 | validations, 23 | }: Props) => { 24 | return ( 25 |
    26 | 27 | 28 | 29 | {errorMessage && } 30 |
    31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /sesion-8-forms/src/components/RefForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef } from 'react'; 2 | 3 | const RefForm = () => { 4 | const usernameRef = useRef(null); 5 | const passwordRef = useRef(null); 6 | 7 | const handleFormSubmit = (e: React.FormEvent) => { 8 | e.preventDefault(); 9 | 10 | const username = usernameRef.current?.value; 11 | const password = passwordRef.current?.value; 12 | 13 | if (!username || !password) { 14 | // Cambiaremos un state de error 15 | // setError('Los dos campos son requeridos') 16 | return; 17 | } 18 | 19 | const payload = { 20 | username, 21 | password, 22 | }; 23 | 24 | console.log(payload); 25 | }; 26 | 27 | console.log('Rendering RefForm'); 28 | 29 | return ( 30 | 31 |
    32 | 33 | 34 |
    35 | 36 |
    37 | 38 | { 45 | console.log(e.target.value); 46 | }} 47 | /> 48 |
    49 | 50 | 51 | 52 | ); 53 | }; 54 | 55 | export default RefForm; 56 | -------------------------------------------------------------------------------- /sesion-8-forms/src/components/StateForm.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | const StateForm = () => { 4 | const [form, setForm] = useState({ 5 | username: '', 6 | password: '', 7 | }); 8 | 9 | const handleFormSubmit = (e: React.FormEvent) => { 10 | e.preventDefault(); 11 | 12 | console.log('El formulario vale:', form); 13 | // Consumimos nuestra API 14 | // postUser() 15 | }; 16 | 17 | console.log('Renderizando StateForm'); 18 | 19 | return ( 20 |
    21 |
    22 | 23 | { 26 | setForm((prevForm) => ({ 27 | ...prevForm, 28 | username: e.target.value, 29 | })); 30 | }} 31 | id="username" 32 | name="username" 33 | type="text" 34 | /> 35 |
    36 | 37 |
    38 | 39 | { 42 | setForm((prevForm) => ({ 43 | ...prevForm, 44 | password: e.target.value, 45 | })); 46 | }} 47 | id="password" 48 | name="password" 49 | type="password" 50 | /> 51 |
    52 | 53 | 54 |
    55 | ); 56 | }; 57 | 58 | export default StateForm; 59 | -------------------------------------------------------------------------------- /sesion-8-forms/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap'); 2 | 3 | :root { 4 | --black: #000; 5 | --green: #2cb67d; 6 | --grey: #94a1b2; 7 | --purple: #7f5af0; 8 | --red: #a31621; 9 | --dark: #16161a; 10 | } 11 | 12 | body { 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-font-smoothing: antialiased; 15 | background-color: var(--dark); 16 | color: white; 17 | font-family: 'Roboto', sans-serif; 18 | margin: 0; 19 | min-height: 100vh; 20 | } 21 | 22 | * { 23 | box-sizing: border-box; 24 | } 25 | 26 | a { 27 | color: white; 28 | } 29 | a.active { 30 | color: var(--green); 31 | } 32 | 33 | ul { 34 | list-style-type: none; 35 | padding-left: 0; 36 | } 37 | -------------------------------------------------------------------------------- /sesion-8-forms/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById('root') 11 | ); 12 | -------------------------------------------------------------------------------- /sesion-8-forms/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /sesion-8-forms/src/types.ts: -------------------------------------------------------------------------------- 1 | export type Form = { 2 | username: string; 3 | password: string; 4 | }; 5 | -------------------------------------------------------------------------------- /sesion-8-forms/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------