├── 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 => Add task
--------------------------------------------------------------------------------
/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 => Remove
--------------------------------------------------------------------------------
/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 |
7 | {users.map((user, key) => {user} )}
8 |
--------------------------------------------------------------------------------
/modulo2/FormsEx1.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default props =>
4 |
5 | {props.items.map(val => (
6 | {val}
7 | ))}
8 |
--------------------------------------------------------------------------------
/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 |
8 | {users.map((user, key) => {user.name} - {user.age} )}
9 |
--------------------------------------------------------------------------------
/modulo2/List.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ListItem from './ListItem'
3 |
4 | export default props =>
5 |
6 | {props.tasks.map(task => (
7 |
13 | ))}
14 |
--------------------------------------------------------------------------------
/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 && Login }
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 | 
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 | 
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 | 
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 | 
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 | Change order
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 | 
18 |
19 |
20 | Nos centraremos en el fichero `App.js`:
21 |
22 | 
23 |
24 | App es el componente principal de la aplicación y se usa en el `index.js`:
25 |
26 | 
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 | 
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 | 
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 | 
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 | 
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 |
22 |
23 |
24 | Home
25 |
26 |
27 | About
28 |
29 |
30 | Users
31 |
32 |
33 |
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 |
81 | )
82 | }
83 |
84 | }
--------------------------------------------------------------------------------
/modulo3/hoc.md:
--------------------------------------------------------------------------------
1 | ## High order components
2 |
3 | Un componente de orden superior (HOC por las siglas en inglés de higher-order component) es una técnica avanzada en React para el reuso de la lógica de componentes. Los HOCs no son parte de la API de React. Son un patrón que surge de la naturaleza composicional de React.
4 |
5 | En concreto, un componente de orden superior es una función que recibe un componente y devuelve un nuevo componente.
6 |
7 | ```js
8 | const EnhancedComponent = higherOrderComponent(WrappedComponent);
9 | ```
10 |
11 | Mientras que un componente transforma props en interfaz de usuario, un componente de orden superior transforma un componente en otro.
12 |
13 | Los HOCs son comunes en bibliotecas de terceros usadas en React, tales como connect en Redux y createFragmentContainer en Relay.
14 |
15 | ## Usa HOCs para preocupaciones transversales
16 |
17 | Los componentes son la unidad primaria de reuso de código en React. Sin embargo, encontrarás que algunos patrones no se ajustan directamente a componentes tradicionales.
18 |
19 |
20 | ```js
21 | const APIContainer = (Component, url) => {
22 | return class extends React.Component {
23 | state = {
24 | data: []
25 | }
26 |
27 | async componentDidMount() {
28 | let response = await fetch(url)
29 | let data = await response.json()
30 | this.setState({ data: data.results })
31 | }
32 |
33 | render() {
34 | return(
35 |
36 | )
37 | }
38 |
39 | }
40 | }
41 |
42 | const RedditPosts = props =>
43 |
44 | {props.data.map((el, key) => (
45 | {el.name}
46 | ))}
47 |
48 |
49 | const RedditPostsDiv = props =>
50 |
51 | {props.data.map((el, key) => (
52 |
{el.name}
53 | ))}
54 |
55 |
56 | const Reddit = APIContainer(RedditPosts, 'http://www.mocky.io/v2/5d965a7233000003cd2f9091')
57 | const RedditDiv = APIContainer(RedditPostsDiv, 'http://www.mocky.io/v2/5d965aa833000077962f9093')
58 |
59 | function App() {
60 | return (
61 | <>
62 |
63 |
64 | >
65 | );
66 | }
67 |
68 | export default App;
69 | ```
70 |
71 |
72 | ## Ejercicios:
73 |
74 | 1. Crear un HOC que haga peticiones a una api (https://jsonplaceholder.typicode.com/) y usarlo para renderizar el listado de posts como el detalle de un post.
75 |
76 |
77 | [<- Volver al índice](./../README.md)
78 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # [Curso React + Testing + GraphQL](https://github.com/zamarrowski/Curso-React-Testing-GraphQL)
2 |
3 |
4 |
5 | Aprende a programar aplicaciones web con React, una de las tecnologías JS más demandadas del sector, de la mano de reconocidos desarrolladores.
6 |
7 | # Indice
8 |
9 | 1. [Introducción](./introduccion.md)
10 | 2. [Instalando nuestro entorno de desarrollo](./environment.md)
11 | 3. Conceptos básicos de React:
12 | 1. [Introducción a JSX](./modulo2/jsx.md)
13 | 2. [Renderizando componentes](./modulo2/render.md)
14 | 3. [Componentes y propiedades](./modulo2/props.md)
15 | 3. [El estado](./modulo2/state.md)
16 | 4. [Ciclo de vida](./modulo2/lifecycle.md)
17 | 5. [Gestionando eventos en React](./modulo2/events.md)
18 | 6. [Renderizado condicional](./modulo2/conditionalRender.md)
19 | 7. [Pintando listas](./modulo2/renderList.md)
20 | 8. [Formularios](./modulo2/forms.md)
21 | 9. [Subiendo el estado](./modulo2/state2.md)
22 | 10. [Herencia vs composicion](./modulo2/composition.md)
23 | 11. [Pensando en React](./modulo2/thinkingReact.md)
24 | 4. Testing en React:
25 | 1. [Jest](./testing/jest.md)
26 | 2. [Unit Test](./testing/unit.md)
27 | 3. [Enzyme](./testing/enzyme.md)
28 | 4. [Testing Library](./testing/testingLibrary.md)
29 | 5. Conceptos avanzados de React:
30 | 1. [Accesibilidad](./modulo3/accessibility.md)
31 | 2. [División de código](./modulo3/code-splitting.md)
32 | 3. [React Context](./modulo3/context.md)
33 | 4. [Gestión de errores](./modulo3/errorboundaries.md)
34 | 5. [High order components (HOC)](./modulo3/hoc.md)
35 | 6. [Creando referencias a elementos DOM](./modulo3/refs.md)
36 | 7. [Comprobación dinámica de tipos (PropTypes)](./modulo3/proptypes.md)
37 | 8. [Hooks](./modulo3/hooks.md)
38 | 9. [React Lazy y React Suspense](./modulo3/lazySuspense.md)
39 | 6. Librerias imprescindibles:
40 | 1. [React-Router](./modulo4/react-router.md)
41 | 2. [Material-UI](./modulo4/material-ui.md)
42 | 3. [styled-components](./modulo4/styled-components.md)
43 | 7. React con GraphQL
44 | 1. [GraphQL](./graphql/intro.md)
45 | 1. [Apollo](./graphql/apollo.md)
46 | 2. [Queries](./graphql/queries.md)
47 | 3. [Mutations](./graphql/mutations.md)
48 | 8. Redux
49 | 1. [Documentación](./modulo5/redux.md)
50 | 9. NextJS
51 | 1. [Documentación](./modulo6/nextjs.md)
52 | 10. [Opiniones](https://github.com/zamarrowski/Curso-React-Testing-GraphQL/issues/46)
--------------------------------------------------------------------------------
/modulo2/conditionalRender.md:
--------------------------------------------------------------------------------
1 | # Renderizado condicional
2 |
3 | En React, puedes crear distintos componentes que encapsulan el comportamiento que necesitas. Entonces, puedes renderizar solamente algunos de ellos, dependiendo del estado de tu aplicación.
4 |
5 | El renderizado condicional en React funciona de la misma forma que lo hacen las condiciones en JavaScript. Usa operadores de JavaScript como if o el operador condicional para crear elementos representando el estado actual, y deja que React actualice la interfaz de usuario para emparejarlos.
6 |
7 | Considera estos dos componentes:
8 |
9 | ```js
10 | function UserGreeting(props) {
11 | return Welcome back! ;
12 | }
13 | ```
14 |
15 | ```js
16 | function GuestGreeting(props) {
17 | return Please sign up. ;
18 | }
19 | ```
20 |
21 | Vamos a crear un componente Greeting que muestra cualquiera de estos componentes dependiendo si el usuario ha iniciado sesión:
22 |
23 | ```js
24 | function Greeting(props) {
25 | const isLoggedIn = props.isLoggedIn;
26 | if (isLoggedIn) {
27 | return ;
28 | }
29 | return ;
30 | }
31 | ```
32 |
33 | ```js
34 | ,
35 | ```
36 |
37 | ## If en una línea con operador lógico &&
38 |
39 |
40 | Puedes embeber cualquier expresión en JSX envolviéndola en llaves. Esto incluye al operador lógico `&&` de JavaScript. Puede ser útil para incluir condicionalmente un elemento:
41 |
42 | ```js
43 | const Users = props => {
44 | const users = props.users;
45 |
46 | return users.length ? :
47 | }
48 | ```
49 |
50 | ## If-Else en una línea con operador condicional
51 |
52 | ```js
53 | const Greeting = props => {
54 | const isLoggedIn = this.state.isLoggedIn
55 | return (
56 |
57 | The user is {isLoggedIn ? 'currently' : 'not'} logged in.
58 |
59 | )
60 | }
61 | ```
62 |
63 | ## Evitar que el componente se renderice
64 |
65 | En casos excepcionales, es posible que desees que un componente se oculte a sí mismo aunque haya sido renderizado por otro componente. Para hacer esto, devuelve `null` en lugar del resultado de renderizado.
66 |
67 |
68 | ## Ejercicios:
69 |
70 | 1. Quiero tener una pagina que solo se muestre cuando estoy logueado. Cuando estoy logueado se mostrará un texto que dira `"Esta pagina solo puedo verla por que estoy logueado"` y un botón de cerrar sesión. Cuando **NO** esté logueado mostrará un boton de Login y un mensaje de `"Inicia sesión para ver contenido privado"`.
71 |
72 |
73 | [<- Volver al índice](./../README.md)
74 |
--------------------------------------------------------------------------------
/modulo2/ShopPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import productsConstants from './products.constants'
3 | import ProductsList from './ProductsList'
4 | import { getPrice } from './helpers'
5 |
6 | class ShopPage extends React.Component {
7 |
8 | state = {
9 | selectedProducts: [],
10 | discountCode: '',
11 | applyDiscount: false,
12 | }
13 |
14 | componentDidMount() {
15 | const selectedProducts = this.getProductsFormLocalStorage()
16 | this.setState({ selectedProducts })
17 | }
18 |
19 | addProduct = product => {
20 | const newProducts = [...this.state.selectedProducts, product]
21 | this.setProductsInLocalStorage(newProducts)
22 | this.setState({
23 | selectedProducts: newProducts
24 | })
25 | }
26 |
27 | removeProduct = product => {
28 | const newProducts = this.state.selectedProducts.filter(p => p.id !== product.id)
29 | this.setProductsInLocalStorage(newProducts)
30 | this.setState({
31 | selectedProducts: newProducts
32 | })
33 | }
34 |
35 | setProductsInLocalStorage = products => {
36 | localStorage.selectedProducts = JSON.stringify(products)
37 | }
38 |
39 | getProductsFormLocalStorage = () => {
40 | const selectProducts = localStorage.selectedProducts
41 | if (selectProducts) return JSON.parse(localStorage.selectedProducts)
42 | return []
43 | }
44 |
45 | handleChangeDiscountCode = e => {
46 | this.setState({
47 | discountCode: e.target.value
48 | })
49 | }
50 |
51 | validateDiscountCode = () => {
52 | if (this.state.discountCode === 'SAVE10') {
53 | this.setState({ applyDiscount: true })
54 | } else {
55 | this.setState({ applyDiscount: false })
56 | }
57 | }
58 |
59 | render() {
60 | return (
61 |
62 |
63 |
Products
64 |
this.addProduct(product)}>Add product } />
65 |
66 |
67 |
Selected Products
68 |
this.removeProduct(product)}>Remove product } />
69 |
70 | Validate discount code
71 | {this.state.selectedProducts.length > 0 && Total: {this.state.applyDiscount ? getPrice(this.state.selectedProducts) * 0.90 : getPrice(this.state.selectedProducts)}€
}
72 |
73 |
74 | )
75 | }
76 |
77 | }
78 |
79 | export default ShopPage
--------------------------------------------------------------------------------
/modulo3/refs.md:
--------------------------------------------------------------------------------
1 | # Creando referencias a elementos DOM
2 |
3 | Las referencias proporcionan una forma de acceder a los nodos del DOM o a elementos React creados en el método de renderizado.
4 |
5 | En un flujo normal en datos de React, las propiedades son la única forma en la que los componentes padres pueden interactuar con sus hijos. Para modificar un hijo, vuelves a renderizarlo con propiedades nuevas. Sin embargo, hay ciertos casos donde necesitarás modificar imperativamente un hijo fuera del flujo de datos típico. El hijo a ser modificado puede ser una instancia de un componente React, o un elemento del DOM. Para ambos casos, React proporciona una via de escape.
6 |
7 | ## Cuando usar referencias
8 |
9 | Existen unos cuantos buenos casos de uso para referencias:
10 |
11 | * Controlar enfoques, selección de texto, o reproducción de medios.
12 | * Activar animaciones imperativas.
13 | * Integración con bibliotecas DOM de terceros.
14 |
15 | Evita usar referencias en cualquier cosa que pueda ser hecha declarativamente.
16 |
17 | Por ejemplo, en lugar de exponer los métodos `open()` y `close()` en un componente Dialog, pasa una propiedad isOpen a este en su lugar.
18 |
19 |
20 | ## Creando referencias
21 |
22 | Las referencias son creadas usando `React.createRef()` y agregándolas a elementos de React mediante el atributo `ref`. Las referencias son asignadas comúnmente a una propiedad de instancia cuando un componente es construido, así pueden ser referenciadas por el componente.
23 |
24 | ```js
25 | class MyComponent extends React.Component {
26 | constructor(props) {
27 | super(props);
28 | this.myRef = React.createRef();
29 | }
30 | render() {
31 | return
;
32 | }
33 | }
34 | ```
35 |
36 | ## Accediendo a referencias
37 |
38 | Cuando una referencia es pasada a un elemento en el renderizado, una referencia al nodo pasa a ser accesible en el atributo `current` de la referencia.
39 |
40 | ```js
41 | const node = this.myRef.current;
42 | ```
43 |
44 | El valor de la referencia es diferente dependiendo del tipo de nodo:
45 |
46 | * Cuando el atributo ref es usado en un elemento HTML, la referencia creada en el constructor con `React.createRef()` recibe el elemento DOM adyacente como su propiedad current.
47 | * Cuando el atributo ref es usado en un componente de clase personalizado, el objeto de la referencia recibe la instancia montada del componente como su atributo current.
48 | * No puedes hacer uso de referencias en componentes de función debido a que no tienen instancias.
49 |
50 |
51 | ## Ejercicios:
52 |
53 | 1. Crear un pequeño formulario con dos campos de texto. Cuando se le de a enviar obtener los valores de los inputs e imprimirlos en la consola.
54 |
55 |
56 | [<- Volver al índice](./../README.md)
57 |
--------------------------------------------------------------------------------
/modulo3/accessibility.md:
--------------------------------------------------------------------------------
1 | # Accesibilidad
2 |
3 | React es totalmente compatible con la creación de sitios web accesibles, a menudo mediante el uso de técnicas estándar de HTML.
4 |
5 | ## Normas y lineamientos
6 |
7 | El documento Iniciativa de Accesibilidad Web - Aplicaciones de Internet Enriquecidas y Accesibles (WAI-ARIA por sus siglas en inglés) contiene técnicas para construir widgets de JavaScript totalmente accesibles.
8 |
9 | Ten en cuenta que todos los atributos HTML aria- * son totalmente compatibles con JSX. Mientras que la mayoría de las propiedades y atributos de DOM en React son camelCase, estos atributos deben tener un guión (también conocido como kebab-case, lisp-case, etc.) ya que están en HTML simple:
10 |
11 | ```js
12 |
20 | ```
21 |
22 | ## HTML semántico
23 |
24 | El HTML semántico es la base de la accesibilidad en una aplicación web. Haciendo uso de los diversos elementos HTML para reforzar el significado de la información en nuestros sitios web a menudo nos dará accesibilidad de forma gratuita.
25 |
26 | A veces rompemos la semántica HTML cuando agregamos elementos `` a nuestro JSX para hacer que nuestro código React funcione, especialmente cuando trabajamos con listas `(
, y )` y la etiqueta `` de HTML. En estos casos, deberíamos usar Fragmentos React para agrupar varios elementos.
27 |
28 | Por ejemplo,
29 |
30 | ```js
31 | import React, { Fragment } from 'react';
32 |
33 | function ListItem({ item }) {
34 | return (
35 |
36 | {item.term}
37 | {item.description}
38 |
39 | );
40 | }
41 |
42 | function Glossary(props) {
43 | return (
44 |
45 | {props.items.map(item => (
46 |
47 | ))}
48 |
49 | );
50 | }
51 | ```
52 |
53 | Cuando no necesites ninguna prop en la etiqueta Fragment, puedes usar la sintaxis corta, si tus herramientas lo admiten:
54 |
55 | ```js
56 | function ListItem({ item }) {
57 | return (
58 | <>
59 | {item.term}
60 | {item.description}
61 | >
62 | );
63 | }
64 | ```
65 |
66 | ## Formularios accesibles
67 |
68 | Todos los controles de formulario HTML, como ` ` y ``, deben ser etiquetados de forma accesible. Necesitamos proporcionar etiquetas descriptivas que también estén expuestas a los lectores de pantalla.
69 |
70 | Aunque estas prácticas estándar de HTML se pueden usar directamente en React, ten en cuenta que el atributo for se escribe como htmlFor en JSX:
71 |
72 | ```js
73 | Name:
74 |
75 | ```
76 |
77 |
78 |
79 | [<- Volver al índice](./../README.md)
80 |
--------------------------------------------------------------------------------
/modulo2/jsx.md:
--------------------------------------------------------------------------------
1 | # Introducción a JSX
2 |
3 | ## JSX
4 |
5 | ```jsx
6 | const element = () => ¡Hola Fictizia!
7 | ```
8 |
9 | El código mostrado arriba, a simple vista, puede parecer raro. No es ni HTML, ni es un string es JSX. Básicamente es una extensión de la sintáxis de JavaScript y es la manera más como de desarrollar en React. No es obligatorio pero podríamos decir que todo el mundo que hace React utiliza JSX.
10 |
11 | Esta idea de meter el HTML dentro de JavaScript es muy criticada pero se basa en el concepto de que la lógica de la interfaz de usuario esta intrínsecamente unida a la lógica de renderizado. De esta forma tenemos un componente que contiene en un solo fichero JavaScript el maquetado y la lógica.
12 |
13 | [Charla explicando el por qué de JSX en profundidad](https://www.youtube.com/watch?v=x7cQ3mrcKaY)
14 |
15 | ## Diferencias en cuanto al HTML clásico
16 |
17 | Hay algunos atributos de las etiquetas HTML que cambian:
18 |
19 | * class: en React hay que escribir **className**
20 | * onclick: en React hay que escribir **onClick**
21 | * A los atributos se les puede pasar funciones, objetos, texto, expresiones...
22 |
23 | ## Renderizando variables en JSX
24 |
25 | En el siguiente ejemplo tenemos una variable llamada `name` la cual la usamos dentro de JSX envolviéndola entre llaves:
26 |
27 | ```js
28 | const name = 'Fictizia'
29 | const element = () => ¡Hola {name}!
30 | ```
31 |
32 | No solo se pueden renderizar variables sino todo tipo de expresiones dentro de las llaves:
33 |
34 | * Funciones:
35 |
36 | ```js
37 | const getName = (user) => user.name
38 | const element = () => ¡Hola {getName()}!
39 | ```
40 |
41 | * Ternarias:
42 |
43 | ```js
44 | const age = 18
45 | const element = () => ¡Hola {age > 18 ? 'adulto' : 'joven'}!
46 | ```
47 |
48 | Realmente JSX representa objetos de JavaSript pero [Babel](https://babeljs.io/) compila el JSX a una función de React llamada `createElement()`.
49 |
50 | ```jsx
51 | const element = () => ¡Hola Fictizia!
52 | ```
53 |
54 | Sería igual a esto:
55 |
56 | ```jsx
57 | const element = React.createElement(
58 | 'h1',
59 | null,
60 | '¡Hola Fictizia!'
61 | );
62 | ```
63 |
64 | Puedes comprobarlo visitando este [link](https://babeljs.io/repl#?babili=false&browsers=&build=&builtIns=false&spec=false&loose=false&code_lz=MYewdgzgLgBApgGzgWzmWBeGAeAFgRgD4BCgCRAQEMYAxAS2CjoC87KBCbAegMICggA&debug=false&forceAllTransforms=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=es2015%2Creact%2Cstage-2&prettier=false&targets=&version=7.6.0&externalPlugins=)
65 |
66 | ## Ejercicios:
67 |
68 | 1. Entrar en [BabelJS](https://babeljs.io/repl) y probar a escribir un componente que tenga una clase llamada `title`. Revisar qué salida nos da BabelJS después del compilado.
69 |
70 |
71 |
72 | [<- Volver al índice](./../README.md)
--------------------------------------------------------------------------------
/modulo4/styled-components.md:
--------------------------------------------------------------------------------
1 | # styled-components
2 |
3 | ## Instalación
4 |
5 | ```js
6 | npm install --save styled-components
7 | ```
8 |
9 | styled-components utiliza los template strings para dar estilos a tus componentes.
10 | Esto elimina el tener que mapear los estilos a los componentes. Es decir, cuando tu defines unos estilos tu estas creando un componente de React con los estilos que hayas definido.
11 |
12 | ## Ejemplos básicos
13 |
14 | Simplemente hay que importar styled:
15 |
16 | ```js
17 | import styled from 'styled-components'
18 | ```
19 |
20 | Y después basta con usar styled seguido de una etiqueta HTML:
21 |
22 | ```js
23 | const Title = styled.h1`
24 | font-size: 1.5em;
25 | text-align: center;
26 | color: palevioletred;
27 | `;
28 | ```
29 |
30 | ```js
31 | const Wrapper = styled.section`
32 | padding: 4em;
33 | background: papayawhip;
34 | `;
35 | ```
36 |
37 |
38 | ## Adaptando el estilo según las props:
39 |
40 | Al ser un template string de ES6 podemos interpolar expresiones:
41 |
42 | ```js
43 | const Button = styled.button`
44 | background: ${props => props.primary ? "palevioletred" : "white"};
45 | color: ${props => props.primary ? "white" : "palevioletred"};
46 |
47 | font-size: 1em;
48 | margin: 1em;
49 | padding: 0.25em 1em;
50 | border: 2px solid palevioletred;
51 | border-radius: 3px;
52 | `;
53 | ```
54 |
55 | Si queremos agregar estilos en grupo segun una prop debemos usar `css`:
56 |
57 | ```js
58 | import { css } from 'styled-components'
59 | ```
60 |
61 | ```js
62 | const Button = styled.button`
63 | background: white;
64 | color: palevioletred;
65 | ${props => props.primary && css`
66 | background: palevioletred;
67 | color: white;
68 | `};
69 | font-size: 1em;
70 | margin: 1em;
71 | padding: 0.25em 1em;
72 | border: 2px solid palevioletred;
73 | border-radius: 3px;
74 | `;
75 | ```
76 |
77 | ## Extendiendo estilos
78 |
79 | Podemos extender estilos de otros componentes. La única diferencia es que en vez de usar una etiqueta usaremos dicho componente en styled:
80 |
81 | ```js
82 | const Button = styled.button`
83 | color: palevioletred;
84 | font-size: 1em;
85 | margin: 1em;
86 | padding: 0.25em 1em;
87 | border: 2px solid palevioletred;
88 | border-radius: 3px;
89 | `;
90 |
91 | const TomatoButton = styled(Button)`
92 | color: tomato;
93 | border-color: tomato;
94 | `;
95 | ```
96 |
97 | ## Animaciones
98 |
99 | Se pueden crear animaciones reutilizables de manera sencilla. Simplemente tenemos que usar `keyframes` para después usarla dentro de nuestro componente:
100 |
101 | ```js
102 | const rotate = keyframes`
103 | from {
104 | transform: rotate(0deg);
105 | }
106 | to {
107 | transform: rotate(360deg);
108 | }
109 | `;
110 | const Rotate = styled.div`
111 | display: inline-block;
112 | animation: ${rotate} 2s linear infinite;
113 | padding: 2rem 1rem;
114 | font-size: 1.2rem;
115 | `;
116 | ```
117 |
118 | ### Ejercicios:
119 |
120 | 1. Maquetar un boton con styled-components que pueda ser:
121 | * Default
122 | * Success
123 | * Warning
124 | * Error
125 | * Info
126 | 2. Crear un grid con 5 columnas donde se use cada uno de los botones anteriores
127 | 3. Usar el `src/logo.svg` y crear una animación que haga rotar el logo
--------------------------------------------------------------------------------
/graphql/queries.md:
--------------------------------------------------------------------------------
1 | # Queries
2 |
3 | ## Ejecutando una query
4 |
5 | Apollo nos da un hook llamado `useQuery` al cual se le puede pasar una query y se ejecuta. Cuando el componente se renderiza se ejecutará. El hook `useQuery` nos devuelve un objeto con las siguientes propiedades:
6 |
7 | * loading
8 | * error
9 | * data
10 |
11 | ## Escribiendo una query
12 |
13 | Lo primero que tenemos que hacer es escribir una query para poder pasársela a `useQuery`.
14 |
15 | ```js
16 | import { gql } from '@apollo/client'
17 |
18 | export const GET_USERS = gql`
19 | {
20 | getUsers{
21 | id
22 | username
23 | }
24 | }
25 | `
26 | ```
27 |
28 | ## Usando `useQuery`
29 |
30 | Una vez que tenemos creada nuestra primera query podemos pasársela a `useQuery`:
31 |
32 | ```js
33 | import React from 'react';
34 | import './App.css';
35 | import { useQuery } from '@apollo/client';
36 | import { GET_USERS } from './queries'
37 |
38 | function App() {
39 |
40 | const { loading, error, data } = useQuery(GET_USERS)
41 |
42 | if (loading) return 'Loading...'
43 | if (error) return `Ops! Something went wrong!`
44 |
45 | return (
46 |
47 | {data && data.getUsers.map(u => (
48 |
{u.username}
49 | ))}
50 |
51 | );
52 | }
53 |
54 | export default App;
55 | ```
56 |
57 | ## Ejecutando queries manualmente
58 |
59 | Apollo nos da un hook llamado `useLazyQuery` el cual nos sirve para cuando queremos ejecutar queries manualmente, por ejemplo, al pulsar un botón.
60 |
61 | `useLazyQuery` nos devuelve un array con dos elementos:
62 |
63 | * una función que ejecuta la query y recibe un objeto por el cual le podemos pasar variables a la query
64 | * un objeto con las propiedades `loading` y `data`
65 |
66 | Para obtener los datos de un usuario deberíamos escribir primero la query:
67 |
68 | ```js
69 | export const GET_USER = gql`
70 | query getUser($userId: ID!) {
71 | getUser(userId: $userId) {
72 | username,
73 | first_name
74 | }
75 | }
76 | `
77 | ```
78 |
79 | Después podríamos usarla así:
80 |
81 | ```js
82 | import React from 'react';
83 | import './App.css';
84 | import { useQuery, useLazyQuery } from '@apollo/client';
85 | import { GET_USERS, GET_USER } from './queries'
86 |
87 | function App() {
88 |
89 | const { loading, error, data } = useQuery(GET_USERS)
90 | const [ getUserInfo, userInfoRequest ] = useLazyQuery(GET_USER)
91 |
92 | if (loading) return 'Loading...'
93 | if (error) return `Ops! Something went wrong!`
94 |
95 | const onGetUserInfo = (userId) => {
96 | getUserInfo({ variables: { userId } })
97 | }
98 |
99 | return (
100 |
101 | {userInfoRequest.data && `El nombre del usuario pinchado es ${userInfoRequest.data.getUser.first_name}`}
102 | {data && data.getUsers.map(u => (
103 |
onGetUserInfo(u.id)}>{u.username}
104 | ))}
105 |
106 | );
107 | }
108 |
109 | export default App;
110 |
111 | ```
112 |
113 | ## Ejercicios
114 |
115 | 1. Montar una aplicación con react-router en la que la ruta a '/' muestre una lista de usuarios tirando contra el servidor de GraphQL
116 | 2. Cuando se pinche en un usuario tiene que ir a /users/:id y muestre la información del usuario
117 | 3. En la ventana del ejercicio anterior pintar los posts del usuario
118 | 4. BONUS: pintar los comentarios de los posts del usuario
119 |
120 | [<- Volver al índice](./../README.md)
--------------------------------------------------------------------------------
/modulo2/props.md:
--------------------------------------------------------------------------------
1 | # Componentes y propiedades
2 |
3 | Los componentes permiten separar la interfaz de usuario en piezas independientes, reutilizables y pensar en cada pieza de forma aislada.
4 |
5 | Conceptualmente, los componentes son como las funciones de JavaScript. Aceptan entradas arbitrarias (llamadas “props”) y devuelven a React elementos que describen lo que debe aparecer en la pantalla.
6 |
7 | ## Componente funcionales
8 |
9 | La forma más sencilla de definir un componente es escribir una función de JavaScript:
10 |
11 | ```js
12 | const Welcome = (props) => Hola, {props.name}
13 | ```
14 |
15 | Esta función es un componente de React válido porque acepta un solo argumento de objeto “props” (que proviene de propiedades) con datos y devuelve un elemento de React. Llamamos a dichos componentes “funcionales” porque literalmente son funciones JavaScript.
16 |
17 | ## Componentes de clase
18 |
19 | También puedes utilizar una clase de ES6 para definir un componente:
20 |
21 |
22 | ```js
23 | class Welcome extends React.Component {
24 | render() {
25 | return Hello, {this.props.name} ;
26 | }
27 | }
28 | ```
29 |
30 | Las clases tienen algunas características adicionales que veremos en las próximas secciones. Hasta entonces, usaremos componentes funcionales por su brevedad.
31 |
32 | ## Accediendo a los hijos
33 |
34 | Se puede acceder a los hijos de un componente a través de las `props` usando `props.children`
35 |
36 | ## Restricciones
37 |
38 | Las props son de solo lectura. Ya sea que declares un componente como una función o como una clase, este nunca debe modificar sus props. Considera esta función sum :
39 |
40 | ```js
41 | function sum(a, b) {
42 | return a + b
43 | }
44 | ```
45 |
46 | Tales funciones son llamadas “puras” por que no tratan de cambiar sus entradas, y siempre devuelven el mismo resultado para las mismas entradas.
47 |
48 | En contraste, esta función es impura por que cambia su propia entrada:
49 |
50 | ```js
51 | function withdraw(account, amount) {
52 | account.total -= amount
53 | }
54 | ```
55 |
56 | React es bastante flexible pero tiene una sola regla estricta:
57 |
58 | **Todos los componentes de React deben actuar como funciones puras con respecto a sus props.**
59 |
60 | ¿Entonces como cambio dinámicamente lo que muestra un componente?
61 |
62 | En la siguiente sección, introduciremos un nuevo concepto de “estado”. El estado le permite a los componentes de React cambiar su salida a lo largo del tiempo en respuesta a acciones del usuario, respuestas de red y cualquier otra cosa, sin violar esta regla.
63 |
64 | **Ejercicios:**
65 |
66 | 1. Dado el siguiente código HTML:
67 |
68 | ```html
69 | Necesito partir en componentes todo esto
70 | Para ello puedo usar React que me permitirá poder reutilizar todos esos componentes. Para ello tengo que:
71 |
72 | Observar el HTML
73 | Pensar en como puedo extraer cada trozo en componentes
74 | Usarlos en React
75 |
76 |
77 | React Docs
78 | ```
79 | Debemos crear los siguientes componentes y que se muestre en React como debería:
80 | * Title
81 | * Text
82 | * List
83 | * ListItem
84 | * Link: debemos poder elegir por la prop `openInNewTab` si queremos que se abra en una nueva ventana o no.
85 |
86 | 2. Crear un componente llamado `Loading` que si su prop `show` es es verdadera muestre sus hijos. Si es falsa muestre un mensaje: `Loading...`. **Utilizar como hijos el ejercicio anterior.**
87 |
88 | 3. Crear un componente button y pasarle por props una función (`() => console.log('holi')`). Añadirla al onClick del botón.
89 |
90 | 4. Crear un componente LogProps que renderize una etiqueta `p` y saque por consola todas las props que se le pasen, que deben ser:
91 | * colors: `array`
92 | * isActive: `boolean`
93 | * callBack: `function`
94 | * numberOfColors: `number`
95 | * name: `string`
96 |
97 |
98 | [<- Volver al índice](./../README.md)
99 |
--------------------------------------------------------------------------------
/testing/testingLibrary.md:
--------------------------------------------------------------------------------
1 | # Testing Library
2 |
3 | Si usamos create-react-app ya viene por defecto instalado en cualquier otro caso haríamos:
4 |
5 | ```
6 | npm install --save-dev @testing-library/react
7 | ```
8 |
9 | ## Ejemplos
10 |
11 | 1. Obtener un elemento
12 |
13 | ```js
14 | test('Should return hola', () => {
15 | render(hola )
16 | expect(screen.getByRole('heading')).toHaveTextContent('hola')
17 | })
18 | ```
19 |
20 | 2. Obtener varios elementos
21 |
22 | ```js
23 | render(
24 | <>
25 | hola
26 | adios
27 | >
28 | )
29 | expect(screen.getAllByRole('heading')[0]).toHaveTextContent('hola')
30 | expect(screen.getAllByRole('heading')[1]).toHaveTextContent('adios')
31 | ```
32 |
33 | 3. Probar eventos
34 |
35 | ```js
36 | const fn = jest.fn()
37 | render(click me )
38 | fireEvent.click(screen.getByRole('button'))
39 | expect(fn).toHaveBeenCalled()
40 | expect(fn).toHaveBeenCalledTimes(1)
41 | expect(fn).toHaveBeenCalledWith('mi valor')
42 | ```
43 |
44 |
45 | ## Queries
46 |
47 | * ByLabelText find by label or aria-label text content
48 | * getByLabelText
49 | * queryByLabelText
50 | * getAllByLabelText
51 | * queryAllByLabelText
52 | * findByLabelText
53 | * findAllByLabelText
54 |
55 | * ByPlaceholderText find by input placeholder value
56 | * getByPlaceholderText
57 | * queryByPlaceholderText
58 | * getAllByPlaceholderText
59 | * queryAllByPlaceholderText
60 | * findByPlaceholderText
61 | * findAllByPlaceholderText
62 |
63 | * ByText find by element text content
64 | * getByText
65 | * queryByText
66 | * getAllByText
67 | * queryAllByText
68 | * findByText
69 | * findAllByText
70 |
71 | * ByDisplayValue find by form element current value
72 | * getByDisplayValue
73 | * queryByDisplayValue
74 | * getAllByDisplayValue
75 | * queryAllByDisplayValue
76 | * findByDisplayValue
77 | * findAllByDisplayValue
78 |
79 | * ByAltText find by img alt attribute
80 | * getByAltText
81 | * queryByAltText
82 | * getAllByAltText
83 | * queryAllByAltText
84 | * findByAltText
85 | * findAllByAltText
86 |
87 | * ByTitle find by title attribute or svg title tag
88 | * getByTitle
89 | * queryByTitle
90 | * getAllByTitle
91 | * queryAllByTitle
92 | * findByTitle
93 | * findAllByTitle
94 |
95 | * ByRole find by aria role
96 | * getByRole
97 | * queryByRole
98 | * getAllByRole
99 | * queryAllByRole
100 | * findByRole
101 | * findAllByRole
102 |
103 | * ByTestId find by data-testid attribute
104 | * getByTestId
105 | * queryByTestId
106 | * getAllByTestId
107 | * queryAllByTestId
108 | * findByTestId
109 | * findAllByTestId
110 |
111 |
112 | ## Buscar textos
113 |
114 | ```js
115 | // Matching a string:
116 | getByText('Hello World') // full string match
117 | getByText('llo Worl', {exact: false}) // substring match
118 | getByText('hello world', {exact: false}) // ignore case
119 |
120 | // Matching a regex:
121 | getByText(/World/) // substring match
122 | getByText(/world/i) // substring match, ignore case
123 | getByText(/^hello world$/i) // full string match, ignore case
124 | getByText(/Hello W?oRlD/i) // advanced regex
125 |
126 | // Matching with a custom function:
127 | getByText((content, element) => content.startsWith('Hello'))
128 | ```
129 |
130 | ## Ejercicios
131 |
132 | 1. Con el ejercicio de la tienda que hemos hecho en clases anteriores testear:
133 | 1. El componente `ShopPage.js`
134 | 2. El componente `ProductsList.js`
135 |
136 | 2. Testear el componente `src/clase4/Select.js`
137 | ```js
138 | import React from 'react'
139 |
140 | export default props =>
141 |
142 | {props.items.map(val => (
143 | {val}
144 | ))}
145 |
146 | ```
147 |
148 | [<- Volver al índice](./../README.md)
--------------------------------------------------------------------------------
/testing/jest.md:
--------------------------------------------------------------------------------
1 | # Jest
2 |
3 | 
4 |
5 | * Zero config
6 | * Permite snapshots
7 | * Aislados
8 | * API sencilla
9 | * Por defecto con create-react-app
10 | * Coverage
11 |
12 | ## Common matchers
13 |
14 | La forma más sencilla de comprobar el valor de un test es:
15 |
16 | ```js
17 | test('two plus two is four', () => {
18 | expect(2 + 2).toBe(4);
19 | });
20 | ```
21 |
22 | Si queremos comparar objectos deberiamos usar `toEqual` ya que este comprueba cada campo y valor de un objeto o de un array:
23 |
24 | ```js
25 | test('object assignment', () => {
26 | const data = {one: 1};
27 | data['two'] = 2;
28 | expect(data).toEqual({one: 1, two: 2});
29 | });
30 | ```
31 |
32 | Hay diferencias entre `toEqual` y `toStrictEqual`:
33 |
34 | * Las keys con propiedades undefined son comprobadas, es decir, `{a: undefined, b: 2}` no va a ser igual que `{b: 2}` cuando usas `toStrictEqual`
35 | * Las posiciones ignoradas en un array son comprobadas, es decir, `[, 1]` no va a ser igual que `[undefined, 1]` cuando usas `toStrictEqual`
36 | * Los tipos de objetos son comprobados, es decir, una clase con una propiedad `a` no va a ser igual que un objeto con una propiedad `a`
37 |
38 | ### Truthiness
39 |
40 | Hay veces que en los tests que escribimos tenemos que distinguir entre `null`, `undefined` o `false` para ello existen los siguientes matchers:
41 |
42 | * toBeNull
43 | * toBeUndefined
44 | * toBeDefined
45 | * toBeTruthy
46 | * toBeFalsy
47 |
48 | ### Numbers
49 |
50 | Cuando estamos tratando con números hay veces que no solo queremos comprobar que el valor sea igual por lo que tenemos otros matchers:
51 |
52 | ```js
53 | test('two plus two', () => {
54 | const value = 2 + 2;
55 | expect(value).toBeGreaterThan(3);
56 | expect(value).toBeGreaterThanOrEqual(3.5);
57 | expect(value).toBeLessThan(5);
58 | expect(value).toBeLessThanOrEqual(4.5);
59 |
60 | expect(value).toBe(4);
61 | expect(value).toEqual(4);
62 | });
63 | ```
64 |
65 | ### Strings
66 |
67 | Podemos jugar con los strings y ver si pasan una expresión regular:
68 |
69 | ```js
70 | test('there is no I in team', () => {
71 | expect('team').not.toMatch(/I/);
72 | });
73 | ```
74 |
75 | ### Arrays
76 |
77 | Un test muy común es ver si un array contiene cierto elemento:
78 |
79 | ```js
80 | const shoppingList = [
81 | 'diapers',
82 | 'kleenex',
83 | 'trash bags',
84 | 'paper towels',
85 | 'beer',
86 | ];
87 |
88 | test('the shopping list has beer on it', () => {
89 | expect(shoppingList).toContain('beer');
90 | });
91 | ```
92 |
93 | ### Excepciones
94 |
95 | Si una función lanza una excepción podemos comprobarlo:
96 |
97 | ```js
98 | function compileAndroidCode() {
99 | throw new Error('you are using the wrong JDK');
100 | }
101 |
102 | test('compiling android goes as expected', () => {
103 | expect(compileAndroidCode).toThrow();
104 | expect(compileAndroidCode).toThrow('you are using the wrong JDK');
105 | expect(compileAndroidCode).toThrow(/JDK/);
106 | });
107 | ```
108 |
109 | ## Usando Jest en create-react-app
110 |
111 | create-react-app nos lo pone muy fácil ya que no tenemos que configurar nada para empezar a escribir y lanzar tests.
112 |
113 | Para escribir un test lo primero que debemos hacer es crear un fichero `nombreDelTest.test.js` y Jest ya sabrá que ese fichero es un fichero de tests y lanzará los tests que escribamos.
114 |
115 | Después simplemente debemos empezar a escribir los tests dentro del fichero con la siguiente sintáxis:
116 |
117 | ```js
118 | //podemos importarnos lo que necesitemos
119 |
120 | import sum from './sum'
121 |
122 | test("nombre descriptivo del tests", () => {
123 | //comprobaciones que queramos hacer
124 | expect(sum(2, 2)).toBe(4)
125 | })
126 | ```
127 |
128 | Para lanzar los tests debemos ejecutar `yarn test` o `npm test` y lanzará todos los tests que detecte.
129 |
130 | ## Ejercicios
131 |
132 | 1. Comprobar que en el array que devuelve la función existe el elemento "blue":
133 | ```js
134 | const getColors = () => {
135 | return ['yellow', 'red', 'blue']
136 | }
137 | ```
138 |
139 | 2. Dado el siguiente código comprobar que el array tiene 2 elementos y que blue no existe:
140 | ```js
141 | const getColors = () => {
142 | return ['yellow', 'red', 'blue']
143 | }
144 |
145 | const removeColorFromArray = (array, color) => {
146 | return array.filter(element => element !== color)
147 | }
148 |
149 | const result = removeColorFromArray(getColors(), 'blue')
150 | ```
151 |
152 | 3. Comprobar que la siguiente función devuelve el objeto que debería crear correctamente:
153 |
154 | ```js
155 | const createUser = (name, age) => ({ name, age })
156 | ```
157 |
158 | [<- Volver al índice](./../README.md)
--------------------------------------------------------------------------------
/modulo2/lifecycle.md:
--------------------------------------------------------------------------------
1 | # Ciclos de vida de un componente
2 |
3 | Cada componente tiene varios “métodos de ciclo de vida” que puedes sobrescribir para ejecutar código en momentos particulares del proceso. Puedes usar este diagrama de ciclo de vida como una hoja de referencia. En la lista de abajo, los métodos de ciclo de vida comúnmente usados están marcados en negrita. El resto de ellos existen para casos de uso relativamente raros.
4 |
5 | 
6 |
7 | ## **constructor()**
8 |
9 | Si no inicializas el estado y no enlazas los métodos, no necesitas implementar un constructor para tu componente React.
10 |
11 | El constructor para un componente React es llamado antes de ser montado. Al implementar el constructor para una subclase React.Component, deberías llamar a super(props) antes que cualquier otra instrucción. De otra forma, this.props no estará definido en el constructor, lo que puede ocasionar a errores.
12 |
13 | Normalmente, los constructores de React sólo se utilizan para dos propósitos:
14 |
15 | Para inicializar un estado local asignando un objeto al this.state.
16 |
17 | Para enlazar manejadores de eventos a una instancia.
18 |
19 | No debes llamar setState() en el constructor(). En su lugar, si su componente necesita usar el estado local, asigna directamente el estado inicial al this.state directamente en el constructor:
20 |
21 | ```js
22 | constructor(props) {
23 | super(props);
24 | // No llames this.setState() aquí!
25 | this.state = { counter: 0 };
26 | this.handleClick = this.handleClick.bind(this);
27 | }
28 | ```
29 |
30 | ## **componentDidMount()**
31 |
32 | componentDidMount() se invoca inmediatamente después de que un componente se monte (se inserte en el árbol). La inicialización que requiere nodos DOM debería ir aquí. Si necesita cargar datos desde un punto final remoto, este es un buen lugar para instanciar la solicitud de red.
33 |
34 | Este método es un buen lugar para establecer cualquier suscripción. Si lo haces, no olvides darle de baja en componentWillUnmount().
35 |
36 | Puedes llamar setState() inmediatamente en componentDidMount(). Se activará un renderizado extra, pero sucederá antes de que el navegador actualice la pantalla. Esto garantiza que, aunque en este caso se invocará dos veces el render(), el usuario no verá el estado intermedio. Utiliza este patrón con precaución porque a menudo causa problemas de rendimiento. En la mayoría de los casos, deberías ser capaz de asignar el estado inicial en el constructor() en su lugar. Sin embargo, puede ser necesario para casos como modales y tooltips cuando se necesita medir un nodo DOM antes de representar algo que depende de su tamaño o posición.
37 |
38 | ## **componentDidUpdate()**
39 |
40 | ```
41 | componentDidUpdate(prevProps, prevState, snapshot)
42 | ```
43 |
44 | componentDidUpdate() se invoca inmediatamente después de que la actualización ocurra. Este método no es llamado para el renderizador inicial.
45 |
46 | Use esto como una oportunidad para operar en DOM cuando el componente se haya actualizado. Este es también un buen lugar para hacer solicitudes de red siempre y cuando compare los accesorios actuales con los anteriores (por ejemplo, una solicitud de red puede no ser necesaria si los props no han cambiado).
47 |
48 | ```js
49 | componentDidUpdate(prevProps) {
50 | // Uso tipico (no olvides de comparar los props):
51 | if (this.props.userID !== prevProps.userID) {
52 | this.fetchData(this.props.userID);
53 | }
54 | }
55 | ```
56 |
57 | Puedes llamar setState() inmediatamente en componentDidUpdate() pero ten en cuenta que debe ser envuelto en una condición como en el ejemplo anterior, o causará un bucle infinito. También causaría una renderización adicional que, aunque no sea visible para el usuario, puede afectar el rendimiento del componente.
58 |
59 | ## **componentWillUnmount()**
60 |
61 | componentWillUnmount() se invoca inmediatamente antes de desmontar y destruir un componente. Realiza las tareas de limpieza necesarias en este método, como la invalidación de temporizadores, la cancelación de solicitudes de red o la eliminación de las suscripciones que se crearon en componentDidMount().
62 |
63 | No debes llamar setState() en componentWillUnmount() porque el componente nunca será vuelto a renderizar. Una vez que una instancia de componente sea desmontada, nunca será montada de nuevo.
64 |
65 |
66 | ## Ejercicios:
67 |
68 |
69 | 1. Crear un componente de clase que tenga un estado con una propiedad `users` y sea un `array` de nombres. El estado debe estar inicializado con dos nombres. Cuando el componente se haya montado añadir un usuario más y actualizar el estado. Cuando el componente se destruya debería de sacar un mensaje por consola diciendo `¡Componente destruido!`.
70 |
71 | 2. Crear un componente que vaya mostrando los segundos que pasan
72 |
73 | 3. Crear un componente de clase que tenga un estado con la propiedad `tasks`. Hacer una peticion a `https://jsonplaceholder.typicode.com/todos` y pintar el JSON que te devuelve.
74 |
75 | [<- Volver al índice](./../README.md)
76 |
--------------------------------------------------------------------------------
/modulo6/nextjs.md:
--------------------------------------------------------------------------------
1 | # NextJS
2 |
3 | NextJS nos da una CLI que nos instala y prepara todo para poder iniciar un proyecto. Para ello debemos ejecutar:
4 |
5 | ```
6 | npx create-next-app@latest
7 | ```
8 |
9 | Para arrancar el servidor debemos ejecutar:
10 |
11 | ```
12 | yarn run dev
13 | ```
14 |
15 | y nos levantará el servidor en http://localhost:3000.
16 |
17 | Nos creará una estructura básica donde en el directorio `pages` podremos crear nuestras páginas que realmente son componentes de React. Por cada uno de los componentes que creemos creará una ruta con ese nombre.
18 |
19 | Podemos tener rutas anidadas creando carpetas, por ejemplo: `pages/about/projects.js` o `pages/about/me.js`. Inluco podemos tener rutas con parámetros dinámicos de la siguiente forma `pages/about/[id].js`.
20 |
21 | Para recoger los valores dinamicos de la URL sería algo así:
22 |
23 | ```js
24 | import { useRouter } from "next/router"
25 |
26 | const Dynamic = (props) => {
27 | const router = useRouter()
28 | const { id } = router.query
29 | return Dynamic {id}
30 | }
31 |
32 | export default Dynamic
33 | ```
34 |
35 | ## Renderizar componentes que obtienen datos de servidores
36 |
37 | Por ejemplo, podemos necesitar renderizar nuestra página con los datos que se obtienen de un servidor externo. Para ello NextJS nos da una función llamada `getStaticProps` en la cual podemos hacer llamadas a un servidor y el se encargará de devolver esa página ya renderizada. Por ejemplo:
38 |
39 | ```js
40 | const Users = (props) => {
41 | return
42 | {props.users.map(user =>
{user.username}
)}
43 |
44 | }
45 |
46 | export async function getStaticProps() {
47 | const res = await fetch('https://jsonplaceholder.typicode.com/users')
48 | const users = await res.json()
49 |
50 | return {
51 | props: {
52 | users,
53 | },
54 | }
55 | }
56 |
57 | export default Users
58 | ```
59 |
60 | Si por ejemplo necesitamos el id del usuario para renderizar el detalle podemos hacer algo como lo siguiente:
61 |
62 | ```js
63 | const User = (props) => {
64 | return
65 | {props.user?.username}
66 |
67 | }
68 |
69 | export async function getStaticPaths() {
70 | const res = await fetch(`https://jsonplaceholder.typicode.com/users`)
71 | const users = await res.json()
72 | const paths = users.map((user) => ({
73 | params: { id: user.id.toString() },
74 | }))
75 |
76 | return {
77 | paths,
78 | fallback: false,
79 | }
80 | }
81 |
82 | export async function getStaticProps({ params }) {
83 | const res = await fetch(`https://jsonplaceholder.typicode.com/users/${params.id}`)
84 | const user = await res.json()
85 |
86 | return {
87 | props: {
88 | user,
89 | },
90 | }
91 | }
92 |
93 | export default User
94 | ```
95 |
96 | Hay un parámetro en la función getStaticPaths que es muy importante y es el `fallback`:
97 |
98 | * true -> nos va a generar estáticamente todo el contenido de las rutas que le indiquemos. Para el resto de rutas las genererá la primera vez que se la soliciten.
99 | * false -> tenemos que indicarle las rutas que queremos generar estátitcamente y si se pide alguna de las cuales no está devolverá un 404.
100 | * `blocking` -> todas las rutas que no son indicadas esperarán a generarse estáticamente por eso se llama bloqueante.
101 |
102 |
103 | Si por el contrario queremos que una página SIEMPRE se genere cada vez que se pide deberíamos usar `getServerSideProps`:
104 |
105 | ```js
106 | const User = (props) => {
107 | return
108 | {props.user?.username}
109 |
110 | }
111 |
112 | export async function getServerSideProps({ params }) {
113 | const res = await fetch(`https://jsonplaceholder.typicode.com/users/${params.id}`)
114 | const user = await res.json()
115 |
116 | return {
117 | props: {
118 | user,
119 | },
120 | }
121 | }
122 |
123 | export default User
124 | ```
125 |
126 | ## Componentes
127 |
128 | Para crear nuestros componentes y usarlos en nuestras páginas debemos meterlos en una carpeta llamada `components` y después importarlos en la página que queramos.
129 |
130 |
131 | ## Despliegue en Vercel
132 |
133 | Desplegar con Vercel es muy sencillo. Lo único que tenemos que hacer es acceder a [vercel](https://vercel.com) y loguearnos con Github. Una vez allí debemos de decirle que repositorio queremos desplegar y darle a import:
134 |
135 | 
136 |
137 | Como NextJS es de Vercel el despliegue es muy sencillo y no tenemos que configurar nada simplemente le daremos al botón de Deploy.
138 |
139 | Una vez hecho esto podemos irnos al [Dashboard](https://vercel.com/dashboard) para ver como va el deployment y ver el dominio que nos ha generado.
140 |
141 |
142 | ## Ejercicios:
143 |
144 | 1. Crear una página donde pinte todo el listado de criptomonedas (name, price) de https://api.coincap.io/v2/assets
145 | 2. Crear una página dinámica donde pinte más info de una criptomoneda (name, symbol, price, maxSupply...)
146 | 3. Hacer que se pueda navegar desde la primera página a la del detalle de cada criptomoneda.
147 | 4. Desplegar en Vercel
148 |
149 | [<- Volver al índice](./../README.md)
150 |
--------------------------------------------------------------------------------
/modulo2/renderList.md:
--------------------------------------------------------------------------------
1 | # Pintando listas
2 |
3 | Primero, vamos a revisar como transformas listas en Javascript.
4 |
5 | Dado el código de abajo, usamos la función map() para tomar un array de numbers y duplicar sus valores. Asignamos el nuevo array devuelto por map() a la variable doubled y la mostramos:
6 |
7 | ```js
8 | const numbers = [1, 2, 3, 4, 5]
9 | const doubled = numbers.map((number) => number * 2)
10 | console.log(doubled)
11 | ```
12 |
13 | Este código muestra [2, 4, 6, 8, 10] a la consola.
14 |
15 | En React, transformar arrays en listas de elementos es casi idéntico.
16 |
17 | ```js
18 | const numbers = [1, 2, 3, 4, 5]
19 | const listItems = numbers.map((number) =>
20 | {number}
21 | )
22 | ```
23 |
24 | ## Keys
25 |
26 | Las keys ayudan a React a identificar que ítems han cambiado, son agregados, o son eliminados. Las keys deben ser dadas a los elementos dentro del array para darle a los elementos una identidad estable:
27 |
28 | ```js
29 | const numbers = [1, 2, 3, 4, 5]
30 | const listItems = numbers.map((number) =>
31 |
32 | {number}
33 |
34 | )
35 | ```
36 |
37 | La mejor forma de elegir una key es usando un string que identifique únicamente a un elemento de la lista entre sus hermanos. Habitualmente vas a usar IDs de tus datos como key:
38 |
39 | ```js
40 | const todoItems = todos.map((todo) =>
41 |
42 | {todo.text}
43 |
44 | );
45 | ```
46 |
47 | Cuando no tengas IDs estables para renderizar, puedes usar el índice del ítem como una key como último recurso:
48 |
49 | ```js
50 | const todoItems = todos.map((todo, index) =>
51 | // Only do this if items have no stable IDs
52 |
53 | {todo.text}
54 |
55 | );
56 | ```
57 |
58 | No recomendamos usar índices para keys si el orden de los ítems puede cambiar. Esto puede impactar negativamente el rendimiento y puede causar problemas con el estado del componente
59 |
60 | ## Integrar map() en JSX
61 |
62 | JSX permite integrar cualquier expresión en llaves así que podemos alinear el resultado de map():
63 |
64 | ```js
65 | function NumberList(props) {
66 | const numbers = props.numbers;
67 | return (
68 |
69 | {numbers.map((number) =>
70 |
72 |
73 | )}
74 |
75 | );
76 | }
77 | ```
78 |
79 |
80 | ## Ejercicios:
81 |
82 | 1. Dado el siguiente array de usuarios pintar una lista (`ul`) con sus nombres:
83 |
84 | ```js
85 | const users = ['sergio', 'victoria', 'iván', 'liviu']
86 | ```
87 |
88 | 2. Dado el siguiente array pintar una lista de usuarios donde aparezca el nombre y la edad de cada uno:
89 | ```js
90 | const users = [{ name: 'Sergio', age: 28 }, { name: 'Victoria', age: 27 }, { name: 'Iván', age: 30 }, { name: 'Liviu', age: 26 }]
91 | ```
92 |
93 | 3. Hacer una petición a `api.coincap.io/v2/assets` y pintar un select con las distintas criptomonedas que nos devuelve
94 |
95 | 4. Vamos a crear un portfolio de criptomonedas. Debemos poder:
96 | * Añadir una criptomoneda al portfolio con la cantidad que deseemos
97 | * Borrar una criptomoneda al portfolio
98 | * Mostrar el precio total de nuestras criptomonedas
99 | * Si añadimos dos veces la misma moneda debería de sumarse a la anterior cantidad que tuviesemos y no salir por duplicado
100 | * Pintar una gráfica con el porcentaje de criptomonedas que tenemos. Para ello he creado este componente que podéis copiar y pegar:
101 |
102 | ```js
103 | import React from 'react'
104 | import { Doughnut } from 'react-chartjs-2'
105 |
106 | const getData = cryptos => {
107 | const total = cryptos.reduce((acum, next) => acum + next.price * next.total, 0)
108 | return {
109 | labels: cryptos.map(c => c.name),
110 | datasets: [{
111 | data: cryptos.map(c => (c.total * c.price) * 100 / total),
112 | backgroundColor: [
113 | 'rgba(255, 99, 132, 0.2)',
114 | 'rgba(54, 162, 235, 0.2)',
115 | 'rgba(255, 206, 86, 0.2)',
116 | 'rgba(75, 192, 192, 0.2)',
117 | 'rgba(153, 102, 255, 0.2)',
118 | 'rgba(255, 159, 64, 0.2)',
119 | ],
120 | borderColor: [
121 | 'rgba(255, 99, 132, 1)',
122 | 'rgba(54, 162, 235, 1)',
123 | 'rgba(255, 206, 86, 1)',
124 | 'rgba(75, 192, 192, 1)',
125 | 'rgba(153, 102, 255, 1)',
126 | 'rgba(255, 159, 64, 1)',
127 | ],
128 | }]
129 | }
130 | }
131 |
132 | const PortfolioChart = props => (
133 |
134 |
135 |
136 | )
137 |
138 | export default PortfolioChart
139 | ```
140 |
141 | Hay que instalar `chartjs y react-chartjs`: `npm install --save react-chartjs-2 chart.js`
142 | Para utilizarlo tenéis que pasarle la información de la siguiente manera:
143 |
144 | ```js
145 |
152 | ```
153 |
154 | 5. Crear un TODO list que permita añadir, borrar y editar. Debería de haber los siguientes componentes:
155 | * ListContainer
156 | * List
157 | * ListItem
158 | * InputText
159 | * AddTaskButton
160 | * RemoveTaskButton
161 |
162 | [<- Volver al índice](./../README.md)
163 |
164 |
--------------------------------------------------------------------------------
/modulo3/proptypes.md:
--------------------------------------------------------------------------------
1 | # Comprobación dinámica de tipos (PropTypes)
2 |
3 | A medida que tu aplicación crece, puedes capturar una gran cantidad de errores con verificación de tipos. Para algunas aplicaciones, puedes usar extensiones de Javascript como Flow o TypeScript para verificar los tipos en tu aplicación. Pero incluso si no usas alguno de ellos, React tiene algunas habilidades de verificación de tipos incorporadas. Para usar verificación de tipos en las props de un componente, puedes asignar la propiedad especial PropTypes:
4 |
5 | ```js
6 | import PropTypes from 'prop-types';
7 |
8 | class Greeting extends React.Component {
9 | render() {
10 | return (
11 | Hello, {this.props.name}
12 | );
13 | }
14 | }
15 |
16 | Greeting.propTypes = {
17 | name: PropTypes.string
18 | };
19 | ```
20 |
21 |
22 | ## PropTypes
23 |
24 | Aquí hay un ejemplo que documenta los diferentes tipos de validadores:
25 |
26 | ```js
27 | import PropTypes from 'prop-types';
28 |
29 | MyComponent.propTypes = {
30 | // Puedes declarar que una propiedad es un tipo específico de JS. Por defecto, estas
31 | // son todas opcionales.
32 | optionalArray: PropTypes.array,
33 | optionalBool: PropTypes.bool,
34 | optionalFunc: PropTypes.func,
35 | optionalNumber: PropTypes.number,
36 | optionalObject: PropTypes.object,
37 | optionalString: PropTypes.string,
38 | optionalSymbol: PropTypes.symbol,
39 |
40 | // Cualquier cosa que sea interpretada: números, cadenas, elementos o un array
41 | // (o fragment) que contengan estos tipos.
42 | optionalNode: PropTypes.node,
43 |
44 | // Un elemento de React
45 | optionalElement: PropTypes.element,
46 |
47 | // Un tipo de elemento React (ej. MyComponent).
48 | optionalElementType: PropTypes.elementType,
49 |
50 | // Además puedes declarar que una prop es una instancia de una clase. Este usa
51 | // el operador instanceof de JS.
52 | optionalMessage: PropTypes.instanceOf(Message),
53 |
54 | // Puedes asegurar que una prop esta limitada a valores específicos si se
55 | // considera como enum.
56 | optionalEnum: PropTypes.oneOf(['News', 'Photos']),
57 |
58 | // Un objeto que puede ser de diferentes tipos
59 | optionalUnion: PropTypes.oneOfType([
60 | PropTypes.string,
61 | PropTypes.number,
62 | PropTypes.instanceOf(Message)
63 | ]),
64 |
65 | // Un array de determinado tipo
66 | optionalArrayOf: PropTypes.arrayOf(PropTypes.number),
67 |
68 | // Un objeto con valores de propiedad de determinado tipo
69 | optionalObjectOf: PropTypes.objectOf(PropTypes.number),
70 |
71 | // Un objeto que tenga determinada estructura
72 | optionalObjectWithShape: PropTypes.shape({
73 | color: PropTypes.string,
74 | fontSize: PropTypes.number
75 | }),
76 |
77 | // Un objeto con advertencias sobre propiedades adicionales
78 | optionalObjectWithStrictShape: PropTypes.exact({
79 | name: PropTypes.string,
80 | quantity: PropTypes.number
81 | }),
82 |
83 | // Puedes encadenar cualquiera de los anteriores con `isRequired` para asegurar
84 | // que se muestre una advertencia si la prop no se suministra.
85 | requiredFunc: PropTypes.func.isRequired,
86 |
87 | // Un valor de cualquier tipo
88 | requiredAny: PropTypes.any.isRequired,
89 |
90 | // También puedes suministrar un validador personalizado. Debe retornar un objeto Error
91 | // si la validación falla. No uses `console.warn` o throw, porque no va a funcionar en
92 | // `oneOfType`
93 | customProp: function(props, propName, componentName) {
94 | if (!/matchme/.test(props[propName])) {
95 | return new Error(
96 | 'Invalid prop `' + propName + '` supplied to' +
97 | ' `' + componentName + '`. Validation failed.'
98 | );
99 | }
100 | },
101 |
102 | // También puedes suministrar un validador personalizado a `arrayOf` y `objectOf`.
103 | // Debe retornar un objeto Error si la validación falla. El validador se llamará
104 | // por cada key en el array o el objeto. Los primeros dos arguments del validador
105 | // son el array o el objeto, y la key del elemento actual.
106 | customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
107 | if (!/matchme/.test(propValue[key])) {
108 | return new Error(
109 | 'Invalid prop `' + propFullName + '` supplied to' +
110 | ' `' + componentName + '`. Validation failed.'
111 | );
112 | }
113 | })
114 | };
115 | ```
116 |
117 | ## Valores por defecto de props
118 |
119 | Puedes definir los valores por defecto de tus props al asignar la propiedad especial defaultProps:
120 |
121 | ```js
122 | class Greeting extends React.Component {
123 | render() {
124 | return (
125 | Hello, {this.props.name}
126 | );
127 | }
128 | }
129 |
130 | // Especifica los valores por defecto de props:
131 | Greeting.defaultProps = {
132 | name: 'Stranger'
133 | };
134 |
135 | // Renderiza "Hello, Stranger":
136 | ReactDOM.render(
137 | ,
138 | document.getElementById('example')
139 | );
140 | ```
141 |
142 | ## Ejercicios:
143 |
144 | 1. Crear un componente que se llame ShowServerConfig. Debe validar lo siguiente:
145 | * La config que se le pasa por props debe tener la siguiente estructura:
146 | - minConnections: boolean
147 | - maxConnections: boolean
148 | - restartAlways: boolean
149 | * El environment solo puede ser dev, play o live
150 | * SSL debe ser obligatorio si el entorno es live.
151 |
152 |
153 | [<- Volver al índice](./../README.md)
154 |
--------------------------------------------------------------------------------
/modulo2/events.md:
--------------------------------------------------------------------------------
1 | # Gestionando eventos en React
2 |
3 | Manejar eventos en elementos de React es muy similar a manejar eventos con elementos del DOM. Hay algunas diferencias de sintaxis:
4 |
5 | Los eventos de React se nombran usando camelCase, en vez de minúsculas.
6 | Con JSX pasas una función como el manejador del evento, en vez de un string.
7 |
8 | Por ejemplo, el HTML:
9 |
10 | ```js
11 |
12 | Activate Lasers
13 |
14 | ```
15 |
16 |
17 | En React es algo diferente:
18 |
19 | ```js
20 |
21 | Activate Lasers
22 |
23 | ```
24 |
25 | Otra diferencia es que en React no puedes retornar false para prevenir el comportamiento por defecto. Debes, explícitamente, llamar preventDefault. Por ejemplo, en un HTML plano, para prevenir el comportamiento por defecto de un enlace de abrir una nueva página, puedes escribir:
26 |
27 | ```js
28 |
29 | Click me
30 |
31 | ```
32 |
33 | En cambio en React, esto podría ser:
34 | ```js
35 | function ActionLink() {
36 | function handleClick(e) {
37 | e.preventDefault();
38 | console.log('The link was clicked.');
39 | }
40 |
41 | return (
42 |
43 | Click me
44 |
45 | );
46 | }
47 | ```
48 |
49 | Cuando estás utilizando React, generalmente, no debes llamar addEventListener para agregar escuchadores de eventos a un elemento del DOM después de que este es creado. Por el contrario, solo debes proveer un manejador de eventos cuando el elemento es inicialmente renderizado.
50 |
51 | Cuando defines un componente usando una clase de ES6, un patrón muy común es que los manejadores de eventos sean un método de la clase. Por ejemplo, este componente Toggle renderiza un botón que permite al usuario cambiar el estado entre “ENCENDIDO” y “APAGADO”:
52 |
53 | ```js
54 | class Toggle extends React.Component {
55 | constructor(props) {
56 | super(props);
57 | this.state = {isToggleOn: true};
58 |
59 | // Este enlace es necesario para hacer que `this` funcione en el callback
60 | this.handleClick = this.handleClick.bind(this);
61 | }
62 |
63 | handleClick() {
64 | this.setState(state => ({
65 | isToggleOn: !state.isToggleOn
66 | }));
67 | }
68 |
69 | render() {
70 | return (
71 |
72 | {this.state.isToggleOn ? 'ON' : 'OFF'}
73 |
74 | );
75 | }
76 | }
77 | ```
78 |
79 | Tienes que tener mucho cuidado en cuanto al significado de this en los callbacks de JSX. En JavaScript, los métodos de clase no están ligados por defecto. Si olvidas ligar this.handleClick y lo pasas a onClick, this será undefined cuando se llame la función.
80 |
81 | Esto no es un comportamiento especifico de React; esto hace parte de como operan las funciones JavaScript. Generalmente, si refieres un método sin usar () después de este, tal como onClick={this.handleClick}, deberías ligar ese método.
82 |
83 | Si te molesta llamar bind, existen dos maneras de evitarlo. Si usas la sintaxis experimental campos públicos de clases, puedes usar los campos de clases para ligar los callbacks correctamente:
84 |
85 | ```js
86 | class LoggingButton extends React.Component {
87 | // Esta sintaxis nos asegura que `this` está ligado dentro de handleClick
88 | // Peligro: esto es una sintaxis *experimental*
89 | handleClick = () => {
90 | console.log('this is:', this);
91 | }
92 |
93 | render() {
94 | return (
95 |
96 | Click me
97 |
98 | );
99 | }
100 | }
101 | ```
102 |
103 | Si no estas usando la sintaxis de campos públicos de clases, puedes usar una función flecha en el callback:
104 | ```js
105 | class LoggingButton extends React.Component {
106 | handleClick() {
107 | console.log('this is:', this);
108 | }
109 |
110 | render() {
111 | // Esta sintaxis nos asegura que `this` esta ligado dentro de handleClick
112 | return (
113 | this.handleClick(e)}>
114 | Click me
115 |
116 | );
117 | }
118 | }
119 | ```
120 |
121 | El problema con esta sintaxis es que un callback diferente es creado cada vez que LogginButton es renderizado. En la mayoría de los casos, esto está bien. Sin embargo, si este callback se pasa como una propiedad a componentes más bajos, estos componentes podrían renderizarse nuevamente. Generalmente, recomendamos ligar en el constructor o usar la sintaxis de campos de clases, para evitar esta clase de problemas de rendimiento.
122 |
123 | ## Pasando argumentos a escuchadores de eventos
124 |
125 | Dentro de un bucle es muy común querer pasar un parámetro extra a un manejador de eventos. Por ejemplo, si id es el ID de una fila, cualquiera de los códigos a continuación podría funcionar:
126 |
127 | ```js
128 | this.deleteRow(id, e)}>Delete Row
129 | Delete Row
130 | ```
131 |
132 | Las dos líneas anteriores son equivalentes, y utilizan funciones flecha y Function.prototype.bind respectivamente.
133 |
134 | En ambos casos, el argumento e que representa el evento de React va a ser pasado como un segundo argumento después del ID. Con una función flecha, tenemos que pasarlo explícitamente, pero con bind cualquier argumento adicional es pasado automáticamente
135 |
136 | ## Ejercicios:
137 |
138 | 1. Crear un componente de clases que al pulsar un boton llame a `https://jsonplaceholder.typicode.com/todos/1` y pinte el JSON.
139 | 2. Dada la siguiente lista pintar el número del item seleccionado en una etiqueta ``:
140 |
141 | ```
142 |
143 | Primera opción
144 | Segunda opción
145 | Tercera opción
146 | Cuarta opción
147 |
148 | ```
149 | 3. Crear un componente que sea un campo de texto que cuando tenga el `focus` muestre el mensaje "dentro" y que cuando pierda el focus muestre un mensaje "fuera"
150 | 4. Crear un componente que muestre el `value` seleccionado de una etiqueta ``
151 |
152 | [<- Volver al índice](./../README.md)
153 |
--------------------------------------------------------------------------------
/modulo2/state.md:
--------------------------------------------------------------------------------
1 | # El estado
2 |
3 | Aquí introduciremos el concepto de estado en un componente de React.
4 |
5 | ```jsx
6 | const Counter = props => {
7 |
8 | let count = 0
9 |
10 | let increment = () => {
11 | count += 1
12 | }
13 |
14 | return (
15 | <>
16 | {count}
17 | Increment
18 | >
19 | )
20 | }
21 | ```
22 |
23 | Para actualizar la interfaz la única manera que hay es através del `state`. Para poder utilizar el estado de un componente y hacer que sea dinámico el componente tiene que ser una clase. Para pasar cualquier componente funcional a uno de clase solo hay que seguir los siguientes pasos:
24 |
25 | 1. Crear una clase ES6 con el mismo nombre que herede de `React.Component`.
26 | 2. Agregar un único método vacío llamado `render()`.
27 | 3. Mover el cuerpo de la función al método `render()`.
28 | 4. Reemplazar props con `this.props` en el cuerpo de `render()`.
29 | 5. Borrar el resto de la declaración de la función ya vacía.
30 |
31 | Quedaría de la siguiente manera:
32 |
33 | ```js
34 | class Counter extends React.Component {
35 | render() {
36 | return (
37 | <>
38 | {count}
39 | Increment
40 | >
41 | )
42 | }
43 | }
44 | ```
45 |
46 | Una vez que tenemos una clase podemos usar el `state`. Para agregar estado a una clase basta con añadirlo como propiedad:
47 |
48 | ```js
49 | class Counter extends React.Component {
50 |
51 | state = {
52 | count: 0
53 | }
54 |
55 | render() {
56 | return (
57 | <>
58 | {count}
59 | Increment
60 | >
61 | )
62 | }
63 | }
64 | ```
65 |
66 | Por lo que en vez de usar `count` usaremos `this.state.count`:
67 |
68 | ```js
69 | class Counter extends React.Component {
70 |
71 | state = {
72 | count: 0
73 | }
74 |
75 | render() {
76 | return (
77 | <>
78 | {this.state.count}
79 | Increment
80 | >
81 | )
82 | }
83 | }
84 | ```
85 |
86 | El estado **NUNCA** se debe modificar usando `this.state.count = 2`. Existe una función llamada `this.setState` que pasandole un objeto con lo que queremos modificar nos actualizará el estado y se encargará de actualizar la interfaz.
87 |
88 | Por lo que quedaría asi:
89 |
90 | ```js
91 | class Counter extends React.Component {
92 |
93 | state = {
94 | count: 0
95 | }
96 |
97 | increment() {
98 | this.setState({ count: this.state.count + 1 })
99 | }
100 |
101 | render() {
102 | return (
103 | <>
104 | {this.state.count}
105 | this.increment()}>Increment
106 | >
107 | )
108 | }
109 | }
110 | ```
111 |
112 | ## Las actualizaciones del estado pueden ser asíncronas
113 |
114 | React puede agrupar varias invocaciones a setState() en una sola actualización para mejorar el rendimiento.
115 |
116 | Debido a que this.props y this.state pueden actualizarse de forma asincrónica, no debes confiar en sus valores para calcular el siguiente estado.
117 |
118 | Por ejemplo, el código anterior puede fallar en actualizar el contador. Para arreglarlo, usa una segunda forma de `setState()` que acepta una función en lugar de un objeto. Esa función recibirá el estado previo como primer argumento, y las props en el momento en que se aplica la actualización como segundo argumento:
119 |
120 | ```js
121 | this.setState(state => ({
122 | count: state.count + 1
123 | }))
124 | ```
125 |
126 | ## Las actualizaciones de estado se fusionan
127 |
128 | Cuando invocas a setState(), React combina el objeto que proporcionaste con el estado actual.
129 |
130 | Por ejemplo, tu estado puede contener varias variables independientes:
131 | ```js
132 | state = {
133 | posts: [],
134 | comments: []
135 | }
136 | ```
137 |
138 | Luego puedes actualizarlas independientemente con invocaciones separadas a setState():
139 |
140 | ```js
141 | fetchPosts().then(response => {
142 | this.setState({
143 | posts: response.posts
144 | })
145 | })
146 |
147 | fetchComments().then(response => {
148 | this.setState({
149 | comments: response.comments
150 | })
151 | })
152 | ```
153 |
154 | ## Los datos fluyen hacia abajo
155 |
156 | Ni los componentes padres o hijos pueden saber si un determinado componente tiene o no tiene estado y no les debería importar si se define como una función o una clase.
157 |
158 | Por eso es que el estado a menudo se le denomina local o encapsulado. No es accesible desde otro componente excepto de aquel que lo posee y lo asigna.
159 |
160 | Un componente puede elegir pasar su estado como props a sus componentes hijos:
161 |
162 | ```js
163 | It is {this.state.date.toLocaleTimeString()}.
164 | ```
165 |
166 | Esto también funciona para componentes definidos por el usuario:
167 |
168 | ```js
169 | function FormattedDate(props) {
170 | return It is {props.date.toLocaleTimeString()}. ;
171 | }
172 |
173 | ```
174 |
175 | **Ejercicios:**
176 |
177 | 1. Vamos a tener un campo de texto donde vamos a poder escribir y por otro lado vamos a tener una etiqueta `` donde mostraremos el texto que va escribiendo el usuario
178 | 2. Vamos a tener un campo de texto donde poder meter nuestra contraseña. Al lado tiene que haber un botón que si pulsamos en el se mostrará la contraseña en claro.
179 | 3. Crear un componente de clase que muestre un contador y tenga dos botones: uno de incrementar y otro de decrementar.
180 | 4. Crear un componente de clase que muestre un `string` vacio y tenga dos botones: uno de incrementar y otro de decrementar. Cuandos se pinche el de incrementar añadirá una letra al string y cuando se pinche en el de decrementar quitará una letra.
181 | 5. Vamos a tener un botón que al pinchar activará un cronómetro (de segundo en segundo) e irá mostrandose en pantalla los segundos
182 | 6. Vamos a tener el típico boton de likes (el valor de likes estará inicializado a 50). Sólo se puede dar like una vez.
183 | 7. Refactorizar el primer ejemplo para que:
184 | 1. Los botones sean componentes a parte.
185 | 2. El texto del contador sea un componente a parte
186 | 3. El componente Counter sea quien tenga el estado y las funciones que lo actualizan y se lo pase a sus hijos.
187 |
188 |
189 | [<- Volver al índice](./../README.md)
190 |
--------------------------------------------------------------------------------
/modulo2/forms.md:
--------------------------------------------------------------------------------
1 | # Formularios en React
2 |
3 | Los elementos de formularios en HTML funcionan un poco diferente a otros elementos del DOM en React, debido a que los elementos de formularios conservan naturalmente algún estado interno. Por ejemplo, este formulario solamente en HTML, acepta un solo nombre.
4 |
5 | ```js
6 |
7 |
8 | Name:
9 |
10 |
11 |
12 |
13 | ```
14 |
15 | Este formulario tiene el comportamiento predeterminado en HTML que consiste en navegar a una nueva página cuando el usuario envía el formulario. Si deseas este comportamiento en React, simplemente ya funciona así. Pero en la mayoría de casos, es conveniente tener una función en Javascript que se encargue del envío del formulario, y que tenga acceso a los datos que el usuario introdujo en el formulario. La forma predeterminada para conseguir esto es una técnica llamada “componentes controlados”.
16 |
17 | ## Componentes controlados
18 |
19 |
20 | En HTML, los elementos de formularios como los ` `, `` y el `` normalmente mantienen sus propios estados y los actualizan de acuerdo a la interacción del usuario. En React, el estado mutable es mantenido normalmente en la propiedad estado de los componentes, y solo se actualiza con setState().
21 |
22 | Podemos combinar ambos haciendo que el estado de React sea la “única fuente de la verdad”. De esta manera, los componentes React que rendericen un formulario también controlan lo que pasa en ese formulario con las subsecuentes entradas del usuario. Un campo de un formulario cuyos valores son controlados por React de esta forma es denominado “componente controlado”.
23 |
24 | Por ejemplo, si queremos hacer que el ejemplo anterior muestre el nombre que esta siendo suministrado, podemos escribir el formulario como un componente controlado:
25 |
26 | ```js
27 | class NameForm extends React.Component {
28 |
29 | state = {
30 | value: ''
31 | }
32 |
33 | handleChange = (event) => {
34 | this.setState({value: event.target.value})
35 | }
36 |
37 | handleSubmit = (event) => {
38 | alert('A name was submitted: ' + this.state.value)
39 | event.preventDefault()
40 | }
41 |
42 | render() {
43 | return (
44 |
45 |
46 | Name:
47 |
48 |
49 |
50 |
51 | )
52 | }
53 | }
54 |
55 | ```
56 |
57 | ## La etiqueta textarea
58 |
59 | En HTML, el elemento `` define su texto por sus hijos:
60 |
61 | ```js
62 |
63 | Hello there, this is some text in a text area
64 |
65 | ```
66 |
67 | En React, un `` utiliza un atributo value en su lugar. De esta manera, un formulario que hace uso de un `` puede ser escrito de manera similar a un formulario que utiliza un campo en una sola línea:
68 |
69 | ```js
70 |
71 | ```
72 |
73 |
74 | ## La etiqueta select
75 |
76 | En HTML, `` crea una lista desplegable. Por ejemplo, este HTML crea una lista desplegable de sabores:
77 |
78 | ```js
79 |
80 | Grapefruit
81 | Lime
82 | Coconut
83 | Mango
84 |
85 | ```
86 | Ten en cuenta que la opción Coco es inicialmente seleccionada, debido al atributo selected. React, en lugar de utilizar el atributo selected, utiliza un atributo value en la raíz de la etiqueta select. Esto es más conveniente en un componente controlado debido a que solo necesitas actualizarlo en un solo lugar, por ejemplo:
87 |
88 | ```js
89 |
90 | Grapefruit
91 | Lime
92 | Coconut
93 | Mango
94 |
95 | ```
96 |
97 |
98 | ## Manejando múltiples inputs
99 |
100 | Cuando necesitas manejar múltiples elementos input controlados, puedes agregar un atributo name a cada uno de los elementos y dejar que la función controladora decida que hacer basada en el valor de event.target.name.
101 |
102 | ```js
103 |
109 |
110 | handleInputChange(event) {
111 | const target = event.target
112 | const value = target.type === 'checkbox' ? target.checked : target.value
113 | const name = target.name
114 |
115 | this.setState({
116 | [name]: value
117 | })
118 | }
119 |
120 | ```
121 |
122 | ## Ejercicios:
123 |
124 | 1. Crear un componente `Select` que pemita como props:
125 | * value
126 | * items
127 | * onChange
128 |
129 | 2. Crear un componente que sea un campo de texto y muestre un mensaje de error en caso de que el texto introducido no sea "A tope con React"
130 |
131 | 3. Crear un formulario de login (username y password) cumpliendo lo siguiente:
132 | * debe validar que la password tenga al menos 8 caracteres
133 | * el boton de login debe estar deshabilitado si no hay datos o la contraseña no tiene al menos 8 caracteres
134 | * cuando se envíe el formulario debe loggear los datos en la consola
135 |
136 | 4. Crear un componente que muestre lo fuerte que es una contraseña. Para ello vamos a definir las siguientes reglas:
137 | * Si tiene al menos 8 caracteres: +1 punto
138 | * Si tiene algún número: +1 punto
139 | * Si tiene alguna letra en mayúscula: +1 punto
140 | * Si tiene alguno de estos símbolos: $ % & / ( ) + -: +1 punto
141 | * Si tiene 1 punto la contraseña es débil
142 | * Si tiene 2 o 3 puntos la contraseña es normal
143 | * Si tiene 4 puntos la contraseña es fuerte
144 |
145 | 5. Crear un formulario donde vamos a rellenar informacion de un usuario. Al pulsar el boton o pulsar el enter debería de sacar un mensaje en la consola con todos los datoss. El formulario debe contener los siguientes campos:
146 |
147 | * Name
148 | * Firstname
149 | * Description (textarea)
150 | * Gender (radiobutton)
151 | * Age
152 | * Country (Spain, USA)
153 | * Province (Guadalajara, Madrid en caso de haber seleccionado Spain como pais)
154 | * Hobbies (**Checbox**:Games, Football, Basketball, Art)
155 |
156 |
157 | [<- Volver al índice](./../README.md)
158 |
--------------------------------------------------------------------------------
/modulo3/hooks.md:
--------------------------------------------------------------------------------
1 | # Hooks
2 |
3 | Hooks son una nueva característica en React 16.8. Estos te permiten usar el estado y otras características de React sin escribir una clase.
4 |
5 | Antes escribiríamos algo así:
6 | ```js
7 | class Example extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | count: 0
12 | };
13 | }
14 |
15 | render() {
16 | return (
17 |
18 |
You clicked {this.state.count} times
19 |
this.setState({ count: this.state.count + 1 })}>
20 | Click me
21 |
22 |
23 | );
24 | }
25 | }
26 | ```
27 |
28 | Con hooks quedaría así:
29 |
30 | ```js
31 | import React, { useState } from 'react';
32 |
33 | function Example() {
34 | // Declaración de una variable de estado que llamaremos "count"
35 | const [count, setCount] = useState(0);
36 |
37 | return (
38 |
39 |
You clicked {count} times
40 |
setCount(count + 1)}>
41 | Click me
42 |
43 |
44 | );
45 | }
46 | ```
47 |
48 | ## Declarando múltiples variables de estado
49 |
50 | ```js
51 | Puedes usar el Hook de estado más de una vez en un mismo componente:
52 |
53 | function ExampleWithManyStates() {
54 | // Declarar múltiple variables de estado!
55 | const [age, setAge] = useState(42);
56 | const [fruit, setFruit] = useState('banana');
57 | const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
58 | // ...
59 | }
60 | ```
61 |
62 | ## useEffect()
63 |
64 | El Hook de efecto, useEffect, agrega la capacidad de realizar efectos secundarios desde un componente funcional. Tiene el mismo propósito que componentDidMount,componentDidUpdate y componentWillUnmount en las clases React, pero unificadas en una sola API.
65 |
66 | Con clases:
67 |
68 | ```js
69 | class Example extends React.Component {
70 | constructor(props) {
71 | super(props);
72 | this.state = {
73 | count: 0
74 | };
75 | }
76 |
77 | componentDidMount() {
78 | document.title = `You clicked ${this.state.count} times`;
79 | }
80 |
81 | componentDidUpdate() {
82 | document.title = `You clicked ${this.state.count} times`;
83 | }
84 |
85 | render() {
86 | return (
87 |
88 |
You clicked {this.state.count} times
89 |
this.setState({ count: this.state.count + 1 })}>
90 | Click me
91 |
92 |
93 | );
94 | }
95 | }
96 | ```
97 |
98 | Con hooks:
99 |
100 | ```js
101 | import React, { useState, useEffect } from 'react';
102 |
103 | function Example() {
104 | const [count, setCount] = useState(0);
105 |
106 | useEffect(() => {
107 | document.title = `You clicked ${count} times`;
108 | });
109 |
110 | return (
111 |
112 |
You clicked {count} times
113 |
setCount(count + 1)}>
114 | Click me
115 |
116 |
117 | );
118 | }
119 | ```
120 |
121 | **¿Qué hace useEffect?** Al usar este Hook, le estamos indicando a React que el componente tiene que hacer algo después de renderizarse. React recordará la función que le hemos pasado (nos referiremos a ella como nuestro “efecto”), y la llamará más tarde después de actualizar el DOM. En este efecto, actualizamos el título del documento, pero también podríamos hacer peticiones de datos o invocar alguna API imperativa.
122 |
123 | **¿Por qué se llama a useEffect dentro del componente?** Poner useEffect dentro del componente nos permite acceder a la variable de estado count (o a cualquier prop) directamente desde el efecto. No necesitamos una API especial para acceder a ella, ya que se encuentra en el ámbito de la función. Los Hooks aprovechan los closures de JavaScript y evitan introducir APIs específicas de React donde JavaScript ya proporciona una solución.
124 |
125 | **¿Se ejecuta useEffect después de cada renderizado?** ¡Sí! Por defecto se ejecuta después del primer renderizado y después de cada actualización. Más tarde explicaremos cómo modificar este comportamiento. En vez de pensar en términos de “montar” y “actualizar”, puede resultarte más fácil pensar en efectos que ocurren “después del renderizado”. React se asegura de que el DOM se ha actualizado antes de llevar a cabo el efecto.
126 |
127 |
128 | ## Como conseguir el mismo funcionamiento que los ciclos de vida:
129 |
130 | ### componentDidMount():
131 |
132 |
133 | ```js
134 | class Example extends React.Component {
135 | componentDidMount() {
136 | console.log('I am mounted!');
137 | }
138 | render() {
139 | return null;
140 | }
141 | }
142 | ```
143 |
144 |
145 | ```js
146 | function Example() {
147 | useEffect(() => console.log('mounted'), []);
148 | return null;
149 | }
150 | ```
151 |
152 | ### componentDidUpdate();
153 |
154 | ```js
155 | componentDidUpdate() {
156 | console.log('mounted or updated');
157 | }
158 | ```
159 |
160 | ```js
161 | useEffect(() => console.log('mounted or updated'));
162 | ```
163 |
164 | ### componentWillUnmount():
165 |
166 | ```js
167 | componentWillUnmount() {
168 | console.log('will unmount');
169 | }
170 | ```
171 |
172 | ```js
173 | useEffect(() => {
174 | return () => {
175 | console.log('will unmount');
176 | }
177 | }, []);
178 | ```
179 |
180 | ## Construyendo tus propios Hooks
181 |
182 | Construir tus propios Hooks te permite extraer la lógica del componente en funciones reutilizables.
183 |
184 | ```js
185 | function useCallAPI(url) {
186 | const [data, setData] = useState([])
187 |
188 | const getData = async (url) => {
189 | let response = await fetch(url)
190 | let data = await response.json()
191 | setData(data.results)
192 | }
193 |
194 | useEffect(() => {
195 | getData(url)
196 | }, [])
197 |
198 | return data
199 | }
200 | ```
201 |
202 | Después lo podemos usar así:
203 |
204 | ```js
205 | function App() {
206 |
207 | const data = useCallAPI('http://www.mocky.io/v2/5d965a7233000003cd2f9091')
208 |
209 | return (
210 | <>
211 |
212 | >
213 | );
214 | }
215 | ```
216 |
217 |
218 | ### Ejercicios:
219 |
220 | 1. Crear un componente funcional que sea un campo de texto y cuando escribamos algo cambie las "a" por las "b"
221 | 2. Crear un componente funcional que cuando desaparezca imprima por consola "Desmontado!"
222 | 3. Crear un componente funcional que cuando reciba nuevas props del padre imprima por consola "Actualizando!"."
223 | 4. Crear un componente funcional que llame a la API https://jsonplaceholder.typicode.com/todos y pinte las tareas.
224 | 6. Crear un componente funcional que sea un campo de texto que muestre un error cuando el texo introducido no sea "zamarro". Controlar si el componente está "dirty".
225 | 5. Extrar la lógica de la llamada a la API a un hook personalizado y usarlo.
226 |
227 | [<- Volver al índice](./../README.md)
228 |
--------------------------------------------------------------------------------
/modulo3/context.md:
--------------------------------------------------------------------------------
1 | # React Context
2 |
3 | Context provee una forma de pasar datos a través del árbol de componentes sin tener que pasar props manualmente en cada nivel.
4 |
5 | En una aplicación típica de React, los datos se pasan de arriba hacia abajo (de padre a hijo) a través de props, pero esto puede ser complicado para ciertos tipos de props (por ejemplo, localización, el tema de la interfaz) que son necesarios para muchos componentes dentro de una aplicación. Context proporciona una forma de compartir valores como estos entre componentes sin tener que pasar explícitamente un prop a través de cada nivel del árbol.
6 |
7 | ## Cuándo usar Context
8 |
9 | Context está diseñado para compartir datos que pueden considerarse “globales” para un árbol de componentes en React, como el usuario autenticado actual, el tema o el idioma preferido. Por ejemplo, en el código a continuación, pasamos manualmente un prop de “tema” para darle estilo al componente Button:
10 |
11 | ```js
12 | class App extends React.Component {
13 | render() {
14 | return ;
15 | }
16 | }
17 |
18 | function Toolbar(props) {
19 | // El componente Toolbar debe tener un prop adicional "theme"
20 | // y pasarlo a ThemedButton. Esto puede llegar a ser trabajoso
21 | // si cada botón en la aplicación necesita saber el tema,
22 | // porque tendría que pasar a través de todos los componentes.
23 | return (
24 |
25 |
26 |
27 | );
28 | }
29 |
30 | class ThemedButton extends React.Component {
31 | render() {
32 | return ;
33 | }
34 | }
35 | ```
36 |
37 | Usando Context podemos evitar pasar props a través de elementos intermedios:
38 |
39 | ```js
40 | // Context nos permite pasar un valor a lo profundo del árbol de componentes
41 | // sin pasarlo explícitamente a través de cada componente.
42 | // Crear un Context para el tema actual (con "light" como valor predeterminado).
43 | const ThemeContext = React.createContext('light');
44 |
45 | class App extends React.Component {
46 | render() {
47 | // Usa un Provider para pasar el tema actual al árbol de abajo.
48 | // Cualquier componente puede leerlo, sin importar qué tan profundo se encuentre.
49 | // En este ejemplo, estamos pasando "dark" como valor actual.
50 | return (
51 |
52 |
53 |
54 | );
55 | }
56 | }
57 |
58 | // Un componente en el medio no tiene que
59 | // pasar el tema hacia abajo explícitamente.
60 | function Toolbar(props) {
61 | return (
62 |
63 |
64 |
65 | );
66 | }
67 |
68 | class ThemedButton extends React.Component {
69 | // Asigna un contextType para leer el contexto del tema actual.
70 | // React encontrará el Provider superior más cercano y usará su valor.
71 | // En este ejemplo, el tema actual es "dark".
72 | static contextType = ThemeContext;
73 | render() {
74 | return ;
75 | }
76 | }
77 | ```
78 |
79 | ## API
80 |
81 | **React.createContext**
82 |
83 | ```js
84 | const MyContext = React.createContext(defaultValue);
85 | ```
86 |
87 | Crea un objeto Context. Cuando React renderiza un componente que se suscribe a este objeto Context, este leerá el valor de contexto actual del Provider más cercano en el árbol.
88 |
89 | El argumento defaultValue es usado únicamente cuando un componente no tiene un Provider superior a él en el árbol. Esto puede ser útil para probar componentes de forma aislada sin contenerlos. Nota: pasar undefined como valor al Provider no hace que los componentes que lo consumen utilicen defaultValue.
90 |
91 | **Context.Provider**
92 |
93 | ```js
94 |
95 | ```
96 |
97 | Cada objeto Context viene con un componente Providers de React que permite que los componentes que lo consumen se suscriban a los cambios del contexto.
98 |
99 | Acepta un prop value que se pasará a los componentes consumidores que son descendientes de este Provider. Un Provider puede estar conectado a muchos consumidores. Los Providers pueden estar anidados para sobreescribir los valores más profundos dentro del árbol.
100 |
101 | Todos los consumidores que son descendientes de un Provider se vuelven a renderizar cada vez que cambia el prop value del Provider. La propagación del Provider a sus consumidores descendientes no está sujeta al método shouldComponentUpdate, por lo que el consumidor se actualiza incluso cuando un componente padre evita la actualización.
102 |
103 | Los cambios se determinan comparando los valores nuevos y antiguos utilizando el mismo algoritmo que Object.is.
104 |
105 | ## Actualizando Context desde un componente anidado
106 |
107 | A menudo es necesario actualizar el contexto desde un componente que está anidado en algún lugar del árbol de componentes. En este caso, puedes pasar una función a través del contexto para permitir a los consumidores actualizar el contexto:
108 | ```js
109 | theme-context.js
110 |
111 | // Make sure the shape of the default value passed to
112 | // createContext matches the shape that the consumers expect!
113 | export const ThemeContext = React.createContext({
114 | theme: themes.dark,
115 | toggleTheme: () => {},
116 | });
117 | ```
118 | ```js
119 | theme-toggler-button.js
120 |
121 | import {ThemeContext} from './theme-context';
122 |
123 | function ThemeTogglerButton() {
124 | // The Theme Toggler Button receives not only the theme
125 | // but also a toggleTheme function from the context
126 | return (
127 |
128 | {({theme, toggleTheme}) => (
129 |
132 | Toggle Theme
133 |
134 | )}
135 |
136 | );
137 | }
138 |
139 | export default ThemeTogglerButton;
140 | ```
141 | ```js
142 | app.js
143 |
144 | import {ThemeContext, themes} from './theme-context';
145 | import ThemeTogglerButton from './theme-toggler-button';
146 |
147 | class App extends React.Component {
148 | constructor(props) {
149 | super(props);
150 |
151 | this.toggleTheme = () => {
152 | this.setState(state => ({
153 | theme:
154 | state.theme === themes.dark
155 | ? themes.light
156 | : themes.dark,
157 | }));
158 | };
159 |
160 | // State also contains the updater function so it will
161 | // be passed down into the context provider
162 | this.state = {
163 | theme: themes.light,
164 | toggleTheme: this.toggleTheme,
165 | };
166 | }
167 |
168 | render() {
169 | // The entire state is passed to the provider
170 | return (
171 |
172 |
173 |
174 | );
175 | }
176 | }
177 |
178 | function Content() {
179 | return (
180 |
181 |
182 |
183 | );
184 | }
185 |
186 | ReactDOM.render( , document.root);
187 | ```
188 |
189 |
190 | ## Ejercicio
191 |
192 | 1. Queremos tener una aplicación que soporte dos idiomas. Para ello tendremos:
193 | * App: debe contener el estado y el context
194 | * Header: desde este componente se cambiará el idioma de la aplicación
195 | * Content:
196 | - Text: que muestre un texto en el idioma seleccionado. **DEBE SER UNA CLASE**
197 | - Button: que muestre un texto en el idioma seleccionado. **DEBE SER UNA FUNCIÓN**
198 |
199 |
200 | [<- Volver al índice](./../README.md)
201 |
--------------------------------------------------------------------------------
/testing/enzyme.md:
--------------------------------------------------------------------------------
1 | # Enzyme
2 |
3 | ## Instalar como dependencia de desarrollo
4 |
5 | `npm i --save-dev enzyme enzyme-adapter-react-16`
6 |
7 | Una vez instalado necesitamos usar el adaptador de enzyme. Lo más fácil es añadirlo en el `src/setupTests.js`
8 |
9 | ```js
10 | import Enzyme from 'enzyme';
11 | import Adapter from 'enzyme-adapter-react-16';
12 |
13 | Enzyme.configure({ adapter: new Adapter() });
14 | ```
15 |
16 |
17 | ## Shallow
18 |
19 | * No renderiza los hijos
20 | * Es perfecto si queremos testear solo ese componente
21 | * No pasa por componentDidMount ni por componentDidUpdate
22 |
23 | Dado este componente:
24 |
25 | ```js
26 | import React from 'react'
27 |
28 | const Greeting = props => {props.children}
29 |
30 | export default Greeting
31 | ```
32 |
33 | Podemos testearlo así:
34 |
35 | ```js
36 | import React from 'react'
37 | import { shallow } from 'enzyme'
38 |
39 | import Greeting from './Greeting'
40 |
41 | it('Should renders without errors', () => {
42 | shallow( )
43 | })
44 |
45 | it('Should renders an h1 tag', () => {
46 | const wrapper = shallow( )
47 | expect(wrapper.find('h1').length).toBe(1)
48 | })
49 |
50 | it('Should renders children when passed', () => {
51 | const wrapper = shallow(Hola )
52 | expect(wrapper.contains('Hola')).toBe(true)
53 | })
54 | ```
55 |
56 | Si el componente fuese así:
57 |
58 | ```js
59 | import React from 'react'
60 | import Link from './Link'
61 |
62 | const Greeting = props =>
63 |
64 | export default Greeting
65 | ```
66 |
67 | El siguiente test fallaría ya que no renderiza todos los hijos:
68 |
69 | ```js
70 | it('Should renders an h1 tag', () => {
71 | const wrapper = shallow( )
72 | expect(wrapper.find('a').length).toBe(1)
73 | })
74 | ```
75 |
76 | Para hacer funcionar ese test deberíamos usar `mount`.
77 |
78 | ## Mount
79 |
80 | * Renderiza todos los hijos
81 | * Suele tardar mas en ejecutarse el test
82 | * Pasa por todos los ciclos de vida
83 |
84 |
85 | ```js
86 | it('Should renders an h1 tag', () => {
87 | const wrapper = mount( )
88 | expect(wrapper.find('a').length).toBe(1)
89 | })
90 | ```
91 |
92 | ## Render
93 |
94 | * Solo pasa por el render pero renderiza todos los hijos
95 |
96 | ## ¿Qué usar en cada caso?
97 |
98 | 1. Siempre empezar con `shallow`
99 | 2. Si tienes que testear el componentDidMount o el componentDidUpdate usar `mount`
100 | 3. Si quieres testear algo del ciclo de vida usa `mount`
101 | 4. Si quieres testear todos los hijos del componente pero te da igual los métodos del ciclo de vida usa `render`
102 |
103 | ## Métodos más usados
104 |
105 | 1. `at(index)` devuelve un wrapper del nodo dada la posición pasada como parámetro
106 |
107 | ```js
108 | const wrapper = shallow(Hola )
109 | expect(wrapper.find('h1').at(0).contains('Hola')).toBe(true)
110 | expect(wrapper.find('h1').at(1).contains('adios')).toBe(true)
111 | ```
112 |
113 | 2. `childAt(index)` devuelve un wrapper del hijo especificado por parámetro
114 |
115 | ```js
116 | const wrapper = shallow( );
117 | expect(wrapper.find('ul').childAt(0).type()).to.equal('li');
118 | ```
119 |
120 | 3. `children()` devuelve un wrapper con todos los hijos del wrapper
121 |
122 | ```js
123 | const wrapper = shallow( );
124 | expect(wrapper.find('ul').children()).to.have.lengthOf(items.length);
125 | ```
126 |
127 | 4. `contains(nodeOrNodes) bool`
128 |
129 | ```js
130 | const wrapper = shallow(Hola )
131 | expect(wrapper.contains('Hola')).toBe(true)
132 | ```
133 |
134 | 5. `html() string` devuelve el html de un nodo como string
135 |
136 | ```js
137 | const wrapper = shallow(Hola )
138 | expect(wrapper.html()).toBe("Hola ")
139 | ```
140 |
141 | 6. `props() object` devuelve las props que se le pasan al *componente raiz del wrapper*
142 |
143 | ```js
144 | const wrapper = shallow( ,)
145 | console.log(wrapper.props()) //{ text: 'hola' }
146 | expect(wrapper.props().text).toBe("hola")
147 | ```
148 |
149 | 7. `setProps` es útil para testear el comportamiento del componente cuando cambian sus props
150 |
151 | ```js
152 | const wrapper = shallow( ,)
153 | expect(wrapper.props().text).toBe("hola")
154 | wrapper.setProps({ text: 'adios' })
155 | expect(wrapper.props().text).toBe("adios")
156 | ```
157 |
158 | 8. `instance() ReactComponent` devuelve la instancia de la clase y podemos acceder a sus propiedades
159 |
160 | ```js
161 | const wrapper = shallow( ,)
162 | expect(wrapper.find('div').text()).toBe('')
163 | wrapper.instance().increment()
164 | expect(wrapper.find('div').text()).toBe('a')
165 | ```
166 |
167 | 9. `state() object` devuelve un objeto con los datos del estado
168 |
169 | ```js
170 | const wrapper = shallow( ,)
171 | expect(wrapper.state().text).toBe('')
172 | wrapper.instance().increment()
173 | expect(wrapper.state().text).toBe('a')
174 | ```
175 |
176 | 10. `simulate(event, data)` simula un evento en el nodo raíz del wrapper
177 |
178 | ```js
179 | const wrapper = shallow( ,)
180 | wrapper.find('button').at(0).simulate('click')
181 | expect(wrapper.state().text).toBe('a')
182 | ```
183 |
184 | ## Testeando una clase
185 |
186 | Teniendo este componente:
187 |
188 | ```js
189 | import React, { Component } from 'react'
190 | import Title from './Title'
191 | import Button from './Button'
192 |
193 | class Counter extends Component {
194 |
195 | state = {
196 | count: 0,
197 | }
198 |
199 | handleChange = action => {
200 | this.setState(prevState =>
201 | ({ count: action === 'increment' ? prevState.count + 1 : prevState.count - 1 })
202 | )
203 | }
204 |
205 | render() {
206 | return (
207 | <>
208 |
209 | this.handleChange('increment')} label="Increment"/>
210 | this.handleChange('decrement')} label="Decrement"/>
211 | >
212 | )
213 | }
214 | }
215 |
216 | export default Counter
217 | ```
218 |
219 | Podríamos escribir los siguientes tests:
220 |
221 | ```js
222 | import React from 'react'
223 | import { shallow, mount } from 'enzyme'
224 | import Counter from './Counter'
225 |
226 | test('Should renders without errors', () => {
227 | shallow( )
228 | })
229 |
230 | test('Should start in 0', () => {
231 | const wrapper = shallow( )
232 | expect(wrapper.state().count).toBe(0)
233 | })
234 |
235 | test('Should change count to 1', () => {
236 | const wrapper = shallow( )
237 | wrapper.instance().handleChange('increment')
238 | expect(wrapper.state().count).toBe(1)
239 | })
240 |
241 | test('Should change count to -1', () => {
242 | const wrapper = shallow( )
243 | wrapper.instance().handleChange('decrement')
244 | expect(wrapper.state().count).toBe(-1)
245 | })
246 |
247 | test('Should call to increment when click on button', () => {
248 | const wrapper = mount( )
249 | wrapper.find('button').at(0).simulate('click')
250 | expect(wrapper.state().count).toBe(1)
251 | wrapper.find('button').at(1).simulate('click')
252 | expect(wrapper.state().count).toBe(0)
253 | })
254 |
255 | test('Should render count', () => {
256 | const wrapper = mount( )
257 | wrapper.find('button').at(0).simulate('click')
258 | expect(wrapper.find('h1').text()).toBe('1')
259 | })
260 | ```
261 |
262 | ## Testeando componentes funcionales
263 |
264 | Dado el siguiente componente:
265 |
266 | ```js
267 | import React, { Component, useState } from 'react'
268 | import Title from './Title'
269 | import Button from './Button'
270 |
271 | const Counter = props => {
272 |
273 | const [count, setCount] = useState(0)
274 |
275 | const handleChange = action => {
276 | setCount(prevCount => action === 'increment' ? prevCount + 1 : prevCount - 1)
277 | }
278 |
279 | return (
280 | <>
281 |
282 | handleChange('increment')} label="Increment"/>
283 | handleChange('decrement')} label="Decrement"/>
284 | >
285 | )
286 |
287 | }
288 |
289 | export default Counter
290 | ```
291 |
292 | Podríamos escribir los siguientes tests:
293 |
294 | ```js
295 | import React from 'react'
296 | import { shallow, mount } from 'enzyme'
297 | import Counter from './Counter'
298 |
299 | test('Should renders without errors', () => {
300 | shallow( )
301 | })
302 |
303 | test('Should start in 0', () => {
304 | const wrapper = mount( )
305 | expect(wrapper.find('h1').text()).toBe('0')
306 | })
307 |
308 | test('Should call to increment when click on button', () => {
309 | const wrapper = mount( )
310 | wrapper.find('button').at(0).simulate('click')
311 | expect(wrapper.find('h1').text()).toBe('1')
312 | wrapper.find('button').at(1).simulate('click')
313 | expect(wrapper.find('h1').text()).toBe('0')
314 | })
315 | ```
316 |
317 | ## Testeando funciones que se pasan por props
318 |
319 | Si tuviésemos un botón como este:
320 |
321 | ```js
322 | import React from 'react'
323 |
324 | export default props =>
325 | {props.label}
326 | ```
327 |
328 | Podríamos testearlo así:
329 |
330 | ```js
331 | import React from 'react'
332 | import { shallow } from 'enzyme'
333 | import Button from './Button'
334 |
335 | test('Should renders without errors', () => {
336 | shallow( )
337 | })
338 |
339 | test('Should renders a button with label', () => {
340 | const wrapper = shallow( )
341 | expect(wrapper.find('button').text()).toBe('click me!')
342 | })
343 |
344 | test('Should call to function when click in the button', () => {
345 | const pressFn = jest.fn()
346 | const wrapper = shallow( )
347 | wrapper.find('button').simulate('click')
348 | expect(pressFn).toHaveBeenCalled()
349 | })
350 | ```
351 |
352 | ## Ejercicios
353 |
354 | 1. Con el ejercicio de la tienda que hemos hecho en clases anteriores testear:
355 | 1. El componente `ShopPage.js`
356 | 2. El componente `ProductsList.js`
357 |
358 | 2. Testear el componente `src/clase4/Select.js`
359 | ```js
360 | import React from 'react'
361 |
362 | export default props =>
363 |
364 | {props.items.map(val => (
365 | {val}
366 | ))}
367 |
368 | ```
369 |
370 | [<- Volver al índice](./../README.md)
--------------------------------------------------------------------------------
/modulo6/crypto.json:
--------------------------------------------------------------------------------
1 | {"data":[{"id":"bitcoin","rank":"1","symbol":"BTC","name":"Bitcoin","supply":"18897081.0000000000000000","maxSupply":"21000000.0000000000000000","marketCapUsd":"903343801092.0782540057359635","volumeUsd24Hr":"19441309332.6284067168886946","priceUsd":"47803.3512737802337835","changePercent24Hr":"-1.7085454199274698","vwap24Hr":"48633.4851123599518745","explorer":"https://blockchain.info/"},{"id":"ethereum","rank":"2","symbol":"ETH","name":"Ethereum","supply":"118682065.6240000000000000","maxSupply":null,"marketCapUsd":"474772585412.2514088054916616","volumeUsd24Hr":"19789033231.0947998200612698","priceUsd":"4000.3734592587209384","changePercent24Hr":"-5.2431527895263824","vwap24Hr":"4137.5654238572094396","explorer":"https://etherscan.io/"},{"id":"binance-coin","rank":"3","symbol":"BNB","name":"Binance Coin","supply":"166801148.0000000000000000","maxSupply":"166801148.0000000000000000","marketCapUsd":"94213074575.3498844143944208","volumeUsd24Hr":"1099177181.6101454448699970","priceUsd":"564.8226987943145596","changePercent24Hr":"-2.6311825219027430","vwap24Hr":"579.2655475640467666","explorer":"https://etherscan.io/token/0xB8c77482e45F1F44dE1745F52C74426C631bDD52"},{"id":"tether","rank":"4","symbol":"USDT","name":"Tether","supply":"76156304083.6859100000000000","maxSupply":null,"marketCapUsd":"76323541149.1300611350390012","volumeUsd24Hr":"48356826655.1697870855763086","priceUsd":"1.0021959713966736","changePercent24Hr":"0.4279104403903883","vwap24Hr":"1.0008889782336578","explorer":"https://www.omniexplorer.info/asset/31"},{"id":"solana","rank":"5","symbol":"SOL","name":"Solana","supply":"307346995.2063772000000000","maxSupply":null,"marketCapUsd":"52965140664.0691078802708153","volumeUsd24Hr":"674917114.9855059871147609","priceUsd":"172.3301073059266542","changePercent24Hr":"-6.8767131612760461","vwap24Hr":"180.5578724138836112","explorer":"https://explorer.solana.com/"},{"id":"cardano","rank":"6","symbol":"ADA","name":"Cardano","supply":"33429062833.5530000000000000","maxSupply":"45000000000.0000000000000000","marketCapUsd":"42411646708.2007982849208452","volumeUsd24Hr":"1236188516.4737823820337854","priceUsd":"1.2687058240122697","changePercent24Hr":"-5.3033524910237236","vwap24Hr":"1.3073016579143746","explorer":"https://cardanoexplorer.com/"},{"id":"usd-coin","rank":"7","symbol":"USDC","name":"USD Coin","supply":"41149918264.8038250000000000","maxSupply":null,"marketCapUsd":"41235483051.0832830922318132","volumeUsd24Hr":"3038953565.4304715850039050","priceUsd":"1.0020793428003633","changePercent24Hr":"1.4127487651253140","vwap24Hr":"1.0010225545023848","explorer":"https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"},{"id":"xrp","rank":"8","symbol":"XRP","name":"XRP","supply":"45404028640.0000000000000000","maxSupply":"100000000000.0000000000000000","marketCapUsd":"37268414015.6784520028435200","volumeUsd24Hr":"2162155885.3695669123218513","priceUsd":"0.8208173400464680","changePercent24Hr":"-8.6485681467926801","vwap24Hr":"0.8568920543040138","explorer":"https://xrpcharts.ripple.com/#/graph/"},{"id":"polkadot","rank":"9","symbol":"DOT","name":"Polkadot","supply":"1063770608.4878400000000000","maxSupply":null,"marketCapUsd":"27970163409.3787270764306789","volumeUsd24Hr":"1053672694.7416586752477814","priceUsd":"26.2934162555389450","changePercent24Hr":"-5.5923413596585910","vwap24Hr":"27.2766340491925890","explorer":"https://polkascan.io/polkadot"},{"id":"terra-luna","rank":"10","symbol":"LUNA","name":"Terra","supply":"379069829.9458898300000000","maxSupply":null,"marketCapUsd":"24501538017.9253541088569519","volumeUsd24Hr":"392206382.4195554108792039","priceUsd":"64.6359485307042670","changePercent24Hr":"-7.7841337457352146","vwap24Hr":"68.8435143739357208","explorer":"https://finder.terra.money/"},{"id":"dogecoin","rank":"11","symbol":"DOGE","name":"Dogecoin","supply":"132467276813.1361100000000000","maxSupply":null,"marketCapUsd":"22216710590.6872539591074113","volumeUsd24Hr":"593182974.4542646824067156","priceUsd":"0.1677147075502056","changePercent24Hr":"-3.8962684056784738","vwap24Hr":"0.1720959569887082","explorer":"http://dogechain.info/chain/Dogecoin"},{"id":"avalanche","rank":"12","symbol":"AVAX","name":"Avalanche","supply":"242832142.6006301300000000","maxSupply":null,"marketCapUsd":"20594977343.5018988426680543","volumeUsd24Hr":"836367896.5260114040017720","priceUsd":"84.8115785782654315","changePercent24Hr":"-3.4627502057274374","vwap24Hr":"87.4919896664172860","explorer":"https://avascan.info/"},{"id":"shiba-inu","rank":"13","symbol":"SHIB","name":"SHIBA INU","supply":"549057767444318.7000000000000000","maxSupply":null,"marketCapUsd":"19034777315.1618845382917132","volumeUsd24Hr":"807548499.8147408636894422","priceUsd":"0.0000346680776483","changePercent24Hr":"-3.9942676884437062","vwap24Hr":"0.0000351726457101","explorer":"https://etherscan.io/token/0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce"},{"id":"polygon","rank":"14","symbol":"MATIC","name":"Polygon","supply":"7081682963.2700000000000000","maxSupply":"10000000000.0000000000000000","marketCapUsd":"15372415030.6325194345396928","volumeUsd24Hr":"2473177537.8986338396586512","priceUsd":"2.1707290640322926","changePercent24Hr":"-2.1869761936255588","vwap24Hr":"2.1648296981664068","explorer":"https://etherscan.io/token/0x7D1AfA7B718fb893dB30A3aBc0Cfc608AaCfeBB0"},{"id":"crypto-com-coin","rank":"15","symbol":"CRO","name":"Crypto.com Coin","supply":"25263013692.0000000000000000","maxSupply":"30263013692.0000000000000000","marketCapUsd":"14132874759.3328711155548256","volumeUsd24Hr":"266263015.4627132097340732","priceUsd":"0.5594294857944168","changePercent24Hr":"-4.8044720443082029","vwap24Hr":"0.5777223287586025","explorer":"https://etherscan.io/token/0xa0b73e1ff0b80914ab6fe0444e65848c4c34450b"},{"id":"binance-usd","rank":"16","symbol":"BUSD","name":"Binance USD","supply":"13800014685.5400000000000000","maxSupply":null,"marketCapUsd":"13838261305.0836996382531077","volumeUsd24Hr":"1097085282.7187296881600181","priceUsd":"1.0027714912204967","changePercent24Hr":"0.4120228296336782","vwap24Hr":"1.0012596700925065","explorer":"https://etherscan.io/token/0x4Fabb145d64652a948d72533023f6E7A623C7C53"},{"id":"wrapped-bitcoin","rank":"17","symbol":"WBTC","name":"Wrapped Bitcoin","supply":"256789.7340656800000000","maxSupply":null,"marketCapUsd":"12269832100.3563679401177117","volumeUsd24Hr":"269922584.2222602571029555","priceUsd":"47781.6301535561794368","changePercent24Hr":"-1.4442166131250719","vwap24Hr":"48435.7168039514615429","explorer":"https://etherscan.io/token/0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"},{"id":"litecoin","rank":"18","symbol":"LTC","name":"Litecoin","supply":"69158282.2114717700000000","maxSupply":"84000000.0000000000000000","marketCapUsd":"10468808417.2960990688227000","volumeUsd24Hr":"778017807.6780907869581440","priceUsd":"151.3746160623920802","changePercent24Hr":"-3.3066789355000520","vwap24Hr":"154.3061210686506574","explorer":"http://explorer.litecoin.net/chain/Litecoin"},{"id":"uniswap","rank":"19","symbol":"UNI","name":"Uniswap","supply":"627936758.8572923000000000","maxSupply":"1000000000.0000000000000000","marketCapUsd":"9686298754.2728502485204157","volumeUsd24Hr":"198271696.8445908282776869","priceUsd":"15.4255959977558848","changePercent24Hr":"-8.1775581541030607","vwap24Hr":"16.1486254796127715","explorer":"https://etherscan.io/token/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"},{"id":"algorand","rank":"20","symbol":"ALGO","name":"Algorand","supply":"6312021583.5573750000000000","maxSupply":"10000000000.0000000000000000","marketCapUsd":"9523652985.1975424654349329","volumeUsd24Hr":"244664979.4546655414165189","priceUsd":"1.5088118535599387","changePercent24Hr":"-6.0929991629167962","vwap24Hr":"1.5668591742300948","explorer":"https://algoexplorer.io/"},{"id":"tron","rank":"21","symbol":"TRX","name":"TRON","supply":"101888513435.9739400000000000","maxSupply":null,"marketCapUsd":"9141510985.0040354740257339","volumeUsd24Hr":"647108577.8135244739685107","priceUsd":"0.0897207219609549","changePercent24Hr":"-1.9122635979636036","vwap24Hr":"0.0905597216268243","explorer":"https://tronscan.org/#/"},{"id":"chainlink","rank":"22","symbol":"LINK","name":"Chainlink","supply":"467009553.9174637000000000","maxSupply":"1000000000.0000000000000000","marketCapUsd":"8941856111.5885687269106849","volumeUsd24Hr":"750721170.6697139179775378","priceUsd":"19.1470517820902986","changePercent24Hr":"-9.6874492019749184","vwap24Hr":"20.3927019503564907","explorer":"https://etherscan.io/token/0x514910771af9ca656af840dff83e8264ecf986ca"},{"id":"terrausd","rank":"23","symbol":"UST","name":"TerraUSD","supply":"8608416322.5461060000000000","maxSupply":null,"marketCapUsd":"8648128604.2389265772344002","volumeUsd24Hr":"143786092.6245171531172320","priceUsd":"1.0046131925089184","changePercent24Hr":"0.4066428777447761","vwap24Hr":"1.0023514493208944","explorer":"https://finder.terra.money/"},{"id":"bitcoin-cash","rank":"24","symbol":"BCH","name":"Bitcoin Cash","supply":"18923525.0000000000000000","maxSupply":"21000000.0000000000000000","marketCapUsd":"8508688183.9496984522797025","volumeUsd24Hr":"548886748.7846258212282145","priceUsd":"449.6354766857495341","changePercent24Hr":"-2.4701390746099509","vwap24Hr":"455.1349184888376863","explorer":"https://blockchair.com/bitcoin-cash/blocks"},{"id":"stellar","rank":"25","symbol":"XLM","name":"Stellar","supply":"24621430066.0378950000000000","maxSupply":"50001806812.0000000000000000","marketCapUsd":"6593222128.3951703880145444","volumeUsd24Hr":"314114849.9478898816130676","priceUsd":"0.2677838821998270","changePercent24Hr":"-6.0807039245650773","vwap24Hr":"0.2768601106644657","explorer":"https://dashboard.stellar.org/"},{"id":"multi-collateral-dai","rank":"26","symbol":"DAI","name":"Multi Collateral DAI","supply":"6474951713.6353770000000000","maxSupply":null,"marketCapUsd":"6487776679.2714932174655482","volumeUsd24Hr":"574042597.2039226154985188","priceUsd":"1.0019807044443449","changePercent24Hr":"1.3545908246465439","vwap24Hr":"1.0005236086049829","explorer":"https://etherscan.io/token/0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"},{"id":"decentraland","rank":"27","symbol":"MANA","name":"Decentraland","supply":"1824595434.8740842000000000","maxSupply":null,"marketCapUsd":"6176142659.6820495873316046","volumeUsd24Hr":"583257892.5130717968808630","priceUsd":"3.3849381301933745","changePercent24Hr":"-7.1924043157535583","vwap24Hr":"3.4963875364618083","explorer":"https://etherscan.io/token/decentraland"},{"id":"axie-infinity","rank":"28","symbol":"AXS","name":"Axie Infinity","supply":"60907500.0000000000000000","maxSupply":"270000000.0000000000000000","marketCapUsd":"6111774961.5680803345792500","volumeUsd24Hr":"171370426.6435775193496692","priceUsd":"100.3451949524784359","changePercent24Hr":"-6.3790050589883989","vwap24Hr":"104.7177535312909171","explorer":"https://etherscan.io/token/0xf5d669627376ebd411e34b98f19c868c8aba5ada"},{"id":"steth","rank":"29","symbol":"STETH","name":"Lido stETH","supply":"1558457.2166837100000000","maxSupply":"141554.0000000000000000","marketCapUsd":"5780559481.9203202449454082","volumeUsd24Hr":"117522.9893320454451788","priceUsd":"3709.1550669712666877","changePercent24Hr":"-1.9634993712665719","vwap24Hr":"3884.2969176338162741","explorer":"https://etherscan.io/token/0xae7ab96520de3a18e5e111b5eaab095312d7fe84"},{"id":"cosmos","rank":"30","symbol":"ATOM","name":"Cosmos","supply":"248453201.0000000000000000","maxSupply":null,"marketCapUsd":"5549082621.1098065979050505","volumeUsd24Hr":"320081677.0159098691669557","priceUsd":"22.3345185281384505","changePercent24Hr":"-4.8906598315357315","vwap24Hr":"23.0605138653391971","explorer":"https://www.mintscan.io/"},{"id":"filecoin","rank":"31","symbol":"FIL","name":"Filecoin","supply":"135513229.0000000000000000","maxSupply":null,"marketCapUsd":"5512627216.9506169381739950","volumeUsd24Hr":"638393100.4199892455242331","priceUsd":"40.6796241048216550","changePercent24Hr":"4.5925017089796047","vwap24Hr":"38.9897482409233758","explorer":"https://protocol.ai"},{"id":"ftx-token","rank":"32","symbol":"FTT","name":"FTX Token","supply":"139295691.3187437400000000","maxSupply":"352170015.0000000000000000","marketCapUsd":"5456729928.6951510287692961","volumeUsd24Hr":"51331208.9461870398953577","priceUsd":"39.1737165524292787","changePercent24Hr":"-4.9974673108220212","vwap24Hr":"40.3904951447101832","explorer":"https://etherscan.io/token/0x50d1c9771902476076ecfc8b2a83ad6b9355a4c9"},{"id":"vechain","rank":"33","symbol":"VET","name":"VeChain","supply":"64315576989.0000000000000000","maxSupply":"86712634466.0000000000000000","marketCapUsd":"5408673894.3777785228969253","volumeUsd24Hr":"254572418.3861043620875630","priceUsd":"0.0840958621159977","changePercent24Hr":"-5.9675913603730881","vwap24Hr":"0.0868138846866538","explorer":"https://explore.veforge.com/"},{"id":"near-protocol","rank":"34","symbol":"NEAR","name":"NEAR Protocol","supply":"573563553.0000000000000000","maxSupply":"1000000000.0000000000000000","marketCapUsd":"5400840818.5098544076018088","volumeUsd24Hr":"516346473.7807010590156593","priceUsd":"9.4162901220989096","changePercent24Hr":"2.5676921358298046","vwap24Hr":"9.6143587899956624","explorer":"https://explorer.nearprotocol.com/"},{"id":"elrond-egld","rank":"35","symbol":"EGLD","name":"Elrond","supply":"19851222.4866275200000000","maxSupply":"31415926.0000000000000000","marketCapUsd":"5337723363.4222650822410660","volumeUsd24Hr":"204186108.6525211053215781","priceUsd":"268.8863805248237396","changePercent24Hr":"-5.8541155996625597","vwap24Hr":"274.6130586246449060","explorer":"https://explorer.elrond.com/"},{"id":"internet-computer","rank":"36","symbol":"ICP","name":"Internet Computer","supply":"183750197.9000000000000000","maxSupply":null,"marketCapUsd":"5102128710.8147351784802263","volumeUsd24Hr":"162888515.7770003080592374","priceUsd":"27.7666569566983589","changePercent24Hr":"-5.5339507419656117","vwap24Hr":"28.7236661170120620","explorer":"https://www.dfinityexplorer.org/#/"},{"id":"bitcoin-bep2","rank":"37","symbol":"BTCB","name":"Bitcoin BEP2","supply":"105115.2302546000000000","maxSupply":null,"marketCapUsd":"5018355179.4366915444458630","volumeUsd24Hr":"651301897.7306571493887713","priceUsd":"47741.4658873097107768","changePercent24Hr":"-2.0362269677017736","vwap24Hr":"48317.4656904723763189","explorer":"https://explorer.binance.org/asset/BTCB-1DE"},{"id":"ethereum-classic","rank":"38","symbol":"ETC","name":"Ethereum Classic","supply":"131542065.3316171800000000","maxSupply":"210700000.0000000000000000","marketCapUsd":"4875915770.8395388542199001","volumeUsd24Hr":"242751843.7897218905462889","priceUsd":"37.0673499655594489","changePercent24Hr":"-4.9155185697527754","vwap24Hr":"37.9847000960082325","explorer":"http://gastracker.io/"},{"id":"the-sandbox","rank":"39","symbol":"SAND","name":"The Sandbox","supply":"913364619.2233225000000000","maxSupply":"3000000000.0000000000000000","marketCapUsd":"4588741436.6269520427383017","volumeUsd24Hr":"754546441.7532819497139849","priceUsd":"5.0239973610199372","changePercent24Hr":"-8.7005394970479970","vwap24Hr":"5.2288690991943697","explorer":"https://etherscan.io/token/0x3845badAde8e6dFF049820680d1F14bD3903a5d0"},{"id":"theta","rank":"40","symbol":"THETA","name":"THETA","supply":"1000000000.0000000000000000","maxSupply":"1000000000.0000000000000000","marketCapUsd":"4401227738.1671206000000000","volumeUsd24Hr":"210094334.9479154071377590","priceUsd":"4.4012277381671206","changePercent24Hr":"-4.4053585129450606","vwap24Hr":"4.5015357702539249","explorer":"https://explorer.thetatoken.org/"},{"id":"tezos","rank":"41","symbol":"XTZ","name":"Tezos","supply":"869992251.3470220000000000","maxSupply":null,"marketCapUsd":"3930254262.9917756080256561","volumeUsd24Hr":"299181524.8898255111447548","priceUsd":"4.5175738713839166","changePercent24Hr":"-12.8415987011621039","vwap24Hr":"4.8468746661259501","explorer":"https://tzkt.io/"},{"id":"hedera-hashgraph","rank":"42","symbol":"HBAR","name":"Hedera Hashgraph","supply":"14832756028.0000000000000000","maxSupply":"50000000000.0000000000000000","marketCapUsd":"3661533899.8050127212455724","volumeUsd24Hr":"59071625.9297073050124417","priceUsd":"0.2468545894568133","changePercent24Hr":"-6.3538129002642610","vwap24Hr":"0.2567240876973502","explorer":"https://hash-hash.info/"},{"id":"fantom","rank":"43","symbol":"FTM","name":"Fantom","supply":"2545006273.0000000000000000","maxSupply":"3175000000.0000000000000000","marketCapUsd":"3605735900.1075044753708036","volumeUsd24Hr":"307345747.0116987119301506","priceUsd":"1.4167886100560132","changePercent24Hr":"-5.0214380078098637","vwap24Hr":"1.4524441515718122","explorer":"https://etherscan.io/token/0x4e15361fd6b4bb609fa63c81a2be19d873717870"},{"id":"unus-sed-leo","rank":"44","symbol":"LEO","name":"UNUS SED LEO","supply":"953954130.0000000000000000","maxSupply":null,"marketCapUsd":"3543866166.9009279287148150","volumeUsd24Hr":"3188384.5153935039359462","priceUsd":"3.7149230297906755","changePercent24Hr":"0.6030500242963176","vwap24Hr":"3.7150692806837209","explorer":"https://eospark.com/account/bitfinexleo1"},{"id":"monero","rank":"45","symbol":"XMR","name":"Monero","supply":"18043131.7536865400000000","maxSupply":null,"marketCapUsd":"3419383534.4200801570215474","volumeUsd24Hr":"126752977.1549713732997247","priceUsd":"189.5116424963996567","changePercent24Hr":"-3.0880374793130491","vwap24Hr":"192.3736438937299150","explorer":"http://moneroblocks.info/"},{"id":"klaytn","rank":"46","symbol":"KLAY","name":"Klaytn","supply":"2552250071.0000000000000000","maxSupply":null,"marketCapUsd":"3371351348.6915289163220840","volumeUsd24Hr":"19006258.3349529406877084","priceUsd":"1.3209330022158040","changePercent24Hr":"-4.2982265619112642","vwap24Hr":"1.3464474337591548","explorer":"https://scope.klaytn.com/blocks"},{"id":"gala","rank":"47","symbol":"GALA","name":"Gala","supply":"6977205436.0000000000000000","maxSupply":null,"marketCapUsd":"3349419575.6232008171033904","volumeUsd24Hr":"783445306.3100465349648608","priceUsd":"0.4800517350888564","changePercent24Hr":"-9.4955366524410596","vwap24Hr":"0.5023608668778226","explorer":"https://ethplorer.io/es/address/0x15d4c048f83bd7e37d49ea4c83a07267ec4203da#chart=candlestick"},{"id":"loopring","rank":"48","symbol":"LRC","name":"Loopring","supply":"1328333700.0378666000000000","maxSupply":"1374513896.0000000000000000","marketCapUsd":"3249210929.1001906581862365","volumeUsd24Hr":"584195876.8908223831016364","priceUsd":"2.4460803253034730","changePercent24Hr":"-2.3480619674747845","vwap24Hr":"2.5100602788077704","explorer":"https://etherscan.io/token/0xEF68e7C694F40c8202821eDF525dE3782458639f"},{"id":"bittorrent","rank":"49","symbol":"BTT","name":"BitTorrent","supply":"990000000000.0000000000000000","maxSupply":null,"marketCapUsd":"3163982031.8611560000000000","volumeUsd24Hr":"224613906.5599507216793177","priceUsd":"0.0031959414463244","changePercent24Hr":"-5.2547005746951981","vwap24Hr":"0.0032931034146862","explorer":"https://tronscan.org/#/token/1002000"},{"id":"iota","rank":"50","symbol":"MIOTA","name":"IOTA","supply":"2779530283.0000000000000000","maxSupply":"2779530283.0000000000000000","marketCapUsd":"3125267624.7114100884127028","volumeUsd24Hr":"47712676.7035538411304562","priceUsd":"1.1243869670447516","changePercent24Hr":"-5.8407516872543100","vwap24Hr":"1.1574543694060756","explorer":"https://thetangle.org/"},{"id":"the-graph","rank":"51","symbol":"GRT","name":"The Graph","supply":"4715735200.0000000000000000","maxSupply":"10057044431.0000000000000000","marketCapUsd":"3114919441.3956147545192000","volumeUsd24Hr":"113362074.6184788882271788","priceUsd":"0.6605373943379210","changePercent24Hr":"-6.5407971723159850","vwap24Hr":"0.6891581843371415","explorer":"https://etherscan.io/token/0xc944e90c64b2c07662a292be6244bdf05cda44a7"},{"id":"chrono-tech","rank":"52","symbol":"TIME","name":"Chrono.tech","supply":"710112.0000000000000000","maxSupply":"710112.0000000000000000","marketCapUsd":"3069289918.8930586224977568","volumeUsd24Hr":"672355.2469321694955899","priceUsd":"4322.2617261686306139","changePercent24Hr":"-3.8302782370614208","vwap24Hr":null,"explorer":"https://etherscan.io/token/0x485d17A6f1B8780392d53D64751824253011A260"},{"id":"eos","rank":"53","symbol":"EOS","name":"EOS","supply":"973464118.1114000000000000","maxSupply":null,"marketCapUsd":"3026159040.3036424007802130","volumeUsd24Hr":"499206491.5374328437219383","priceUsd":"3.1086498043447543","changePercent24Hr":"-7.7561913506923615","vwap24Hr":"3.2610928059791779","explorer":"https://bloks.io/"},{"id":"helium","rank":"54","symbol":"HNT","name":"Helium","supply":"103865779.5937404300000000","maxSupply":"223000000.0000000000000000","marketCapUsd":"2970499926.3940070200171017","volumeUsd24Hr":"22542768.0678517048679722","priceUsd":"28.5994091414206939","changePercent24Hr":"0.6107375029158282","vwap24Hr":"29.0775187517493528","explorer":"https://explorer.helium.com/"},{"id":"flow","rank":"55","symbol":"FLOW","name":"Flow","supply":"315588776.0000000000000000","maxSupply":null,"marketCapUsd":"2882686593.8988348662744776","volumeUsd24Hr":"50097182.7221678292826571","priceUsd":"9.1343127928568501","changePercent24Hr":"-5.0172435032248003","vwap24Hr":"9.3974212197315571","explorer":"https://flowscan.org/"},{"id":"pancakeswap","rank":"56","symbol":"CAKE","name":"PancakeSwap","supply":"247992570.5190625000000000","maxSupply":null,"marketCapUsd":"2725478527.8123061545962198","volumeUsd24Hr":"48052344.8984641100694752","priceUsd":"10.9901620121430460","changePercent24Hr":"-5.5757009450451572","vwap24Hr":"11.4613108998362136","explorer":"https://bscscan.com/token/0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82"},{"id":"stacks","rank":"57","symbol":"STX","name":"Stacks","supply":"1287862729.0615110000000000","maxSupply":"1818000000.0000000000000000","marketCapUsd":"2633592980.7488881867910005","volumeUsd24Hr":"14740725.6398693130269974","priceUsd":"2.0449329896114280","changePercent24Hr":"-6.0742818200443298","vwap24Hr":"2.1185276841092598","explorer":"https://explorer.xinfin.network/"},{"id":"bitcoin-sv","rank":"58","symbol":"BSV","name":"Bitcoin SV","supply":"18920001.6442390500000000","maxSupply":"21000000.0000000000000000","marketCapUsd":"2488256624.1605088932662616","volumeUsd24Hr":"151265334.2658391165151338","priceUsd":"131.5146092980471797","changePercent24Hr":"-4.4154830628762723","vwap24Hr":"135.0841919283112555","explorer":"https://bsvexplorer.io/"},{"id":"kusama","rank":"59","symbol":"KSM","name":"Kusama","supply":"8470098.0572620600000000","maxSupply":null,"marketCapUsd":"2434251351.1419815194597375","volumeUsd24Hr":"64637838.6351902953947707","priceUsd":"287.3935265784688863","changePercent24Hr":"-0.8932657051084914","vwap24Hr":"296.5386787368430619","explorer":"https://kusama.subscan.io/"},{"id":"maker","rank":"60","symbol":"MKR","name":"Maker","supply":"988619.8837919600000000","maxSupply":"1005577.0000000000000000","marketCapUsd":"2424520830.9533320243697835","volumeUsd24Hr":"32882449.3218799628266434","priceUsd":"2452.4297666903243401","changePercent24Hr":"-4.5521752988574659","vwap24Hr":"2532.3229196839588908","explorer":"https://etherscan.io/token/Maker"},{"id":"aave","rank":"61","symbol":"AAVE","name":"Aave","supply":"13433292.7049797200000000","maxSupply":"16000000.0000000000000000","marketCapUsd":"2291811908.9003155104722556","volumeUsd24Hr":"119991847.2176600535386613","priceUsd":"170.6068615664676993","changePercent24Hr":"-7.7294318376592140","vwap24Hr":"178.2719640006653814","explorer":"https://etherscan.io/token/0x80fB784B7eD66730e8b1DBd9820aFD29931aab03"},{"id":"zcash","rank":"62","symbol":"ZEC","name":"Zcash","supply":"13271300.0000000000000000","maxSupply":"21000000.0000000000000000","marketCapUsd":"2245605884.5339593633585300","volumeUsd24Hr":"319991632.3406792386122116","priceUsd":"169.2076800715799781","changePercent24Hr":"-1.5395406778150542","vwap24Hr":"168.6439928451645969","explorer":"https://explorer.zcha.in/"},{"id":"ecash","rank":"63","symbol":"XEC","name":"eCash","supply":"18919635923313.0000000000000000","maxSupply":"21000000000000.0000000000000000","marketCapUsd":"2208569474.5583143718181879","volumeUsd24Hr":"13013093.9619715328023736","priceUsd":"0.0001167342481383","changePercent24Hr":"-4.8918200080531792","vwap24Hr":"0.0001196736824991","explorer":"https://explorer.bitcoinabc.org/"},{"id":"cofound-it","rank":"64","symbol":"CFI","name":"Cofound.it","supply":"325000000.0000000000000000","maxSupply":null,"marketCapUsd":"2168442820.1464869425000000","volumeUsd24Hr":"146663.9707273043918161","priceUsd":"6.6721317542968829","changePercent24Hr":"0.1744369599314428","vwap24Hr":null,"explorer":"https://etherscan.io/token/0x12fef5e57bf45873cd9b62e9dbd7bfb99e32d73e"},{"id":"amp","rank":"65","symbol":"AMP","name":"Amp","supply":"42227702186.0000000000000000","maxSupply":"92547638199.0000000000000000","marketCapUsd":"2160977207.6483518134299150","volumeUsd24Hr":"17063734.8836107767641866","priceUsd":"0.0511743972743275","changePercent24Hr":"-5.6876617375185255","vwap24Hr":"0.0533283128030071","explorer":null},{"id":"enjin-coin","rank":"66","symbol":"ENJ","name":"Enjin Coin","supply":"842268984.0675580000000000","maxSupply":"1000000000.0000000000000000","marketCapUsd":"2138181504.7210972329039521","volumeUsd24Hr":"170281008.3604828603240918","priceUsd":"2.5385969864344366","changePercent24Hr":"-6.9211520916066325","vwap24Hr":"2.6101413454186267","explorer":"https://etherscan.io/token/0xf629cbd94d3791c9250152bd8dfbdf380e2a3b9c"},{"id":"quant","rank":"67","symbol":"QNT","name":"Quant","supply":"12072738.0000000000000000","maxSupply":"14612493.0000000000000000","marketCapUsd":"2002161382.8166841493332298","volumeUsd24Hr":"72856891.4799344712021622","priceUsd":"165.8415334464049621","changePercent24Hr":"5.8405190709632735","vwap24Hr":"164.1760692666274477","explorer":"https://etherscan.io/token/0x4a220e6096b25eadb88358cb44068a3248254675"},{"id":"harmony","rank":"68","symbol":"ONE","name":"Harmony","supply":"11461592260.1006720000000000","maxSupply":null,"marketCapUsd":"1991490246.3543049820936422","volumeUsd24Hr":"64494130.4024385260076291","priceUsd":"0.1737533669983138","changePercent24Hr":"-5.5879742257121173","vwap24Hr":"0.1794622283741715","explorer":"https://explorer.harmony.one"},{"id":"neo","rank":"69","symbol":"NEO","name":"Neo","supply":"70538831.0000000000000000","maxSupply":"100000000.0000000000000000","marketCapUsd":"1943625602.0858753119187562","volumeUsd24Hr":"71294246.4215851419838210","priceUsd":"27.5539809000502902","changePercent24Hr":"-4.7632587836784925","vwap24Hr":"28.1468337210890210","explorer":"https://neotracker.io"},{"id":"trustnote","rank":"70","symbol":"TTT","name":"TrustNote","supply":"309999945.0035280000000000","maxSupply":null,"marketCapUsd":"1899965669.1695031025991227","volumeUsd24Hr":"387754.1609387790869862","priceUsd":"6.1289226007697526","changePercent24Hr":"-1.2977001961522664","vwap24Hr":"6.2768742834157849","explorer":"https://explorer.trustnote.org/"},{"id":"chiliz","rank":"71","symbol":"CHZ","name":"Chiliz","supply":"5954921247.5307580000000000","maxSupply":"8888888888.0000000000000000","marketCapUsd":"1749768806.0365550747483346","volumeUsd24Hr":"126577652.0392725075861148","priceUsd":"0.2938357592490592","changePercent24Hr":"-5.9977047385308865","vwap24Hr":"0.3019430364691433","explorer":"https://etherscan.io/token/0x3506424f91fd33084466f402d5d97f05f8e3b4af"},{"id":"kadena","rank":"72","symbol":"KDA","name":"Kadena","supply":"161172075.0645400000000000","maxSupply":"1000000000.0000000000000000","marketCapUsd":"1740562037.8745034056275062","volumeUsd24Hr":"54324874.3111486180551064","priceUsd":"10.7994020501225786","changePercent24Hr":"-1.6244339826092922","vwap24Hr":"11.0699850370082100","explorer":"https://explorer.chainweb.com/mainnet"},{"id":"thorchain","rank":"73","symbol":"RUNE","name":"THORChain","supply":"258210215.1421133000000000","maxSupply":"500000000.0000000000000000","marketCapUsd":"1733128569.0186550552845311","volumeUsd24Hr":"44239334.7694727983356438","priceUsd":"6.7120836720761752","changePercent24Hr":"-7.0471272458861602","vwap24Hr":"7.1658655480046905","explorer":"https://explorer.binance.org/asset/RUNE-B1A"},{"id":"waves","rank":"74","symbol":"WAVES","name":"Waves","supply":"106919482.0000000000000000","maxSupply":null,"marketCapUsd":"1707420189.1133150678146458","volumeUsd24Hr":"40974916.6850924170200744","priceUsd":"15.9692149379597169","changePercent24Hr":"-10.4725631941493975","vwap24Hr":"17.0576192605147367","explorer":"http://wavesexplorer.com/"},{"id":"symbol","rank":"75","symbol":"XYM","name":"Symbol","supply":"5582460004.5584020000000000","maxSupply":"8999999999.0000000000000000","marketCapUsd":"1660393468.4166212493438721","volumeUsd24Hr":"20898852.5542542499561544","priceUsd":"0.2974304279942559","changePercent24Hr":"11.1184528463464757","vwap24Hr":"0.3078376017962649","explorer":"http://explorer.symbolblockchain.io/"},{"id":"basic-attention-token","rank":"76","symbol":"BAT","name":"Basic Attention Token","supply":"1492976103.4473505000000000","maxSupply":"1500000000.0000000000000000","marketCapUsd":"1638105225.6280446023441957","volumeUsd24Hr":"118385369.4386358075539053","priceUsd":"1.0972079337677169","changePercent24Hr":"-8.1870604418854405","vwap24Hr":"1.1558056679542570","explorer":"https://etherscan.io/token/Bat"},{"id":"kucoin-token","rank":"77","symbol":"KCS","name":"KuCoin Token","supply":"80118638.0000000000000000","maxSupply":"170118638.0000000000000000","marketCapUsd":"1606786946.7127437283298184","volumeUsd24Hr":"28892631.7956162276975941","priceUsd":"20.0550956284696668","changePercent24Hr":"-4.2613771674878995","vwap24Hr":"20.8050694469079990","explorer":"https://etherscan.io/token/0xf34960d9d60be18cc1d5afc1a6f012a723a28811"},{"id":"huobi-token","rank":"78","symbol":"HT","name":"Huobi Token","supply":"159308566.2544120800000000","maxSupply":"500000000.0000000000000000","marketCapUsd":"1597851375.3934351776498550","volumeUsd24Hr":"84151981.7942120696441472","priceUsd":"10.0299149817323925","changePercent24Hr":"-0.5489975744059686","vwap24Hr":"9.9701653041556764","explorer":"https://etherscan.io/token/0x6f259637dcd74c767781e37bc6133cd6a68aa161"},{"id":"curve-dao-token","rank":"79","symbol":"CRV","name":"Curve DAO Token","supply":"434851013.9631334000000000","maxSupply":"3303030299.0000000000000000","marketCapUsd":"1526038069.1255396996940876","volumeUsd24Hr":"95026679.9882951518151268","priceUsd":"3.5093354278229117","changePercent24Hr":"-8.5066879597973229","vwap24Hr":"3.6906652616262170","explorer":"https://etherscan.io/token/0xD533a949740bb3306d119CC777fa900bA034cd52"},{"id":"holo","rank":"80","symbol":"HOT","name":"Holo","supply":"173037114405.8564800000000000","maxSupply":null,"marketCapUsd":"1522116423.0692269070745923","volumeUsd24Hr":"33334093.5058481838239277","priceUsd":"0.0087964736830916","changePercent24Hr":"-5.3888199657099349","vwap24Hr":"0.0090796189311207","explorer":"https://etherscan.io/token/0x6c6ee5e31d828de241282b9606c8e98ea48526e2"},{"id":"okb","rank":"81","symbol":"OKB","name":"OKB","supply":"60000000.0000000000000000","maxSupply":null,"marketCapUsd":"1431911251.9316411640000000","volumeUsd24Hr":"689912950.6359337680101648","priceUsd":"23.8651875321940194","changePercent24Hr":"-8.0156591772705354","vwap24Hr":"25.0819275130116220","explorer":"https://etherscan.io/token/0x75231f58b43240c9718dd58b4967c5114342a86c"},{"id":"frax","rank":"82","symbol":"FRAX","name":"Frax","supply":"1425574681.9078815000000000","maxSupply":"131992706.0000000000000000","marketCapUsd":"1430462319.8325070834698145","volumeUsd24Hr":"501942.1305854953391889","priceUsd":"1.0034285386705131","changePercent24Hr":"0.0082458997179344","vwap24Hr":"0.9998441415499375","explorer":"https://etherscan.io/token/0x853d955acef822db058eb8505911ed77f175b99e"},{"id":"defichain","rank":"83","symbol":"DFI","name":"DeFiChain","supply":"300511840.0000000000000000","maxSupply":"1200000000.0000000000000000","marketCapUsd":"1425650796.7006048548762400","volumeUsd24Hr":"4825791.3666960945612988","priceUsd":"4.7440752973347235","changePercent24Hr":"-4.7589571929932046","vwap24Hr":"4.9084829953768232","explorer":"http://explorer.defichain.io/"},{"id":"dash","rank":"84","symbol":"DASH","name":"Dash","supply":"10472491.6253227600000000","maxSupply":"18900000.0000000000000000","marketCapUsd":"1404443088.9131520955557594","volumeUsd24Hr":"188270555.1994794257388928","priceUsd":"134.1078264046706701","changePercent24Hr":"-9.5868163538628316","vwap24Hr":"139.7241962716470025","explorer":"https://explorer.dash.org"},{"id":"arweave","rank":"85","symbol":"AR","name":"Arweave","supply":"33394701.0000000000000000","maxSupply":"66000000.0000000000000000","marketCapUsd":"1380921765.2355661329072900","volumeUsd24Hr":"25375451.8992836795345530","priceUsd":"41.3515235616442900","changePercent24Hr":"-5.7935930753138886","vwap24Hr":"42.4431888759730421","explorer":"https://viewblock.io/arweave"},{"id":"celo","rank":"86","symbol":"CELO","name":"Celo","supply":"367204495.0000000000000000","maxSupply":"1000000000.0000000000000000","marketCapUsd":"1361937965.4417952983766660","volumeUsd24Hr":"38431697.6293981253732780","priceUsd":"3.7089359852247868","changePercent24Hr":"-4.5119428122671719","vwap24Hr":"3.8734066247416932","explorer":"https://explorer.celo.org/blocks"},{"id":"wemix","rank":"87","symbol":"WEMIX","name":"WEMIX","supply":"123233682.3800000000000000","maxSupply":"1015055200.0000000000000000","marketCapUsd":"1343904855.0899496741248438","volumeUsd24Hr":"119622052.2780681229812095","priceUsd":"10.9053371540576184","changePercent24Hr":"-10.1738025810456763","vwap24Hr":"11.1661719236163860","explorer":"https://scope.klaytn.com/token/0x5096db80b21ef45230c9e423c373f1fc9c0198dd?tabId=kctTransfer"},{"id":"trueusd","rank":"88","symbol":"TUSD","name":"TrueUSD","supply":"1299419541.9938512000000000","maxSupply":null,"marketCapUsd":"1302389696.7877302934932309","volumeUsd24Hr":"97504359.8549075465663229","priceUsd":"1.0022857550605416","changePercent24Hr":"0.4609636351994520","vwap24Hr":"1.0009306342292509","explorer":"https://etherscan.io/token/0x8dd5fbce2f6a956c3022ba3663759011dd51e73e"},{"id":"iotex","rank":"89","symbol":"IOTX","name":"IoTeX","supply":"9540779324.3078800000000000","maxSupply":"10000000000.0000000000000000","marketCapUsd":"1271532061.2475413030284686","volumeUsd24Hr":"253400412.4225338524345969","priceUsd":"0.1332733960220574","changePercent24Hr":"7.6434396317732264","vwap24Hr":"0.1306866564644771","explorer":"https://etherscan.io/token/0x6fb3e0a217407efff7ca062d46c26e5d60a14d69"},{"id":"nexo","rank":"90","symbol":"NEXO","name":"Nexo","supply":"560000011.0000000000000000","maxSupply":"1000000000.0000000000000000","marketCapUsd":"1224232568.4043203384390160","volumeUsd24Hr":"7730791.6017000304210226","priceUsd":"2.1861295434944560","changePercent24Hr":"-2.0757942032657330","vwap24Hr":"2.2259182433751207","explorer":"https://etherscan.io/token/0xb62132e35a6c13ee1ee0f84dc5d40bad8d815206"},{"id":"theta-fuel","rank":"91","symbol":"TFUEL","name":"Theta Fuel","supply":"5301214400.0000000000000000","maxSupply":null,"marketCapUsd":"1215900055.1580814170105600","volumeUsd24Hr":"43534536.1144586361313332","priceUsd":"0.2293625504295924","changePercent24Hr":"-4.2892726296561279","vwap24Hr":"0.2321607738055361","explorer":"https://explorer.thetatoken.org/"},{"id":"nem","rank":"92","symbol":"XEM","name":"NEM","supply":"8999999999.0000000000000000","maxSupply":"8999999999.0000000000000000","marketCapUsd":"1210744855.8428231937802944","volumeUsd24Hr":"26190090.2489465263786927","priceUsd":"0.1345272062197056","changePercent24Hr":"-0.7971369596556831","vwap24Hr":"0.1351365009155495","explorer":"http://nembex.nem.ninja/"},{"id":"compound","rank":"93","symbol":"COMP","name":"Compound","supply":"6207618.0749875100000000","maxSupply":"10000000.0000000000000000","marketCapUsd":"1185385995.8501612888687674","volumeUsd24Hr":"116484752.9511104085624342","priceUsd":"190.9566570511904983","changePercent24Hr":"-6.0726716294981215","vwap24Hr":"201.4445421297738928","explorer":"https://etherscan.io/token/0xc00e94cb662c3520282e6f5717214004a7f26888"},{"id":"metahero","rank":"94","symbol":"HERO","name":"Metahero","supply":"5750000000.0000000000000000","maxSupply":"10000000000.0000000000000000","marketCapUsd":"1140048247.2227711750000000","volumeUsd24Hr":"19246140.7774144481910084","priceUsd":"0.1982692603865689","changePercent24Hr":"-4.2802104004581418","vwap24Hr":"0.2049471027190208","explorer":"https://bscscan.com/token/0xD40bEDb44C081D2935eebA6eF5a3c8A31A1bBE13"},{"id":"mina","rank":"95","symbol":"MINA","name":"Mina","supply":"317189469.8400392500000000","maxSupply":null,"marketCapUsd":"1134424920.2962178443824128","volumeUsd24Hr":"23816479.6822180245545195","priceUsd":"3.5764898527946588","changePercent24Hr":"-3.3520985888025849","vwap24Hr":"3.6399790283215712","explorer":"https://minaexplorer.com/"},{"id":"decred","rank":"96","symbol":"DCR","name":"Decred","supply":"13573968.5413418900000000","maxSupply":"21000000.0000000000000000","marketCapUsd":"1089417215.8359055490857772","volumeUsd24Hr":"5069843.8826590418704980","priceUsd":"80.2578267746750188","changePercent24Hr":"-6.6840344777475608","vwap24Hr":"82.7828500021894286","explorer":"https://mainnet.decred.org/"},{"id":"convex-finance","rank":"97","symbol":"CVX","name":"Convex Finance","supply":"40703525.7013128300000000","maxSupply":"100000000.0000000000000000","marketCapUsd":"1048005766.7735565577236853","volumeUsd24Hr":"15929702.2500779136538177","priceUsd":"25.7472970391789607","changePercent24Hr":"-7.2510324921543840","vwap24Hr":"26.2681749085015373","explorer":"https://etherscan.io/token/0x4e3fbd56cd56c3e72c1403e103b45db9da5b9d2b"},{"id":"1inch","rank":"98","symbol":"1INCH","name":"1inch Network","supply":"407450828.0646610000000000","maxSupply":null,"marketCapUsd":"1029941024.1804639345945683","volumeUsd24Hr":"67625509.7394839719064905","priceUsd":"2.5277676549893180","changePercent24Hr":"-3.0198118350858159","vwap24Hr":"2.5411468148118152","explorer":"https://etherscan.io/token/0x111111111117dc0aa78b770fa6a738034120c302"},{"id":"experience-points","rank":"99","symbol":"XP","name":"Experience Points","supply":"1430238524.0102800000000000","maxSupply":null,"marketCapUsd":"1013880475.3940562000374111","volumeUsd24Hr":"639039.7595099666336227","priceUsd":"0.7088890827462908","changePercent24Hr":"-3.8944525285756791","vwap24Hr":"0.6514685013513939","explorer":"https://chainz.cryptoid.info/xp/"},{"id":"wax","rank":"100","symbol":"WAXP","name":"WAX","supply":"1864229388.4495358000000000","maxSupply":null,"marketCapUsd":"1000109197.6593715349933935","volumeUsd24Hr":"116769483.2886433894948969","priceUsd":"0.5364732494058331","changePercent24Hr":"-11.5781745107253753","vwap24Hr":"0.5552488213461084","explorer":"https://etherscan.io/token/0x39Bb259F66E1C59d5ABEF88375979b4D20D98022"}],"timestamp":1639155075677}
--------------------------------------------------------------------------------