├── graphql ├── server │ ├── README.md │ ├── .babelrc │ ├── src │ │ ├── index.js │ │ └── users │ │ │ ├── resolvers.js │ │ │ ├── schema.graphql │ │ │ └── domain.js │ ├── .prettierrc │ ├── .eslintrc.json │ └── package.json ├── apollo.md ├── mutations.md ├── intro.md └── queries.md ├── images ├── app.png ├── index.png ├── header.png ├── public.png ├── vercel.png ├── fictizia.jpeg ├── lifecycle.png ├── boilerplate.png └── stackoverflow_survey.png ├── modulo2 ├── TextColor.js ├── AddTaskButton.js ├── ColorPicker.js ├── RemoveTaskButton.js ├── InputText.js ├── RenderListEx1.js ├── FormsEx1.js ├── products.constants.js ├── ProductsList.js ├── RenderListEx2.js ├── List.js ├── helpers.js ├── ListItem.js ├── FormsEx2.js ├── ColorContainer.js ├── ConditionalRenderEx1.js ├── FormsEx4.js ├── state2.md ├── thinkingReact.md ├── ListContainer.js ├── composition.md ├── render.md ├── FormEx5.js ├── conditionalRender.md ├── ShopPage.js ├── jsx.md ├── props.md ├── lifecycle.md ├── renderList.md ├── events.md ├── state.md └── forms.md ├── environment.md ├── modulo4 ├── material-ui.md ├── react-router.md └── styled-components.md ├── modulo3 ├── lazySuspense.md ├── code-splitting.md ├── errorboundaries.md ├── hoc.md ├── refs.md ├── accessibility.md ├── proptypes.md ├── hooks.md └── context.md ├── .gitignore ├── modulo5 └── redux.md ├── introduccion.md ├── testing ├── unit.md ├── testingLibrary.md ├── jest.md └── enzyme.md ├── README.md └── modulo6 ├── nextjs.md └── crypto.json /graphql/server/README.md: -------------------------------------------------------------------------------- 1 | # GraphQL Server 2 | 3 | ## Setup 4 | 5 | ``` 6 | yarn 7 | yarn start 8 | ``` -------------------------------------------------------------------------------- /images/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zamarrowski/Curso-React-Testing-GraphQL/HEAD/images/app.png -------------------------------------------------------------------------------- /images/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zamarrowski/Curso-React-Testing-GraphQL/HEAD/images/index.png -------------------------------------------------------------------------------- /images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zamarrowski/Curso-React-Testing-GraphQL/HEAD/images/header.png -------------------------------------------------------------------------------- /images/public.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zamarrowski/Curso-React-Testing-GraphQL/HEAD/images/public.png -------------------------------------------------------------------------------- /images/vercel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zamarrowski/Curso-React-Testing-GraphQL/HEAD/images/vercel.png -------------------------------------------------------------------------------- /images/fictizia.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zamarrowski/Curso-React-Testing-GraphQL/HEAD/images/fictizia.jpeg -------------------------------------------------------------------------------- /images/lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zamarrowski/Curso-React-Testing-GraphQL/HEAD/images/lifecycle.png -------------------------------------------------------------------------------- /images/boilerplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zamarrowski/Curso-React-Testing-GraphQL/HEAD/images/boilerplate.png -------------------------------------------------------------------------------- /modulo2/TextColor.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => 4 |

texto

-------------------------------------------------------------------------------- /modulo2/AddTaskButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => -------------------------------------------------------------------------------- /images/stackoverflow_survey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zamarrowski/Curso-React-Testing-GraphQL/HEAD/images/stackoverflow_survey.png -------------------------------------------------------------------------------- /modulo2/ColorPicker.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => 4 | -------------------------------------------------------------------------------- /modulo2/RemoveTaskButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => -------------------------------------------------------------------------------- /modulo2/InputText.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => -------------------------------------------------------------------------------- /graphql/server/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "corejs": 2, 5 | "useBuiltIns": "usage" 6 | }] 7 | ], 8 | "plugins": ["import-graphql"] 9 | } -------------------------------------------------------------------------------- /modulo2/RenderListEx1.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const users = ['sergio', 'victoria', 'iván', 'liviu'] 4 | 5 | export default () => 6 | -------------------------------------------------------------------------------- /modulo2/FormsEx1.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => 4 | -------------------------------------------------------------------------------- /modulo2/products.constants.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | id: 1, 4 | name: 'iPhone X', 5 | price: 800, 6 | }, 7 | { 8 | id: 2, 9 | name: 'iPhone XS', 10 | price: 900, 11 | }, 12 | { 13 | id: 3, 14 | name: 'iPhone 11', 15 | price: 1300, 16 | }, 17 | ] -------------------------------------------------------------------------------- /modulo2/ProductsList.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => { 4 | return ( 5 | props.products.map(product => ( 6 |
7 | {product.name} - {product.price} 8 | {props.button(product)} 9 |
10 | )) 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /modulo2/RenderListEx2.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const users = [{ name: 'Sergio', age: 28 }, { name: 'Victoria', age: 27 }, { name: 'Iván', age: 30 }, { name: 'Liviu', age: 26 }] 4 | 5 | 6 | export default () => 7 | -------------------------------------------------------------------------------- /modulo2/List.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ListItem from './ListItem' 3 | 4 | export default props => 5 | -------------------------------------------------------------------------------- /modulo2/helpers.js: -------------------------------------------------------------------------------- 1 | export const getHobbies = (hobby, checked, hobbies) => { 2 | if (checked) { 3 | return [...hobbies, hobby] 4 | } else { 5 | return hobbies.filter(h => h !== hobby) 6 | } 7 | } 8 | 9 | //[{price: 500}, {price: 250}] 10 | export const getPrice = products => { 11 | return products.reduce((prev, current) => prev + current.price, 0) 12 | } -------------------------------------------------------------------------------- /environment.md: -------------------------------------------------------------------------------- 1 | # Entorno de desarrollo 2 | 3 | Personalmente yo uso [Visual Studio Code](https://code.visualstudio.com/) para desarrollar. 4 | 5 | Para poder trabajar en clase necesitaremos: 6 | 7 | * [NodeJS](https://nodejs.org/es/download/) 8 | * [npm](https://www.npmjs.com/) 9 | * [create-react-app](https://create-react-app.dev/) 10 | 11 | 12 | [<- Volver al índice](./README.md) 13 | -------------------------------------------------------------------------------- /modulo2/ListItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import RemoveTaskButton from './RemoveTaskButton' 3 | 4 | export default props => 5 | <> 6 |
  • 7 | props.onEdit(props.task.id, e.target.value)} 11 | /> 12 |
  • 13 | props.onRemove(props.task.id)} /> 14 | -------------------------------------------------------------------------------- /graphql/server/src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import { ApolloServer } from 'apollo-server-express' 3 | 4 | import { typeDefs } from './users/schema.graphql' 5 | import { resolvers } from './users/resolvers' 6 | 7 | const app = express() 8 | 9 | const server = new ApolloServer({ 10 | typeDefs, 11 | resolvers, 12 | }) 13 | 14 | server.applyMiddleware({ app }) 15 | 16 | app.listen(8000, () => console.log('running in 8000')) 17 | -------------------------------------------------------------------------------- /graphql/server/src/users/resolvers.js: -------------------------------------------------------------------------------- 1 | import { getUsers, getPosts, getComments, getUser, changeUsername } from './domain' 2 | 3 | export const resolvers = { 4 | Query: { 5 | getUser: (_, { userId }) => getUser(userId), 6 | getUsers: () => getUsers(), 7 | getPosts: (_, { userId }) => getPosts(userId), 8 | getComments: (_, { postId }) => getComments(postId), 9 | }, 10 | Mutation: { 11 | changeUsername: (_, { userId, newUsername }) => changeUsername(userId, newUsername) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /graphql/server/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": true, 4 | "htmlWhitespaceSensitivity": "css", 5 | "insertPragma": false, 6 | "jsxBracketSameLine": false, 7 | "jsxSingleQuote": false, 8 | "printWidth": 120, 9 | "proseWrap": "preserve", 10 | "quoteProps": "as-needed", 11 | "requirePragma": false, 12 | "semi": false, 13 | "singleQuote": true, 14 | "tabWidth": 2, 15 | "trailingComma": "es5", 16 | "useTabs": false, 17 | "vueIndentScriptAndStyle": false 18 | } -------------------------------------------------------------------------------- /modulo2/FormsEx2.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const validText = 'A tope con React' 4 | 5 | export default class InputError extends React.Component { 6 | 7 | state = { 8 | text: '' 9 | } 10 | 11 | render() { 12 | return( 13 | <> 14 | this.setState({ text: e.target.value })} 18 | /> 19 | {this.state.text !== validText &&

    El texto introducido no mola

    } 20 | 21 | ) 22 | } 23 | } -------------------------------------------------------------------------------- /modulo2/ColorContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ColorPicker from './ColorPicker' 3 | import TextColor from './TextColor' 4 | 5 | class ColorContainer extends React.Component { 6 | 7 | state = { 8 | color: '#cbcbcb' 9 | } 10 | 11 | changeColor = e => { 12 | this.setState({ 13 | color: e.target.value 14 | }) 15 | } 16 | 17 | render(){ 18 | return( 19 | <> 20 | 21 | 22 | 23 | ) 24 | } 25 | } 26 | 27 | export default ColorContainer -------------------------------------------------------------------------------- /graphql/server/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "parser": "babel-eslint", 7 | "extends": ["eslint:recommended", "plugin:prettier/recommended"], 8 | "globals": { 9 | "Atomics": "readonly", 10 | "SharedArrayBuffer": "readonly", 11 | "it": "readonly", 12 | "test": "readonly", 13 | "expect": "readonly" 14 | }, 15 | "parserOptions": { 16 | "ecmaFeatures": { 17 | "jsx": true 18 | }, 19 | "ecmaVersion": 2018, 20 | "sourceType": "module" 21 | }, 22 | "plugins": [], 23 | "rules": {}, 24 | "settings": {} 25 | } -------------------------------------------------------------------------------- /modulo2/ConditionalRenderEx1.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | class ConditionalRenderEx1 extends React.Component { 4 | 5 | state = { 6 | isLogged: false 7 | } 8 | 9 | doLogin = () => { 10 | this.setState({ isLogged: true }) 11 | } 12 | 13 | render() { 14 | return ( 15 | <> 16 | {this.state.isLogged ?

    Esta pagina solo puedo verla por que estoy logueado

    :

    Inicia sesión para ver contenido privado

    } 17 | {!this.state.isLogged && } 18 | 19 | ) 20 | } 21 | 22 | } 23 | 24 | export default ConditionalRenderEx1 -------------------------------------------------------------------------------- /graphql/server/src/users/schema.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | getUser(userId: ID!) : User 3 | getUsers : [User] 4 | getPosts(userId: ID!) : [Post] 5 | getComments(postId: ID!) : [Comment] 6 | } 7 | 8 | type Mutation { 9 | changeUsername(userId: ID!, newUsername: String!) : User 10 | } 11 | 12 | type User { 13 | id: ID 14 | first_name: String 15 | last_name: String 16 | email: String 17 | username: String 18 | } 19 | 20 | type Post { 21 | id: ID 22 | content: String 23 | userId: ID 24 | likes: Int 25 | } 26 | 27 | type Comment { 28 | id: ID 29 | content: String 30 | postId: ID 31 | likes: Int 32 | } -------------------------------------------------------------------------------- /modulo4/material-ui.md: -------------------------------------------------------------------------------- 1 | # Material-UI 2 | 3 | ## Instalación 4 | 5 | ``` 6 | npm install @material-ui/core 7 | ``` 8 | 9 | ## Documentación 10 | 11 | Tiene una amplia documentación de cada componente que contiene la librería la podemos encontrar accediendo [aquí](https://material-ui.com) en el apartado **Components**. 12 | 13 | 14 | ## Ejercicios 15 | 16 | 1. Sustituir el `Select.js` por el de material-ui 17 | 2. Sustituir el `Loading.js` por el spinner de material-ui 18 | ```js 19 | export default props => props.show ? props.children : 'Loading...' 20 | ``` 21 | 3. En el ejercicio de react-router usar el drawer de material-ui para la selección de la página -------------------------------------------------------------------------------- /modulo2/FormsEx4.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export default props => { 4 | 5 | let texto = '' 6 | let points = 0 7 | 8 | if (!props.password) { 9 | texto = 'no hay contraseña' 10 | } 11 | 12 | if (props.password.length >= 8) points++ 13 | if (/[0-9]/.test(props.password)) points++ 14 | if (/[A-Z]/.test(props.password)) points++ 15 | if (/[$%&/()+-]/.test(props.password)) points++ 16 | 17 | if (points === 0 && props.password) texto = 'contraseña muy debil' 18 | if (points === 1) texto = 'contraseña débil' 19 | if (points > 1 && points <= 3) texto = 'contraseña media' 20 | if (points >= 4) texto = 'contraseña fuerte' 21 | 22 | return ( 23 |

    {texto}

    24 | ) 25 | } -------------------------------------------------------------------------------- /modulo2/state2.md: -------------------------------------------------------------------------------- 1 | ## Levantando el estado 2 | 3 | Usualmente, muchos componentes necesitan reflejar el mismo cambio en los datos. Recomendamos elevar el estado compartido al ancestro común más cercano. Esto lo hemos podido practicar en ejercicios anteriores pero es importantísimo el conocer como compartir un estado entre varios componentes. 4 | 5 | Para ello vamos a practicar con un ejemplo sobre esto: 6 | 7 | ## Ejercicios 8 | 9 | Se trata de tener un color picker el cual cambiará el color de un texto. Por lo tanto tendremos tres componentes: 10 | 11 | * Componente padre 12 | * El color picker para seleccionar un color 13 | * El texto que deberá de cambiar de color segun vamos cambiando en el color picker 14 | 15 | 16 | [<- Volver al índice](./../README.md) 17 | -------------------------------------------------------------------------------- /graphql/server/src/users/domain.js: -------------------------------------------------------------------------------- 1 | import { getAllUsers, getAllPosts, getAllComments } from './data' 2 | 3 | let users = getAllUsers() 4 | const posts = getAllPosts() 5 | const comments = getAllComments() 6 | 7 | export const getUser = (userId) => users.find(u => u.id == Number(userId)) 8 | export const getUsers = () => users 9 | export const getPosts = (userId) => posts.filter(p => p.userId === Number(userId)) 10 | export const getComments = (postId) => comments.filter(c => c.postId === Number(postId)) 11 | export const changeUsername = (userId, newUsername) => { 12 | let userModified = null 13 | users = users.map(c => { 14 | if (c.id === Number(userId)) { 15 | c.username = newUsername 16 | userModified = c 17 | } 18 | return c 19 | }) 20 | return userModified 21 | } 22 | -------------------------------------------------------------------------------- /graphql/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-projects", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "nodemon --ext '.' --exec babel-node src/index.js", 8 | "test": "jest --watch" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@babel/core": "^7.9.0", 15 | "@babel/node": "^7.8.7", 16 | "@babel/preset-env": "^7.9.5", 17 | "babel-eslint": "^10.1.0", 18 | "babel-plugin-import-graphql": "^2.7.0", 19 | "core-js": "2", 20 | "eslint": "^6.8.0", 21 | "eslint-config-prettier": "^6.11.0", 22 | "eslint-plugin-prettier": "^3.1.3", 23 | "express": "^4.17.1", 24 | "jest": "^25.4.0", 25 | "nodemon": "^2.0.3", 26 | "prettier": "^2.0.5" 27 | }, 28 | "dependencies": { 29 | "apollo-server-express": "^2.12.0", 30 | "express-graphql": "^0.9.0", 31 | "graphql": "^15.0.0", 32 | "graphql-import": "^1.0.2", 33 | "graphql-tools": "^5.0.0", 34 | "mongoose": "^5.9.9" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /modulo3/lazySuspense.md: -------------------------------------------------------------------------------- 1 | # React Lazy y React Suspense 2 | 3 | React.lazy() permite definir un componente que es cargado dinámicamente. Esto ayuda a reducir el tamaño del bundle para demorar los componentes de carga que no son usados durante la renderización inicial. 4 | 5 | ```js 6 | const RedditPosts = React.lazy(() => import('./RedditPosts')) 7 | ``` 8 | 9 | Ten en cuenta que renderizar componentes lazy requiere que haya un componente `````` más alto en el árbol de renderización. Así es como se especifica un indicador de carga. 10 | 11 | React.Suspense permite especificar el indicador de carga en caso de que algunos componentes en el árbol más abajo de él todavía no estén listos para renderizarse. Hoy en día, los componentes de carga diferida son el único caso compatible con ``````: 12 | 13 | 14 | ```js 15 | Loading...}> 16 | 17 | 18 | ``` 19 | 20 | 21 | 1. Importar usando lazy el ejercicio número 4 de la sección de los Hooks. 22 | 23 | 24 | [<- Volver al índice](./../README.md) 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | -------------------------------------------------------------------------------- /graphql/apollo.md: -------------------------------------------------------------------------------- 1 | # Apollo 2 | 3 | ![apollo](https://cdn.worldvectorlogo.com/logos/apollo-graphql-1.svg) 4 | 5 | ## ¿Qué es Apollo? 6 | 7 | Es una librería para controlar el estado que nos permite ejecutar queries, mutations, controlar caché y todo de una manera muy fácil en React. 8 | 9 | ``` 10 | yarn add @apollo/client graphql 11 | ``` 12 | 13 | ## Como conectar React al servidor de GraphQL 14 | 15 | Primero debemos de crear un cliente para conectarnos al servidor: 16 | 17 | ```js 18 | import { ApolloClient, InMemoryCache } from '@apollo/client' 19 | 20 | const client = new ApolloClient({ 21 | uri: 'http://localhost:8000/graphql', 22 | cache: new InMemoryCache() 23 | }) 24 | ``` 25 | 26 | Una vez que tenemos ese cliente, Apollo nos ofrece un provider para React al cual le pasaremos por props ese cliente: 27 | 28 | ```js 29 | 30 | import { ApolloProvider } from '@apollo/client' 31 | 32 | ReactDOM.render( 33 | 34 | 35 | 36 | 37 | , 38 | document.getElementById('root') 39 | ); 40 | 41 | ``` 42 | 43 | ## Ejercicios 44 | 45 | 1. Crear el cliente de Apollo y añadir el provider a nuestra aplicación 46 | 47 | [<- Volver al índice](./../README.md) -------------------------------------------------------------------------------- /modulo2/thinkingReact.md: -------------------------------------------------------------------------------- 1 | # Pensando en React 2 | 3 | Durante lo que llevamos de curso hemos hecho varios ejercicios en los que intentabamos hacer componentes que se pudieran reutilizar. 4 | 5 | Una de las grandes ventaja de React es cómo te hace pensar acerca de la aplicación mientras la construyes. Hay unos pasos que deberíamos seguir para conseguir nuestro objetivo: 6 | 7 | 1. Divide la interfaz de usuario en una jerarquía de componentes 8 | 2. Crea una versión estática en React 9 | 3. Identificar la versión mínima (pero completa) del estado de tu interfaz de usuario 10 | 4. Identificar donde debe vivir tu estado 11 | 5. Agregar flujo de datos inverso 12 | 13 | ## Ejercicios 14 | 15 | 1. Para practicar los pasos anteriormente citados vamos a hacer un carrito de la compra sencillo. Tendremos unos productos fuera del carrito y podremos: 16 | * Añadir productos 17 | * Borrar productos 18 | * Mostrar el precio unico de cada producto 19 | * Mostrar el precio total del carrito 20 | * Modificar las unidades de producto en el carrito 21 | * Canjear un codigo de descuento: `SAVE10` que restará un 10% al precio total 22 | * Si es un código erroneo mostrar un error 23 | * **BONUS:** Cuando cierre el navegador y vuelva a entrar debería de mostrar el carrito como estaba la última vez. 24 | 25 | 26 | [<- Volver al índice](./../README.md) 27 | -------------------------------------------------------------------------------- /modulo2/ListContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import List from './List' 3 | import InputText from './InputText' 4 | import AddTaskButton from './AddTaskButton' 5 | 6 | class ListContainer extends React.Component { 7 | 8 | state = { 9 | tasks: [{ 10 | id: 10001, 11 | title: 'Comprar el pan' 12 | }, { 13 | id: 10002, 14 | title: 'Sacar a India' 15 | }], 16 | newTaskText: '' 17 | } 18 | 19 | handleNewTaskText = e => { 20 | this.setState({ newTaskText: e.target.value }) 21 | } 22 | 23 | addTask = () => { 24 | this.setState({ 25 | tasks: [...this.state.tasks, { id: Math.random(), title: this.state.newTaskText }], 26 | newTaskText: '', 27 | }) 28 | } 29 | 30 | removeTask = taskId => { 31 | let newTasks = this.state.tasks.filter(task => task.id !== taskId) 32 | this.setState({ tasks: newTasks }) 33 | } 34 | 35 | editTask = (taskId, value) => { 36 | const newTasks = this.state.tasks.map(task => { 37 | if (task.id === taskId) task.title = value 38 | return task 39 | }) 40 | this.setState({ tasks: newTasks }) 41 | } 42 | 43 | render() { 44 | return ( 45 | <> 46 | 47 | 48 | 49 | 50 | ) 51 | } 52 | 53 | } 54 | 55 | export default ListContainer -------------------------------------------------------------------------------- /modulo5/redux.md: -------------------------------------------------------------------------------- 1 | # Redux 2 | 3 | ![flow](https://github.com/springload/react-redux-exercise/blob/master/readme/redux1.jpg?raw=true) 4 | 5 | Os dejo por [aquí](https://es.slideshare.net/SergioZamarroSnchez/redux-reactadalab) las diapositivas. 6 | 7 | ``` 8 | npm i --save redux react-redux 9 | ``` 10 | 11 | ## Ejercicios: 12 | 13 | Se trata de hacer un ejercicio final donde podamos practicar todo o casi todo lo que hemos visto. He preparado varias APIs para ayudarnos. El objetivo es hacer una especie de Instagram donde usemos Redux, styled-components y react-router. El estilo de la página es lo menos importante pero nos apoyaremos en styled-components cuando sea necesario: 14 | 15 | 1. La primera página al entrar debe ser un formulario de login. Para avanzar a la siguiente pantalla (/posts) debe introducir como datos: 16 | * username: react 17 | * password: fictizia 18 | 19 | 2. La página /posts va a tirar de la API: http://www.mocky.io/v2/5db348a0300000650057b5e3 20 | * Pintar los posts y quien lo ha publicado 21 | * Mostrar los likes 22 | * Poder dar like a una foto y que aumente el marcador 23 | * Mostrar los comentarios y el usuario que lo ha publicado 24 | 25 | 3. Página de mi perfil (/account) 26 | * En esta página vamos a mostrar los datos con los que iniciamos sesión 27 | 28 | 4. Página de perfil de usuario (/profile/:id) va a tirar de la API: http://www.mocky.io/v2/5db34985300000650057b5e8 29 | * Mostrar los datos que devuelve la API 30 | 31 | 32 | [<- Volver al índice](./../README.md) 33 | -------------------------------------------------------------------------------- /introduccion.md: -------------------------------------------------------------------------------- 1 | # Introducción 2 | 3 | ## Quien soy 4 | 5 | Soy Sergio Zamarro trabajo en BBVA Next Technologies en la parte de seguridad informática. Estoy especializado en Javascript pero trabajo tanto con Python, Golang, Docker y Kubernetes. 6 | 7 | Podéis leerme en Twitter: https://twitter.com/zamarrowski 8 | 9 | Podéis leer articulos de programación que he escrito en Medium: https://medium.com/zamarrowski 10 | 11 | Podéis ver mis repositorios en Github: https://github.com/zamarrowski 12 | 13 | ## Objetivo del curso 14 | 15 | El objetivo del curso es aprender a desarrollar aplicaciones con React y apoyandonos en Redux para poder conocer lo máximo posible su entorno. Aprenderemos desde lo más básico de React, después introduciremos Redux y veremos algunas librerías muy interesantes que nos vendrán muy bien para desarrollar aplicaciones. 16 | 17 | ## Metodología de trabajo 18 | 19 | Habrá ciertas partes de teoria explicando el temario y normalmente haremos ejercicios prácticos para afianzar lo aprendido. Después los corregiremos y trabajaremos sobre ellos para ver en que podemos mejorar. 20 | 21 | # Por que aprender React 22 | 23 | React es usado por empresas muy importantes como puede ser Facebook, Netflix, Paypal o Microsoft. La sencillez para desarrollar componentes y la gran comunidad que hay hace que sea, para mi, la mejor opción si quieres desarrollar una interfaz de manera eficiente y rapida. Por otro lado: 24 | 25 | ![stackoverflowsurvey](./images/stackoverflow_survey.png) 26 | 27 | [<- Volver al índice](./README.md) 28 | -------------------------------------------------------------------------------- /modulo2/composition.md: -------------------------------------------------------------------------------- 1 | ## Herencia vs Composicion 2 | 3 | React tiene un potente modelo de composición, y recomendamos usar composición en lugar de herencia para reutilizar código entre componentes. 4 | 5 | En esta sección consideraremos algunos problemas en los que los desarrolladores nuevos en React a menudo emplean herencia, y mostraremos cómo los podemos resolver con composición. 6 | 7 | ## Contención 8 | 9 | Algunos componentes no conocen sus hijos de antemano. Esto es especialmente común para componentes como Sidebar o Dialog que representan “cajas” genéricas. 10 | 11 | Recomendamos que estos componentes usen la prop especial children para pasar elementos hijos directamente en su resultado: 12 | 13 | ```js 14 | function FancyBorder(props) { 15 | return ( 16 |
    17 | {props.children} 18 |
    19 | ); 20 | } 21 | ``` 22 | 23 | ## Especialización 24 | 25 | A veces pensamos en componentes como “casos concretos” de otros componentes. Por ejemplo, podríamos decir que un WelcomeDialog es un caso concreto de Dialog. 26 | 27 | En React, esto también se consigue por composición, en la que un componente más “específico” renderiza uno más “genérico” y lo configura con props: 28 | 29 | ```js 30 | function Dialog(props) { 31 | return ( 32 | 33 |

    34 | {props.title} 35 |

    36 |

    37 | {props.message} 38 |

    39 |
    40 | ); 41 | } 42 | 43 | function WelcomeDialog() { 44 | return ( 45 | 48 | 49 | ); 50 | } 51 | ``` 52 | 53 | 54 | 55 | 56 | [<- Volver al índice](./../README.md) 57 | -------------------------------------------------------------------------------- /graphql/mutations.md: -------------------------------------------------------------------------------- 1 | # Mutations 2 | 3 | ## Ejecutando una mutation 4 | 5 | Apollo nos da un hook llamado `useMutation` al cual se le puede pasar una mutation y nos devuelve una array con dos elementos: 6 | 7 | * una función que ejecuta la query y recibe un objeto por el cual le podemos pasar variables a la query 8 | * un objeto con la propiedad `data` 9 | 10 | ## Escribiendo una mutation 11 | 12 | Lo primero que tenemos que hacer es escribir una mutation para poder pasársela a `useMutation`. 13 | 14 | ```js 15 | import { gql } from '@apollo/client' 16 | 17 | export const CHANGE_USERNAME = gql` 18 | mutation changeUsername($userId: ID!, $newUsername: String!) { 19 | changeUsername(userId: $userId, newUsername: $newUsername) { 20 | username 21 | } 22 | } 23 | ` 24 | ``` 25 | 26 | ## Usando `useMutation` 27 | 28 | Una vez que tenemos creada nuestra primera query podemos pasársela a `useMutation`: 29 | 30 | ```js 31 | import React from 'react'; 32 | import './App.css'; 33 | import { useQuery, useMutation } from '@apollo/client'; 34 | import { GET_USERS, CHANGE_USERNAME } from './queries' 35 | 36 | function App() { 37 | 38 | const { loading, error, data, refetch } = useQuery(GET_USERS) 39 | const [ changeUsername ] = useMutation(CHANGE_USERNAME) 40 | 41 | if (loading) return 'Loading...' 42 | if (error) return `Ops! Something went wrong!` 43 | 44 | const onChangeUsername = (userId, newUsername) => { 45 | changeUsername({ variables: { userId, newUsername } }) 46 | refetch() 47 | } 48 | 49 | return ( 50 |
    51 | {data && data.getUsers.map(u => ( 52 |

    onChangeUsername(u.id, 'zamarrowski')}>{u.username}

    53 | ))} 54 |
    55 | ); 56 | } 57 | 58 | export default App; 59 | 60 | ``` 61 | 62 | ## Ejercicios 63 | 64 | 1. Siguiendo la aplicación anterior permitir cambiar el nombre de usuario desde /users/:id 65 | 66 | [<- Volver al índice](./../README.md) -------------------------------------------------------------------------------- /testing/unit.md: -------------------------------------------------------------------------------- 1 | # Unit testing y TDD 2 | 3 | ![tdd](https://miro.medium.com/proxy/0*R1JmZVWM_H_VhQgy.jpg) 4 | 5 | Es buena práctica separar la lógica de nuestra aplicación del componente de React. Esto nos permite testear de manera más fácil y conseguimos una mayor independencia de React. 6 | 7 | 8 | ## Ejercicios 9 | 10 | 1. Dado el siguiente código debemos: 11 | * Las funciones que tenemos dentro de la clase que no se han implementado sacarlas a un fichero aparte 12 | * Escribir los tests para cada función 13 | * Implementar la función 14 | * Integrarlo con nuestro componente 15 | * Refactor 16 | * EXTRA: añadir un campo de texto que permita filtrar los productos que existen por nombre 17 | 18 | ```js 19 | import React from 'react' 20 | 21 | export default class ProductsPage extends React.Component { 22 | 23 | state = { 24 | products: [ 25 | { 26 | id: 1, 27 | name: 'Chachopo', 28 | price: 30, 29 | }, 30 | { 31 | id: 3, 32 | name: 'Navajas', 33 | price: 25, 34 | }, 35 | { 36 | id: 2, 37 | name: 'Chorizo a la sidra', 38 | price: 15, 39 | } 40 | ] 41 | } 42 | 43 | changeOrderByPrice = () => { 44 | //Should order all products by price in descending order 45 | } 46 | 47 | getPriceColor = price => { 48 | // price > 25 should return red 49 | // price > 15 and price <= 25 should return orange 50 | // In any other case return green 51 | } 52 | 53 | render() { 54 | return ( 55 | <> 56 |

    Products

    57 | 58 | {this.state.products.map(product => ( 59 |
    60 | {product.name} - {product.price} 61 |
    62 | ))} 63 | 64 | ) 65 | } 66 | } 67 | ``` 68 | 69 | [<- Volver al índice](./../README.md) -------------------------------------------------------------------------------- /modulo2/render.md: -------------------------------------------------------------------------------- 1 | # Renderizando elementos 2 | 3 | Una vez que hemos visto un poco cómo funciona JSX es hora de mancharnos las manos y renderizar nuestros propios componentes. Para ello nos vamos a ayudar de la librería `create-react-app` que deberíamos tener instalada. 4 | 5 | `create-react-app` nos ayuda a iniciar un proyecto en React dandonos un pequeño boilerplate y configurandonos nuestro proyecto para que, en principio, no tengamos que preocuparnos de configuraciones. 6 | 7 | Para iniciar un proyecto con `create-react-app` es tan fácil como seguir los siguientes pasos: 8 | 9 | ``` 10 | $ create-react-app render-components 11 | $ cd render-components 12 | $ yarn start 13 | ``` 14 | 15 | Esto nos creará una estructura como esta: 16 | 17 | ![estructura](./../images/boilerplate.png) 18 | 19 | 20 | Nos centraremos en el fichero `App.js`: 21 | 22 | ![app](./../images/app.png) 23 | 24 | App es el componente principal de la aplicación y se usa en el `index.js`: 25 | 26 | ![index](./../images/index.png) 27 | 28 | que a su vez React hace que se renderize en la etiqueta que tiene como id: root. 29 | 30 | Esta etiqueta esta dentro de la carpeta `public/index.html`: 31 | 32 | ![public](./../images/public.png) 33 | 34 | Ejercicios: 35 | 36 | 1. Crear un componente en un fichero llamado `Greeting.js` que sea una etiqueta `h1` y muestre el texto `¡Hola! Este es mi primer componente!`. 37 | 2. Crear un componente en un fichero llamado `ShowName.js` que renderize la propiedad `name` de un objeto llamado `user` en una etiqueta `p`. 38 | 3. Crear un componente `ShowDate.js` que llame a una función que devuelva la fecha actual en una etiqutea `span`. 39 | 4. Crear un componente `ShowMessage.js` que dependiendo de si la variable `showMessage` es true o false muestre el mensaje `Ahora puedes leer esto` en una etiqueta `p`. 40 | 5. Crear un componente `ConditionalRender.js` que dependiendo de si la variable `show` es true o false muestre el componente ShowDate o no muestre nada. 41 | 42 | 43 | [<- Volver al índice](./../README.md) 44 | -------------------------------------------------------------------------------- /modulo3/code-splitting.md: -------------------------------------------------------------------------------- 1 | # División del código 2 | 3 | # Bundling 4 | 5 | La mayoría de las aplicaciones React tendrán sus archivos “empaquetados” o bundled con herramientas como Webpack, Rollup o Browserify. El bundling es el proceso de seguir los archivos importados y fusionarlos en un archivo único: un bundle o “paquete”. Este bundle se puede incluir en una página web para cargar una aplicación completa de una sola vez. 6 | 7 | Ejemplo: 8 | **App:** 9 | 10 | ```js 11 | // app.js 12 | import { add } from './math.js'; 13 | 14 | console.log(add(16, 26)); // 42 15 | ``` 16 | ```js 17 | // math.js 18 | export function add(a, b) { 19 | return a + b; 20 | } 21 | ``` 22 | **Bundle:** 23 | ```js 24 | function add(a, b) { 25 | return a + b; 26 | } 27 | 28 | console.log(add(16, 26)); // 42 29 | ``` 30 | 31 | Si usas Create React App, Next.js, Gatsby, o una herramienta similar, vas a tener una configuración de Webpack incluida para generar el bundle de tu aplicación. 32 | 33 | ## División de código 34 | El Bundling es genial, pero a medida que tu aplicación crezca, tu bundle también crecerá. Especialmente si incluyes grandes bibliotecas de terceros. Necesitas vigilar el código que incluyes en tu bundle, de manera que no lo hagas accidentalmente tan grande que tu aplicación se tome mucho tiempo en cargar. 35 | 36 | Para evitar terminar con un bundle grande, es bueno adelantarse al problema y comenzar a dividir tu bundle. División de código es una funcionalidad disponible en bundlers como Webpack y Browserify (vía factor-bundle) que puede crear múltiples bundles a ser cargados dinámicamente durante la ejecución de tu aplicación. 37 | 38 | ## import() 39 | 40 | La mejor manera de introducir división de código en tu aplicación es a través de la sintaxis de import() dinámico. 41 | 42 | **Antes:** 43 | ```js 44 | import { add } from './math'; 45 | 46 | console.log(add(16, 26)); 47 | ``` 48 | **Después:** 49 | 50 | ```js 51 | import("./math").then(math => { 52 | console.log(math.add(16, 26)); 53 | }); 54 | ``` 55 | 56 | [<- Volver al índice](./../README.md) 57 | -------------------------------------------------------------------------------- /modulo3/errorboundaries.md: -------------------------------------------------------------------------------- 1 | # Gestion de errores 2 | 3 | En el pasado, los errores de JavaScript dentro de los componentes solían corromper el estado interno de React y hacían que emitiera errores crípticos en siguientes renderizados. Estos errores siempre eran causados por un error previo en el código de aplicación, pero React no proveía una manera de gestionarlos elegantemente en componentes, y no podía recuperarse de ellos. 4 | 5 | Un error de JavaScript en una parte de la interfaz no debería romper toda la aplicación. Para resolver este problema, React 16 introduce el nuevo concepto de “límite de errores”. 6 | 7 | Los límites de errores son componentes de React que capturan errores de JavaScript en cualquier parte de su árbol de componentes hijo, registran esos errores, y muestran una interfaz de repuesto en lugar del árbol de componentes que ha fallado. Los límites de errores capturan errores durante el renderizado, en métodos del ciclo de vida, y en constructores de todo el árbol bajo ellos. 8 | 9 | 10 | ```js 11 | import React from 'react' 12 | 13 | export class ErrorBoundary extends React.Component { 14 | 15 | state = { hasError: false } 16 | 17 | static getDerivedStateFromError(error) { 18 | return { hasError: true } 19 | } 20 | 21 | componentDidCatch(error, errorInfo) { 22 | console.log(error, errorInfo) 23 | } 24 | 25 | render() { 26 | if (this.state.hasError) { 27 | return

    Something went wrong.

    28 | } 29 | 30 | return this.props.children 31 | } 32 | } 33 | ``` 34 | 35 | Luego puedes usarlo como un componente normal: 36 | 37 | ```js 38 | 39 | 40 | 41 | ``` 42 | 43 | Los límites de errores funcionan como un bloque catch{} de JavaScript, pero para componentes. Sólo los componentes de clase (class components) pueden ser límites de errores. En la práctica, la mayor parte del tiempo declararás un límite de errores una vez y lo usarás a lo largo de tu aplicación. 44 | 45 | 46 | ## Ejercicios: 47 | 48 | 1. Crear un contador que cuando el marcador sea 3 levante una excepción y la capture un componente mostrando un mensaje de error y sacando el error por consola. 49 | 50 | [<- Volver al índice](./../README.md) 51 | -------------------------------------------------------------------------------- /graphql/intro.md: -------------------------------------------------------------------------------- 1 | # GraphQL 2 | 3 | ![graphql](https://miro.medium.com/max/1000/1*Fz_DTbJptm_S7GccttSFVw.png) 4 | 5 | 6 | ## ¿Qué es GraphQL? 7 | 8 | Es una herramienta desarrollada por **Facebok** que intenta de solucionar un clásico problema que tenemos a la hora de consumir recursos de APIs REST. 9 | Ya hay muchas empresas que lo usan en producción como Facebook, Github, Pinterest, Shopify, Atlassian... 10 | 11 | ## La problemática 12 | 13 | En una API REST se expone un endpoint por recurso, esto implica que hay veces que tendremos que hacer varias llamadas hacia esa API si queremos, por ejemplo, una pantalla en la que se visualicen los datos de un usuario, sus posts y los seguidores que tiene. Para conseguir esto haríamos 3 peticiones, algo así: 14 | 15 | * /users/:id 16 | * /users/:id/posts 17 | * users/:id/followers 18 | 19 | ![rest](https://imgur.com/VRyV7Jh.png) 20 | 21 | En GraphQL podemos pedir los datos que necesitamos desde el cliente. Esto nos permite poder pedir toda la información que necesitemos en una sola petición. 22 | 23 | ![graphql](https://imgur.com/z9VKnHs.png) 24 | 25 | ## Ventajas 26 | 27 | * No se crean endpoints a medida 28 | * No nos traemos datos innecesarios 29 | * Cada aplicación puede pedir los datos que necesite para pintarse adecuadamente 30 | * Las herramientas de desarrollo en torno a GraphQL 31 | 32 | 33 | ## Playground de GraphQL 34 | 35 | Existe un repositorio llamado [GraphQL Apis](https://github.com/APIs-guru/graphql-apis) en el que podemos encontrar una lista de APIs hechas con GraphQL y probarlas. 36 | 37 | ## Ejercicios 38 | 39 | 1. Utilizando la API de [Countries](https://countries.trevorblades.com/) obtener de los países su `code` y `emoji` 40 | 2. Utilizando la API de [Countries](https://countries.trevorblades.com/) obtener el `name` y `native` del país con `code` "ES" 41 | 3. Utilizando la API de [Countries](https://countries.trevorblades.com/) obtener las capitales de todos los continentes 42 | 4. Utilizando la API de [Countries](https://countries.trevorblades.com/) obtener las capitales del continente con `code` "EU" 43 | 5. Arrancar el servidor que hay en `graphql/server` y hacer algunas consultas en `http://localhost:8000/graphql` 44 | 45 | 46 | [<- Volver al índice](./../README.md) -------------------------------------------------------------------------------- /modulo4/react-router.md: -------------------------------------------------------------------------------- 1 | # React Router 2 | 3 | React Router es una libería para gestionar rutas en aplicaciones que utilicen ReactJS. 4 | 5 | ## Intalación 6 | 7 | ``` 8 | npm install react-router-dom@6 9 | ``` 10 | 11 | ## Ejemplo básico 12 | 13 | ```js 14 | import React from 'react' 15 | import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom' 16 | 17 | export default function App() { 18 | return ( 19 | 20 |
    21 | 34 | 35 | 36 | } /> 37 | } /> 38 | } /> 39 | 40 |
    41 |
    42 | ) 43 | } 44 | 45 | function Home() { 46 | return

    Home

    47 | } 48 | 49 | function About() { 50 | return

    About

    51 | } 52 | 53 | function Users() { 54 | return

    Users

    55 | } 56 | 57 | ``` 58 | 59 | ## Recoger valores de la URL 60 | 61 | ```js 62 | 63 | 64 | 65 | ``` 66 | 67 | ```js 68 | function Users() { 69 | let {id} = useParams() 70 | return

    Users {id}

    ; 71 | } 72 | ``` 73 | 74 | ## Mostrar un 404 75 | 76 | El Switch va a renderizar la primera url que coincida, por lo que si no coincide con ninguna renderizará el 404. 77 | 78 | ```js 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | ``` 94 | 95 | ### Ejercicios: 96 | 97 | 1. Crear una miniapp que tenga las siguientes secciones: 98 | * Home (/) Mostrar un mensaje de bienvenida 99 | * Users (/users) mostrar un listado de usuarios 100 | * UserDetail (/users/:id) Mostrar el id del usuario 101 | * 404 -------------------------------------------------------------------------------- /modulo2/FormEx5.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { getHobbies } from './helpers' 3 | import Child from './Child' 4 | 5 | export default class Form extends React.Component { 6 | 7 | state = { 8 | name: '', 9 | firstName: '', 10 | description: '', 11 | gender: '', 12 | age: 0, 13 | country: '', 14 | province: '', 15 | hobbies: [] 16 | } 17 | 18 | handleChange = (e) => { 19 | let name = e.target.name 20 | let value = e.target.value 21 | 22 | if (e.target.type === 'checkbox') { 23 | value = getHobbies(e.target.value, e.target.checked, this.state.hobbies) 24 | } 25 | 26 | this.setState({ 27 | [name]: value 28 | }) 29 | 30 | } 31 | 32 | handleSubmit = e => { 33 | console.log(this.state) 34 | e.preventDefault() 35 | } 36 | 37 | holi = data => { 38 | console.log(data) 39 | } 40 | 41 | render() { 42 | return ( 43 |
    44 | 45 | 46 | 65 | ``` 66 | 67 | En React, un `