Índice del curso de ReactJS

  1. Instalación y configuración inicial
  2. Estructura básica de un proyecto con ReactJS
  3. JSX
  4. Recogida de datos
  5. State
  6. Componentes
  7. Props
  8. Renderizado condicional
  9. Arrays
  10. Style
  11. Valores por defecto
  12. Styled Components
  13. React router
  14. Fetch API (axios)
  15. Recoger parámetros de la url
  16. Context API
  17. Login
  18. Redux
  19. React Developer Tool
  20. Sockets
  21. Build
  22. Styleguidist
  23. Easy FTP
  24. TypeScript
  25. Expo
  26. React native
  27. Hacer peticiones desde el teléfono móvil a mi ordenador
  28. React Native Navigation
  29. Compilar con Expo en la nube
  30. Compartir código entre React Web y React Native. Dos aproximaciones:
    1. Empaquetar con NPM
    2. Importar función que usa Prototype

Instalación y configuración inicial

Introducción

React (también llamado React.js o ReactJS) es una librería Javascript de código abierto para crear interfaces de usuario de tipo SPA (single page applications).

react vs jquery

Por qué no usar programación orientada a objetos con React

  • Porque es más complejo.
  • Porque escribes más código.
  • Porque lo dice Facebook (su compañía creadora).
    Video de Sophie Alpert, gerente de ingeniería en Facebook

    Video de Dan Abramox, co-author de redux y de create react app y trabajador de Facebook.

¿Qué empresas usan ReactJS?

  • Amazon
  • Apple
  • Google
  • Microsoft
  • Dropbox
  • Netflix
  • Spotify
  • Twitter
  • Yahoo
  • ...

Instalación

  1. Instalamos nodejs
  2. A partir de aquí, tenemos dos opciones:
    1. Instalar nuestro "creador" de aplicaciones React:
      sudo npm install -g create-react-app
      create-react-app monteserin-app
    2. Hacer la configuración manualmente. Es buena idea partir de este proyecto que ya tiene módulos preconfigurados:
      git clone https://github.com/monteserin/react-template.git
      El código relevante que está dentro de esta plantilla es:
      package.json{
        "name": "Template",
        "version": "1.0.0",
        "description": "",
        "main": "./public",
        "scripts": {
          "build": "cross-env NODE_ENV=production webpack -p",
          "start": "cross-env NODE_ENV=development webpack-dev-server",
          "upload": "node ./bin/upload.js"
        },
        "author": "",
        "license": "ISC",
        "devDependencies": {
          "@babel/cli": "^7.0.0",
          "@babel/core": "^7.3.4",
          "@babel/plugin-proposal-optional-chaining": "^7.7.5",
          "@babel/plugin-transform-runtime": "^7.3.4",
          "@babel/preset-env": "^7.3.4",
          "@babel/preset-react": "^7.0.0",
          "@babel/runtime": "^7.5.5",
          "babel-loader": "^8.0.5",
          "babel-plugin-import-directory": "^1.1.0",
          "babel-plugin-inline-import": "^3.0.0",
          "babel-plugin-module-resolver": "^3.2.0",
          "babel-plugin-wildcard": "^5.0.0",
          "babel-preset-react": "^6.24.1",
          "html-webpack-plugin": "^3.2.0",
          "copy-webpack-plugin": "^4.6.0",
          "file-loader": "^4.1.0",
          "react-styleguidist": "9.2.0",
          "terser-webpack-plugin": "^1.2.3",
          "url-loader": "^2.2.0",
          "webpack": "4.29.6",
          "webpack-cli": "^3.3.10",
          "webpack-dev-server": "^3.2.1"
        },
        "dependencies": {
          "axios": "^0.19.0",
          "easy-ftp": "^0.4.1",
          "react": "16.9.0",
          "react-dom": "16.9.0",
          "react-router": "^5.0.1",
          "react-router-dom": "^5.0.1",
          "styled-components": "^4.4.0"
        }
      }

      Tener en cuenta que la propieadad publicPath tendrá el valor de la ruta que usaremos en producción, y que para acceder a la aplicación desde local, usaremos esa misma ruta (en nuestro caso, htpp://localhost:8080/ruta-que-usaremos-en-produccion )

      webpack.config.js// Cargamos un módulo nativo de node que nos gestiona rutas
      const path = require('path');
      // Este módulo minifica el bundle (el archivo compilado)
      const TerserPlugin = require('terser-webpack-plugin');
      const HtmlWebpackPlugin = require('html-webpack-plugin');
      const CopyWebpackPlugin = require('copy-webpack-plugin');
      
      // Constant with our paths
      const paths = {
        ROOT: path.resolve(__dirname),
        DIST: path.resolve(__dirname, 'dist'),
        SRC: path.resolve(__dirname, 'src'),
      };
      
      // funciones que nos devuelven true o false
      const development = process.env.NODE_ENV === 'development';
      
      //rutaProduccion : rutaEnDesarrollo
      
      // Set plugins
      const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({
        template: './src/index.html',
        hash: !development,
      });
      
      module.exports = {
        entry: path.join(paths.SRC, 'index.js'),
        output: {
          path: paths.DIST,
          filename: 'index.js',
          publicPath: '/ruta-que-usaremos-en-produccion',
        },
        module: {
          rules: [
            {
              test: /\.(js)$/,
              exclude: /node_modules/,
              use: 'babel-loader',
            },
            {
              test: /\.(jpg|jpeg|gif|png|wav)$/,
              loader: 'file-loader',
              options: {
                publicPath: '/malabares/statics/images/',
                outputPath: 'statics/images/',
                name: '[name].[ext]',
              },
            },
            {
              test: /\.(eot|svg|ttf|woff|woff2|otf)$/,
              loader: 'file-loader',
              options: {
                publicPath: '/malabares/statics/vectors/',
                outputPath: 'statics/vectors/',
                name: '[name].[ext]',
              },
            },
          ],
        },
        resolve: {
          extensions: ['.js'],
        },
        devServer: {
          historyApiFallback: true,
          disableHostCheck: true,
          hot: false,
          inline: true,
        },
        optimization: {
          minimize: true,
          minimizer: [
            new TerserPlugin({
              sourceMap: development,
              cache: true,
              parallel: true,
              terserOptions: {
                compress: true,
                ecma: 6,
                ie8: false,
                mangle: true,
              },
            }),
          ],
        },
        mode: process.env.NODE_ENV,
        devtool: development && 'source-map',
        plugins: [
          HtmlWebpackPluginConfig,
          //El siguiente plugin se encarga de copiar el contenido de unra ruta en otra ruta
          // el from busca dentro de la raíz del proyecto
          // el to busca dentro de la carpeta dist
          // lo usaremos para evitar tener que referenciar los ficheros estáticos (json, js, pdf...) de un módulo para que sean exportados
          new CopyWebpackPlugin([
            {
              from: './src/assets',
              to: './assets',
            },
          ]),
        ],
      };
      /.babelrc{
        "presets": [
          "@babel/preset-env", "@babel/preset-react"
        ],
        "plugins": [
          "@babel/plugin-transform-runtime"
        ]
      }

      Ficheros opcionales

      ./src/index.html<!doctype html>
      <html lang="en">
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>React App</title>
        </head>
        <body>
          <div id="root"></div>
        </body>
      </html>
      index.jsimport React from 'react';
      import ReactDOM from 'react-dom';
      import App from './App';
      
      ReactDOM.render(
        <App />,
        document.getElementById('root')
      );
      ./bin/upload.jsconst EasyFtp = require('easy-ftp');
      const ftp = new EasyFtp();
      const config = {
          "name": "pablomonteserin.com",
          "host": "example.com",
          "port": 21,
          "type": "ftp",
          "username": "usuario",
          "password": "password",
      };
      
      //(connect)
      ftp.connect(config);
      ftp.upload("./dist/**", "/www/termine", function(err) {
          if(err) {
              return console.error(err);
          }
          console.info('Deployed!');
      });
      	

Arrancar una aplicación

npm start

Estructura básica de un proyecto con ReactJS

src/index.js//ReactDOM.render(Que-voy-a-renderizar, Dónde-lo-haré)
ReactDOM.render(<App />, document.getElementById('root'))
public/index.html<div id="root"></div>
src/App.jsimport React from 'react';
import './App.css';


const App = _ => (
  //El siguiente código es JSX
  <div className="App">
    <h1>Welcome to React</h1>
  </div>
);

export default App;

JSX

Se usa para declarar componentes utilizando una sintaxis que recuerda a código html. La transformación de nuestro código JSX a un código Javascript que usa la librería de React es llevada a cabo, habitualmente, por el transpilador babel.

  • Declaración básica de una constante:
    const element = <h1>Hello World</h1>; // Ver nativo
    "use strict";
    
    var element = React.createElement(
    "h1",
    null,
    "Hello, world"
    )
  • Comentarios:
    const JSX = (
      <div>
        <h1>This is a block of JSX</h1>
        <p>Here's a subtitle</p>
      </div>
      /* comentarios*/
    );
  • Cuando tenemos varios elementos que queremos almacenar en una sola variable o constante de JSX, estos elementos deben estar anidados en uno solo:
    const url = <div>
                  <h1>a</h1>
                  <h2>b</h2>
                </div> // Ver nativo
    "use strict";
    
    var url = React.createElement(
      "div",
      null,
      React.createElement(
        "h1",
        null,
        "a"
      ),
      React.createElement(
        "h2",
        null,
        "b"
      )
    );
  • Para definir clases, usaremos className:
    const JSX = (
      <div className="nombre">
        <h1>Add a class to this div</h1>
      </div>);
  • El código HTML se escribe tal cual, y el código Javascript se pone entre llaves:
    const element = <h1>{ 2 + 2 }</h1> // Ver nativo
    "use strict";
    
    var element = React.createElement(
      "h1",
      null,
      2 + 2
    );
    const url = "foto.jpg"
    const foto = <img src={url} /> // Ver nativo
    "use strict";
    
    var url = "foto.jpg";
    var foto = React.createElement("img", { src: url });
    function multiplicar(a,b){return a*b}
    const element = <h1>{ multiplicar(2,3)}</h1> // Ver nativo
    "use strict";
    
    function multiplicar(a, b) {
      return a * b;
    }
    var element = React.createElement(
      "h1",
      null,
      multiplicar(2, 3)
    );
  • Detección de eventos:
    const btnPulsado = () =>{
      alert("hola");
    }
    
    const App = _ => (
      <button onClick={btnPulsado}>Púlsame</button>
    );

    Eventos soportados

  • Array I
    const App = _ => {
      const numeros = [1,1,3,5,7]
      return (
        <div>
          {numeros.map(n => {
            return <p>{n}</p>
          })}
        </div>
      );
    }
  • Array II

    Cada elemento del array debe tener asociada una key única cuando lo recorremos. Si la key está repetida, el valor sólo se imprimirá una vez.

    const App = _ => {
      const numeros = [1,1,3,5,7]
      return (
        <div>
          {numeros.map(n => {
            return <p key={n}>{n}</p>
          })}
        </div>
      );
    }
  • Array III

    Si en lugar de asociar la key al valor, la asociamos al índice, evitaremos que se impriman los duplicados

    const App = _ {
      const numeros = [1,1,3,5,7]
      return (
        <div>
          {numeros.map((n, index) => > {
            return <p key={key}>{n}</p>
          })}
        </div>
      );
    }
  • Recogida de datos

    import React, {useRef} from 'react';
    
    const App = _ => {
      const nombreRef = useRef(null);
    
      const procesar = _ => {
        console.log(nombreRef.current.value);
      }
      return (
        <div className="App">
          <input type="text" ref={nombreRef}/>
          <button onClick={procesar}>Enviar</button>
        </div>
      )
    }
    
    export default App;

    Ejercicios - recogida de datos

    De momento, en estos ejercicios no pintaremos la solución en la página HTML, sino que la mostraremos como una traza en la consola.

    1. Grados centígrados

      Definir una clase App que recogerá los grados centígrados introducidos y se los pasará a la clase App principal para que se encargue de calcular los grados Fahrenheit.

      Para ello deberé multiplicar por 9/5 y sumar 32. Como todo el mundo sabe, 20 grados centígrados son 68 grados Farenheit.

    2. Euros a dólares

      Vamos a hacer un ejercicio similar al anterior. Se trata de un conversor de euros a dólares. Supondremos que un euro son dos dólares. El usuario introducirá un valor en euros y se mostrará un mensaje de alerta con el correspondiente valor en dólares.

      Para hacer este ejercicio, parte del código del ejemplo anterior, pero modificándolo para que en lugar de hablar de grados Centígrados y grados Fahrenheit, en el código fuente se haga referencia a euros y dólares.

    3. Calcular la superficie de un rectángulo

      Tendremos un formulario que recogerá el ancho y el alto de un rectángulo y calculará su superficie.

    State

    Cuando actualizamos una variable state, automáticamente, en caso de ser necesario, el código html vinculado a esa variable es actualizado.

    Los hooks (useState y useEffect)fueron introducidos en la versión 16.8 de react. Por tanto, para utilizarlos, deberemos tener al menos esta versión en el package.json

      "dependencies": {
        "react": "^16.8.6",
        "react-dom": "^16.8.6",

    Cuando queremos modificar un estado, utilizaremos el segundo parámetro que devuelve la función useState, que al ejecutarla nos guardará el parámetro que le pasemos como nuevo valor del estado.

    import React, {useState} from 'react';
    
    const App = _ =>{
      const [contador, actualizarContador] = useState(1);   
      return <span onClick={() => actualizarContador(contador + 1)} >{contador}</span>
    }
    
    export default App;

    Ejercicios State

    1. Ahora modificaremos los ejercicios que vimos en la sección de recogida de datos:

      • Calcular grados Fahrenheit
      • Convertir euros en dólares
      • Calcular la superficie de un rectángulo.

      En estos ejercicios estabamos mostrando el resultado en una traza. Ahora lo mostraremos pintándolo en pantalla, utilizando state.

      A continuación, el ejemplo de cómo lo haríamos en el ejercicio de convertir de grados Centígrados a grados Fahrenheit.

      App.jsimport React, { useRef, useState } from 'react';
      
      const App = _ => {
        const gradosCRef = useRef(null);
        const [gradosF, setGradosF] = useState(0);   
      
      
        const calcular = _ =>{
          const gradosC = gradosCRef.current.value;
          setGradosF(gradosC*9/5+32);
        }
      
          return (
            <div className="App">
              <input type="text" ref={gradosCRef}/>
              <button onClick={calcular}>Calcular</button>
              <div>{gradosF}</div>
            </div>
          );
      }
      
      export default App;
      
    2. Desarrollar una aplicación. Al pulsar sobre cada uno de esos botones, debe cargarse la imagen correspondiente a una de las propiedades del siguiente objeto:

      const ANIMAL_IMAGES = {
        img1: "http://via.placeholder.com/111x111",
        img2: "http://via.placeholder.com/222x222",
        img3: "http://via.placeholder.com/333x333",
      };
      ejercicio imagenes con react

    Componentes

    Son partes de la interfaz visual.

    Su nombre debe, obligatoriamente, comenzar por mayúscula. Si no comienza por mayúscula, tendremos un error que es difícil de interpretar.

    Un componente debe devolver código JSX o null.

    import React from 'react';
    
    const MyComponent = _ => <div>hola</div>;
    
    const App = _ => {
      return (
        <div className="App">
          <MyComponent />
        </div>
      );
    }
    
    export default App;

    Cada vez que el componente se renderiza, sus funciones se vuelven a crear, por lo que cuantas más funciones podamos definr fuera del mismo, mejor

    Creación de un componente en un fichero externo

    src/App.jsimport Saludo from './components/saludador'
    
    const App = _ => (
      <Saludo />
    );
    
    export default App;
    src/components/saludador.jsimport React from 'react'
    
    const Saludo = _ => (
      <div>hola</div>
    );
    
    export default Saludo;

    Ejercicios Componentes

    1. Grados centígrados

      Definir una clase App que tendrá un único componente llamado Form. Este componente recogerá los grados centígrados introducidos y se los pasará a la clase App principal para que se encargue de calcular los grados Fahrenheit.

      Para ello deberé multiplicar por 9/5 y sumar 32. Como todo el mundo sabe, 20 grados centígrados son 68 grados Farenheit.

    2. Euros a dólares

      Vamos a hacer un ejercicio similar al anterior. Se trata de un conversor de euros a dólares. Supondremos que un euro son dos dólares. El usuario introducirá un valor en euros y se mostrará un mensaje de alerta con el correspondiente valor en dólares.

      Para hacer este ejercicio, parte del código del ejemplo anterior, pero modificándolo para que en lugar de hablar de grados Centígrados y grados Fahrenheit, en el código fuente se haga referencia a euros y dólares.

    3. Calcular la superficie de un rectángulo

      Tendremos un formulario que recogerá el ancho y el alto de un rectángulo y calculará su superficie.

      Seguimos en la línea de los ejemplos anteriores, pero ahora vamos a recuperar dos valores en lugar de uno.

    Props

    Sintácticamente una prop es similar a los atributos de HTML.

    const App = _ => (
      <Hello title="hola pepito"/>
    );
    const Hello = (props) => {
      return <h2>{props.title}</h2>
    };

    children

    import React from 'react';
    import Box from './components/box';
    
    const App = _ => (
      <div>
        <Box>Texto 1</Box>
        <Box>Texto 2</Box>
      </div>
    );
    
    export default App;
    import React from 'react';
    
    const Box = (props) => (
      <div>
        {props.children}
      </div>
    );
    
    export default Box;

    Ejercicios props

    1. Ejercicio - props

      Crear un componente llamado Post que debe imprimir el valor de las props postTile, author, y contenido (children).

      const App = _ => (
          <div className="App">
              <Post postTitle="Viaje a la luna" author="Julio Verne">Texto 1</Post>
              <Post postTitle="Viaje a Marte" author="Pablo Mon">Texto 2</Post>
          </div>
      );
    2. Ahora modificaremos los ejercicios que vimos en la sección de recogida de datos:

      • Calcular grados Fahrenheit
      • Convertir euros en dólares
      • Calcular la superficie de un rectángulo.

      En estos ejercicios, dentro del fichero App.js recogíamos los datos y ofrecíamos una respuesta en el propio fichero App.js. En esta ocasión vamos a crear un nuevo componente que recibirá el valor y pintará la respuesta. Para ello, le pasaremos el valor mediante props.

    3. Ejercicio coste hotel.
      Haremos una aplicación para calcular el coste de un viaje. Para ello desarrollaremos dos componentes que recibirán como props el número de noches.
      • Componente CosteHotel: múltiplica el número de noches por los 40 euros que cuesta cada noche.
      • Componente CosteAlquiler:
        • Cada día de alquiler cuesta 40 €.
        • Si alquilas un coche por 3 días o más, obtienes un descuento de 20€ sobre el total.
        • Si alquilas un coche por 7 días o más, obtienes un descuento de 50€ sobre el total (no acumulable con los 20€ de haber alquilado por más de 3 días).
      <input type="text" ref={nochesRef} placeholder="noches" onChange={() => {...}}/>
      <CosteHotel noches={nochesState}/>
      <CosteAlquiler noches={nochesState}/>
      

    Default props

    En caso de que la prop no reciba ningún valor, el default prop será su valor por defecto

    const Prueba = (props) => (
      <div>{props.title}</div> 
    );
    
    Prueba.defaultProps = {
      title: 'Amor'
    }
    
    const App = _ => (
      <Prueba />
    );

    Prop Types

    Nos permiten filtrar el tipo de dato que va a almacenar cierta prop. Este tipo de avisos debe estar deshabilitado en producción, son sólo para desarrollo.

    npm i prop-types
    const Items = (props) => (
      <h1>Quantity: {props.quantity}</h1>
    );

    Destructuring del props

    const App = _ =>(
      <Prueba prop1={3} prop2={4} />
    );
    
    const Prueba = ({prop1, prop2}) =>(
      <div>
        {/* gracias al destructuring no tengo que usar this.props.prop1 para imprimir la propiedad prop1  */}  
        <p>{prop1}</p>
        <p>{prop2}</p>
      </div>
    );

    Las props son constantes, y por tanto, no debemos modificarlas.

    Interpretar el HTML de una prop

    const App = _ =>(
      <Prueba rawHtml={"<strong>hola</strong>"} />
    );
    
    const Prueba = ({rawHtml}) => (
      <div dangerouslySetInnerHTML={{__html: rawHtml}}/>
      {/*El siguiente código no funcionaría*/}
      <div>{rawHtml}</div>
    );

    Inicializando un estado mediante props

    const Prueba = ({ contadorInicial }) => {
      const [contador, actualizarContador] = useState(contadorInicial);
      return(
        <span onClick={() => actualizarContador(contador + 1)}>{contador}</span>
      )
    }
    
    const App = _ => (
      <Prueba contadorInicial={10} />
    );

    Pasar una prop del hijo al padre

    app.jsconst App = _ => {
    
      const changePadre = param => {
        alert(param);
      }
    
      return (
        <div className="App">
          <MiComponente onSomeEvent={changePadre} />
        </div>
      );
    }
    
    export default App;
    miComponente.jsimport React from 'react';
    export default ({onSomeEvent}) => {
    
        return (
            <button onClick={() => onSomeEvent(3)}>Pulame</button>
        )
    }
    Descargar recurso

    Renderizado condicional

    No es posible poner un if en JSX. En su lugar, tenemos las siguientes opciones:

    Con estados. Si el estado o la propiedad existe, lo muestro, si no no hago nada

    return (
      <div>
        {truco.reps && (
            <div>{truco.reps}</div>
        )}
      </div>
    );
    return (
      alumnos && (
        <div>Hola</div>
      )
    )

    Con estados II. Si el estado o la propiedad existe, lo muestro, si no, hago otra cosa

    return (
      <div>
        {truco.reps
          ? <div>{truco.reps}</div>
          : <div>No reps specified</div>
        }
      </div>
    );

    Ejercicios renderizado condicional

    1. Haremos una aplicación para calcular el coste de un viaje. Para ello desarrollaremos dos componentes que recibirán como props el número de noches y un número que determinará si el componente va a calcular el coste del hotel o el coste del viaje:
      <ComponenteMultiple opcion="1" noches={nochesState}/>
      <ComponenteMultiple opcion="2" noches={nochesState}/>
      • Opción 1 ( Coste Hotel): múltiplica el número de noches por los 40 euros que cuesta cada noche.
      • Opción 2 ( Coste Alquiler):
        • Cada día de alquiler cuesta 40 €.
        • Si alquilas un coche por 3 días o más, obtienes un descuento de 20€ sobre el total.
        • Si alquilas un coche por 7 días o más, obtienes un descuento de 50€ sobre el total (no acumulable con los 20€ de haber alquilado por más de 3 días).
    2. Vamos a juntar los siguientes tres proyectos en uno solo:

      • Calcular grados Fahrenheit
      • Convertir euros en dólares
      • Calcular la superficie de un rectángulo.

      Pintaremos tres veces un único componente en pantalla que mostrará en función de una prop el resultado correcto.

      const calcula = _ => {
        setDatos({
          dato1: dato1Ref.current.value,
          dato2: dato2Ref.current.value
        });
      }
      
      return (
        <div className="App">
          <input type="text" ref={dato1Ref} placeholder="dato1" onChange={() => { calcula() }} />
          <input type="text" ref={dato2Ref} placeholder="dato2" onChange={() => { calcula() }} />
      
          <ComponenteMultiple opcion="1" datos={datosState} />
          <ComponenteMultiple opcion="2" datos={datosState} />
          <ComponenteMultiple opcion="3" datos={datosState} />
        </div>
      );

    Renderizado condicional de clases css

    Vamos a mostrar un class u otro en función de si se cumple cierta condición.

    import React from 'react';
    
    const mergeClasses = (...classes) => classes.filter(className => !!className).join(" ");
    
    const App =_ => {
      const isOption1 = true;
      const isOption2 = false;
      
        return (
          <div className={mergeClasses("clase1 clase2", isOption1 && "clase3", isOption2 && "clase4")}>
          </div>
        )
    }
    
    export default App;

    Arrays

    src/data/datos.json[
    {
      "id": 1,
      "nombre": "Pepe",
      "edad": 23
    
    },
    {
      "id": 2,
      "nombre": "Sofía",
      "edad": 34
    
    },
    {
      "id": 3,
      "nombre": "Juan",
      "edad": 24
    
    },
    {
      "id": 4,
      "nombre": "Raquel",
      "edad": 44
    
    },
    {
      "id": 5,
      "nombre": "Andrés",
      "edad": 34
    
    }
    ]
    import datos from './data/datos.json';
    const App = _ => (
      <div>
        {datos.map((dato, index) => {
          return <p key={index}>{dato.nombre}</p>
        })}
        </div>
    );

    Lista con componente anidado

    const Dato = ({persona}) =>(
      <p>
        {persona.nombre} <br/>
        {persona.edad}
      </p>
    );
          
    const App = _ => (
      <div>
        {datos.map((dato, index) => {
          return <Dato key={dato.id} persona={dato} />
        })}
      </div>
    );

    Style

    const App = _ => <div style={{color:"red", fontSize:"72px"}}>Big Red</div>;
    

    Es posible utilizar objetos para definir estilos

    import React from 'react';
    
    const styles ={color: "purple", fontSize: "40px", border:"2px solid purple"}
    
    const App = _ => <div style={styles}>Style Me!</div>;
    
    export default App;
    

    Valores por defecto

    Valor por defecto de una combo

    <select value={value.level}>

    Valor por defecto de un checkbox

    <input type="checkbox" defaultChecked={true}/>

    La propiedad defaultChecked sólo funciona en el render inicial. Si queremos cambiar el valor inicial de la checkbox (por ejemplo, porque hemos hecho una petición al servidor con useEffect, usaremos checked en lugar de defaultChecked).

    Si queremos gestionar un listado de checkboxes

    const cambiaCheckbox = e =>{
      if(e.target.checked){
        alumnos_para_borrar.push(e.target.value);
        set_alumnos_para_borrar(alumnos_para_borrar);
      }else{
        const index = alumnos_para_borrar.indexOf(+e.target.value)
        alumnos_para_borrar.splice(index, 1)
      }
    }
    ...
     cambiaCheckbox(e) } />
    
    

    Styled Components

    npm i --save styled-components

    Usaremos el siguiente plugin de visual studio code: vscode-styled-components from Julien Poissonnier.

    import { Face } from './styled';
    ...
    <Face
    	src={getImageFromAlias(obj.alias)}
    	inactive={obj.estaEnClase == 0}
    	/>
    import styled, { css } from 'styled-components';
    //Importamos la siguiente variable para poder establecer media queries para nuestros compontes
    import { mobile } from '../../styles/media-queries';
    
    export const Face = styled.img`
    	width: 100%;
    	margin-bottom: 0;
    	opacity: ${props => prop.inactive ? 0.5 : 1 };
    `;
    
    // CabeceraComun es un estilo que será compartido por los componentes CabeceraTerminator y CabeceraMerlin
    // Su código debe estar definido antes de ser usado
    const CabeceraComun = css`
    	background-size: 200px auto;
    
    	&:hover {
    	background-position: 0 0
    	}
    
    	${mobile} {
    		background: red;
    	}
    `;
    
    export const CabeceraTerminator = styled.div`
    	//terminatorIMG es una variable de javascript que carga una imagen
    	background-image: url(${terminatorIMG});
    	${CabeceraComun}
    `;
    
    export const CabeceraMerlin = styled.div`
    	//terminatorIMG es una variable de javascript que carga una imagen
    	background-image: url(${terminatorIMG});
    	${CabeceraComun}
    `; 

    Para hacer media queries:

    media-queries.jsexport const desktopStartWidth = 996;
    
    export const desktop = `@media (min-width: ${desktopStartWidth}px)`;
    export const mobile = `@media (max-width: ${desktopStartWidth}px)`;

    Para maquetar el body

    import styled, { createGlobalStyle } from 'styled-components';
    
    export const GlobalStyle = createGlobalStyle`
      body {
        height: 100vh;
        background-image: url(${termineBack});
        background-size: cover;
        margin: 0; 
        font-family: 'open-sans';
        display: flex; 
        justify-content: center; 
        align-items: center;
      }
    `;
    ./src/App.jsimport { GlobalStyle } from '../../comunes';
    
    export default _ => (
        <App>
          <GlobalStyle /> 
        </App>
    );

    Para mquetar un componente que no es una etiqueta de HTML:

    export const Box = styled(NavLink)`

    React router

    Nos permite cargar unos componentes u otros en función de la url.

    npm i react-router
    npm i react-router-dom

    Documentación de react-router

    Configuración básica

    src/App.jsimport React from 'react';
    import Routes from './application/routes';
    
    const App = () => <Routes />;;
    
    export default App;
    
    src/application/routes.jsimport React from 'react';
    import { BrowserRouter, Route, Switch } from 'react-router-dom';
    import Home from '../pages/home';
    import Contacto from '../pages/contacto';
    import Producto from '../pages/producto';
    
    const Routes = () => (
        <BrowserRouter>
            <Switch>
                { /*El atributo exact significa que ese nodo no afecta a sus descendientes.
                 Si no lo ponemos, al escribir /login, accederíamos igualmente a Home */}    
                <Route exact path="/" component={Home} />
                <Route path="/contacto" component={Contacto} />
                <Route path="/producto/:id" component={Producto} />
                 { /* Es muy recomendable añadir esta ruta para obtener un mensaje de error en el caso de que la 
                ruta no exista. De lo contrario, si la ruta no existe llegaremos a una página en blanco */}    
                <Route path="*" component={() => <div>404</div> } />
            </Switch>
        </BrowserRouter>
    );
    
    export default Routes;
    
    src/pages/home/home.jsimport React from 'react';
    import { Link } from 'react-router-dom';
    
    const Home = _ => 
        <ul>
            <li><Link to={`/`}>Inicio</Link></li>
            <li><Link to={`/contacto`}>Contacto</Link></li>
            <li><Link to={`/producto/1`}>Producto 1</Link></li>
            <li><Link to={`/ajksdfkjhasdk`}>Error en la url</Link></li>
        </ul>
    
    export default Home;
    Descargar ejemplo router app

    Cuando hagamos un build del proyecto las rutas definidas en el fichero routes.js, no funcionarán si el proyecto está en una subcarpeta del servidor. Podemos salvar este problema añadiendo el siguiente atributo a la etiqueta <BrowserRouter /> del fichero routes.js

    basename={'/ruta-en-la-que-esta-el-proyecto-y-que-no-debe-terminar-en-barra'}

    Recoger un parámetro enviado con React Router

    src/application/routes.js<Route path="/producto/:id" component={Producto} />
    ProductouseEffect(() => {
      const id = props.match.params.id;
    }, [])

    Redirección desde React

    Definimos el método que ejecutará la redirección:

    const Contacto = ({ history }) => {
        const pulsado = _ => {
            history.replace('/');
        }
    
        return(  
                <button onClick={pulsado}>Pulsame</button>
        )
    }

    HashRouter

    El uso de este enrutador añade un hashtag (#) a la url de tal forma que al contrario de lo que ocurría con el <BrowserRouter> al refrescar una url a la que hemos accedido no obtendremos un 404

    import { Route, Switch, HashRouter } from 'react-router-dom';
    
    const Routes = () => <HashRouter>
        <Switch>
            <Route exact path="/" component={Home} />
            ...
        </Switch>
    </HashRouter>

    Fetch API (axios)

    Cuando queremos recuperar datos de un servidor, debemos usar el método useEffect para ejecutar la petición al servidor sólo cuando el componente se monta. De lo contrario, lo que ocurriría es:

    1. Al renderizar el componente App se haría la petición al servidor.
    2. Cuando la información llega del servidor, cambiaría el estado de la variable name
    3. Al cambiar el estado de la variable, se generaría otro render.
    4. Al ejecutarse otro render, volveríamos al paso 1, y así seguiríamos indefinidamente.

    Ejemplo básico, usando then

    ./app.jsimport React, {useEffect} from 'react';
    
    const App = () => {
      const [name, setName] = useState(null);
    
      useEffect(() => {
        fetch('https://swapi.co/api/people/1') // hacemos la petición get
        .then(res => res.json()) // cuando hayamos terminado (then) parseamos a json la respuesta de la petición
        .then(res => setName(res.name)); // cuando hayamos terminado (then) actualizamos el estado nombre
      }, []); //Debemos usar los [] para que la petición sólo se ejecute cuando el componente se monte. De lo contrario se ejecutaría en cada render. Si ponemos una variable de estado dentro de los [], la petición se ejecutárá cada vez que esa variable cambie.
      return <p>{name}</p>;
    }

    Axios

    import React, {useState, useEffect} from 'react';
    import axios from 'axios';
    
    const App =() => {
      const [users, setUsers ] = useState([]);
    
      useEffect(() => {
        axios.get(`https://pablomonteserin.com/cosas.php?p=list`)
        .then(res => {
          setUsers(res.data);
        })
      }, [])
      ...

    Recoger parámetros de la url

    import React, {useState, useEffect} from 'react'
    import queryString from 'query-string';
    
    export default ({location}) => {
      useEffect(() => {
        const { id } = queryString.parse(location.search);
      }, [])

    Context API

    ./app.jsimport React from 'react';
    import MyProvider from './application/provider';
    import ShowState from './pages/show-state';
    import LogIn from './pages/login';
    
    const App = _ => {
      return (
        <MyProvider>
          <LogIn />
          <ShowState />
        </MyProvider>
      );
    }
    
    export default App;
    ./src/application/provider.jsimport React,{createContext,useState} from 'react';
    
    const MyProvider =(props)=>{
        const [state,setState] = useState({});
        return (
            <div>
                <AppContext.Provider value={[state,setState]}>
                    {props.children}
                </AppContext.Provider>  
            </div>
            );
        }
    
    export default MyProvider;
    export const AppContext = createContext();
    ./src/pages/login.jsimport React,{useRef,useContext} from 'react';
    import {AppContext} from '../application/provider';
    
    const LogIn=()=>{
        let nom = useRef(null);
    
        const [state,setState] = useContext(AppContext);
    
        return( <input type="text" ref = {nom} 
                onChange={ () => {setState({name:nom.current.value})}} />
        );
    }
    
    export default LogIn;
    ./src/pages/show-state.jsimport React, { useContext } from 'react';
    import { AppContext } from '../application/provider';
    
    const ShowState = () => {
      const [state, setState] = useContext(AppContext);
      return ( <p>{state.name}</p> );
    }
    
    export default ShowState;
    Descargar ejercicio resuelto

    Redux

    Permite definir estados globales.

    No tiene sentido usar Redux y useState para una misma variable. Si una variable es global a nivel de aplicación, no tiene sentido hacerla global a nivel de componente. Estaríamos guardando la aplicación dos veces.

    Necesitaremos instalar el siguiente módulo para poder usarlo:

    npm i redux
    npm i react-redux 

    Diferencia entre Context API y Redux

    Redux es una librería totalmente externa a react.

    • Reducers
    • Actions
    • Middlewares

    Context API

    • Provider value={useRreducer()}
    • Consumer

    Aplicación con Redux

    El objeto en el que se guarda el state global de la aplicación se llama store.

    Estructura global:

    • src/App.js
    • src/components/
      • form/form.js
      • palabra/palabra.js
    • src/store/
      • palabra
        • actions.js
        • reducer.js (modifican los estados de la aplicación)
      • index.js (aquí se registran los reducers)
    ./src/App.jsimport React, { Component } from 'react';
    import Form from './components/Form';
    import PalabraAlmacenada from './components/palabra';
    
    const App = _ =>(
    	<div className="App">
    		<Form/>
    		<PalabraAlmacenada/>
    	</div>
    );
    
    export default App;

    Los componentes

    Un componente que accede a un estado de Redux, debe recibirlo como prop. Si no lo hacemos, el código no dará error, pero tampoco funcionará.

    ./src/components/form/form.jsimport { modificaPalabra } from '../../store/palabra/actions';
    import {connect} from "react-redux";
    
    const Form = ({ modificaPalabra }) => {
    	const palabraRef = useRef(null);
    	return (
    		<input ref={palabraRef} type="text" placeholder="La palabra"/>
    		<button onClick={ () => {modificaPalabra(palabraRef.current.value);}} >Add</button>
    	)
    };
    
    /* - El método connect conecta un componente (en este caso Form) con la Store.
       - El primer parámetro (que en este caso vale null porque no lo necesitamos) es la función de este componente que recibiŕía el state de la store para pintarlo por pantalla.
       - El segundo parámetro es un objeto que contiene las acciones de redux que vamos a utilizar en el componente.
    Tanto el state al que nos hemos suscrito con el primer parámetro, como las acciones del segundo le llegarán al componente como props.*/
    
    export default connect(null, { modificaPalabra })(Form)
    
    //Si fuesen varios métodos a los que nos quisieramos conectar, lo haríamos de esta forma:
    //export default connect(null, { modificaPalabra, modificaOtraCosa })(Form)

    El siguiente comoponente no modifica la store, por tanto, no tengo que vincularlo a un action.

    ./src/components/palabra/palabra.jsimport React from 'react';
    import {connect} from 'react-redux';
    import { selectActiveWord } from '../../store/palabra/reducer';
    
    const PalabraAlmacenada = ({ palabra }) => (
    	<output>{palabra}</output>
    );
    
    /*
    * Cada vez que se ejecuta una acción, se ejecutará también una llamada a todas las funciones mapStateToProps de mi aplicación
    * Por tanto, usaremos esta función para recuperar la parte del esta que nos interesa.
    * */
    function mapStateToProps(state) {
        return {
            palabra: selectActiveWord(state)
        }
    }
    
    /*La función connect, vincula el componente PalabraAlmacenada con la store.
    Para ello, utilizamos la función mapStateToProps cuyo parámetro state representa la store. 
    En este caso no usamos acciones (el segundo parámetro).*/
    
    export default connect(mapStateToProps)(PalabraAlmacenada);

    actions

    La modificación de los estados del store no se hace directamente. Para efectuarla, tendremos que definir y usar unas funciones llamadas actions.

    Estas funciones tienen una estructura específica. Deben devolver un objeto con las siguientes propiedades:

    • type. Esta propiedad es obligatoria y su valor debe ser un string. Es la propiedad que usaremos en el reducer para determinar el cambio concreto a realizar en la store.
    • payload. Esta propidad es opcional. Es un contenido asociado a la acción que vamos a realizar. Por ejemplo, si queremos dar una persona de alta, sería un objeto con las propiedades de esa persona.
    ./src/store/palabra/actions.js// Las acciones siempre tienen la estructura { type, payload }
    export const modificaPalabra = (palabra) => {
        return {
            type: 'MODIFY_WORD',
            payload: palabra
        }
    };

    reducers

    Se encargan de materializar los actions.

    Cuando lanzamos un action, se ejecutan todos los reducers, hasta encontrar aquel cuya condición coincide con el type de la action que estamos ejecutando.

    Incluso aunque ya se haya cumplido la igualdad del type, seguirán ejecutándose todos los reducers hasta llegar al final.

    ./src/store/palabra/reducer.jsconst initialState = { palabra: "" };
    
    // action es el valor devuelto por el action
    //action.payload será el valor que quiero añadir, borrar, etc
    export default (state = initialState, action) => {
        if (action.type === 'MODIFY_WORD') {
            return {
                ...state, //Lo que devuelve un reducer es lo que se quedará en el state, por tanto, debe devolver todo lo que había antes (además de la información que cambia)
                palabra: action.payload
            }
        }
    
        return state;
    };
    
    export const selectActiveWord = state => state.palabraReducer.palabra;

    store

    El store un objeto que contiene todo el arbol de estados de la aplicación. Un estado es una propiedad del store.

    En el store se registran los reducers, que como veremos más adelante serán los que modifiquen los estados.

    ./src/store/index.jsimport { createStore, combineReducers } from 'redux';
    import palabraReducer from './palabra/reducer';
    
    const reducers = combineReducers({
        palabraReducer
    });
    
    const store = createStore(
        reducers,
        window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
    );
    
    export default store;

    Finalmente, para cargar React

    ./src/index.jsimport React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './App';
    
    import { Provider } from 'react-redux';
    import store from './store';
    
    const Application = () => (
        <Provider store={store}>
            <App />
        </Provider>
    );
    
    
    ReactDOM.render(<Application />, document.getElementById('root'));

    Ejercicios Redux

    1. Modificaremos los ejercicios que vimos en la sección de recogida de datos:

      • Calcular grados Fahrenheit
      • Convertir euros en dólares
      • Calcular la superficie de un rectángulo.

      Cada uno de estos ejercicios debe tener dos componentes. Uno en el que se introducirán los datos y otro en el que se pintarán los resultados. Utilizaremos redux para compartir del valor a través de los diferentes componentes.

      const App = _ => (
          <div className="App">
              <Form/>
              <PintarResultado/>
          </div>
      );

    Ejercicio listado con Redux

    Crear un componente que consulte un array con un listado de valores y los muestre por pantalla.

    Al pulsar sobre un elemento del listado, debemos almacenar su id con Redux y utilizarla para pintar el título de ese elemento recuperándolo con Redux desde otro componente.

    El componente del listado, quedaría de la siguiente forma:

    import React from "react";
    import {connect} from "react-redux";
    import { setActiveElement } from '../../store/elementoActivo/actions';
    
    const Listado = ({ setActiveElement }) => {
        const elementos = [
            {id: 1, name: 'elemento1'},
            {id: 2, name: 'elemento2'},
            {id: 3, name: 'elemento3'},
            {id: 4, name: 'elemento4'},
            {id: 5, name: 'elemento5'},
            {id: 6, name: 'elemento6'}
        ];
    
        return (
            <ul>
                { elementos.map(({ id, name }) => (
                    <li
                        onClick={() => setActiveElement(id)}
                        key={id}>{name}
                    </li>
                ))}
            </ul>
        );
    };
    
    export default connect(null, { setActiveElement })(Listado)

    Ejercicio listado de componentes con Redux

    El componente (Persona) debe recibir la prop del evento que se ha producido.

    Listado{ elementos.map(({ id, name }) => (
      <Persona
        onClick={() => setActiveElement(id)}
        key={id}
        nombre={name} />
      ))}
    Personaconst Persona = ({ nombre, onClick }) => {
        return (
            <div onClick={onClick}>{nombre}</div>
        );
    };

    React Developer Tool

    Es un plugin que podemos añadir al navegador y que nos permitirá inspeccionar los componentes React de la página, ver sus prop, saber si una página está hecha con React, etc.

    Sockets

    Funciona en ReactJS y React Native.

    Documentación

    npm i socket.io-client
    import io from 'socket.io-client';
    const socket = io('http://localhost:3000');
    ...
    
    socket.emit('msgEnviado', {msg, id:1});

    Login

    Pasos:

    1. Tendremos una página con un componente Login que recogerá el nombre y el usuario de un formulario y se los mandará al servidor.
    2. El servidor le enviará de vuelta un token generado con jwt a partir de la id de ese usuario.
    3. Guardaremos ese token globalmente usando redux.
    4. Tendremos una segunda página en la que veremos la id del usuario logueado sólo si este ha logrado loguearse con éxito.
      const ContenidoRestringido = ({ accessToken }) => { 
          const [userData, setUserData] = useState(null);
      
          useEffect(() => {
              getJson('/user-data', '', { headers: { accessToken } }).then(setUserData);
          },[]);
      
          return (
              <div>
                  <p>Contenido reestringido</p>
                  {userData && (
                      <div>{userData.idRecuperadaDeLaBaseDeDatos}</div>
                  )}
              </div>
          );
      }
      
      const mapStateToProps = state => {
          return{
              accessToken: selectToken(state),
          }
      }
      
      export default connect(mapStateToProps)(ContenidoRestringido);

    Build

    npm run build

    Esto crea en la carpeta build un compilado de nuestra aplicación. Sin embargo, el index.html de la aplicación generada utiliza rutas relativas y sólo funcionará si añadimos un punto al inicio de cada url. Para evitar esto en cada compilado, podemos añadir la siguiente línea al package.json:

    {
      "name": "borrame",
      "version": "0.1.0",
      "private": true,
      "homepage": "./",

    Styleguidist

    Nos permite probar módulos de manera independiente.

    npm i -g cross-env
    npm install -g webpack
    npm install -g react-styleguidist
    ./src/components/titanpad/README.mdBasic usage
    
    ```js
    import Titanpad from './index';
    
    <Titanpad visible={true} cod="appsguays" />    
    ```
    ./src/components/titanpad/index.jsimport React, { useState, useEffect } from 'react';
    
    export default ({ visible, cod }) => {
        return (
            <div>
    			...
            </div>
        )
    }
    package.json{
    	...
    	"scripts": {
        	...
        	"start-styleguide": "styleguidist server",
    	},
    	...
    }
    

    Referenciamos todos los componentes que queremos evaluar.

    styleguide.config.jsconst path = require('path');
    const webpackConfig = require('./webpack.config.js');
    
    module.exports = {
    	webpackConfig,
    	styleguideDir: 'docs',
    	skipComponentsWithoutExample: true,
    	theme: {
    		maxWidth: 1400,
    	},
    	title: 'Termine Core',
    	sections: [
    		{
    			name: 'Titanpad',
    			components: 'src/components/titanpad/index.js',
    		},
    		{
    			name: 'Paneles',
    			components: 'src/components/paneles/index.js',
    		},
    	],
    };

    Easy FTP

    Nos permite subir cómodamente nuestra aplicación a internet mediante ftp.

    npm i easy-ftp
    package.json  "scripts": {
    "upload": "node upload.js",
    const EasyFtp = require('easy-ftp');
    const ftp = new EasyFtp();
    const config = {
      "name": "nombre_conexion",
      "host": "example.com",
      "port": 21,
      "type": "ftp",
      "username": "user",
      "password": "pass",
    };
    
    //서버 접속(connect)
    ftp.connect(config);
    ftp.upload("./build/**", "/www/malabares", function(err) {
      if(err) {
        return console.error(err);
      }
      console.info('Deployed!');
    });

    Typescript

    Documentación oficial

    sudo create-react-app miproyectotypescript -typescript
    Tipado básicoimport React from 'react';
    import logo from './logo.svg';
    import './App.css';
    import { number } from 'prop-types';
    
    interface Author {
      id: number;
      name: string;
      age: number;
    }
    
    interface Book {
      id: number;
      title: string;
      price: number;
      author: Author;
    }
    
    
    const author : Author = {
      id:0,
      name: "Pablo Monteserín",
      age: 27
    }
    
    const books: Book[] = [
      {
        id:0,
        title: "Monteserín lo ha vuelto a hacer",
        price: 21,
        author: author
      },
      {
        id:1,
        title: "Monteserín se va a la montaña",
        price: 19,
        author: author
      }
    ]
    
    interface NumberAndString {
      number: number;
      string: string;
    }
    const getNumberAndString = (number:number, string:string):NumberAndString => {
    
      const NumberAndString ={
        number: 3,
        string: "monteserin"
      }  
    
      return NumberAndString;
    }
    const App: React.FC = () => {
      const myNumberAndString = getNumberAndString(3, "hola");
    
    
      return (
        
        <div className="App">
          {
            books.map(obj => <p>{obj.title}</p>)
          }
          
           <p>{myNumberAndString.string}</p>
          
        </div>
      );
    }
    
    
    export default App;

    Expo

    Documentación de la instalación.

    1. Instalamos Expo en el sistema operativo:
      npm install -g expo-cli
    2. Creaomos nuestro proyecto Expo
      expo init AwesomeProject
    3. Ejecutamos el proyecto que hemos creado
      npm start
    4. Instalamos la aplicación Expo en el teléfono móvil: Expo
    5. Para desplegar la aplicación en el móvil, podemos
      • Pulsar el botón "" teniendo el móvil conectado con la opción debug usb (opciones de desarrollo) activada. Por razones de estabilidad, esta es la opción más recomendable.
      • Escanear el código de qr generado tras ejecutar npm start con la aplicación Expo.
    
    
    	

    React native

    Instalar React Native

    Instalar react nativenpm install -g react-native-cli
    Crear un proyecto con react nativereact-native init pruebaReactNative

    Tras crear nuestro proyecto React, podemos abrir la carpeta Android del mismo con Android Studio.

    Ejecutar nuestra aplicación

    Debemos crear el fichero local.properties:

    ./android/local.propertiessdk.dir = /home/monty/Android/Sdk
    sudo react-native run-android

    En linux, si intentaba lanzar la aplicación después de que el ordenador hubiese entrado en modo suspensión, no se lanzaba, y obtenía un error.

    Para lanzar el emulador sin abrir el android studio:

    cd ~/Android/Sdk/tools/bin
    emulator -list-avds // este comando muestra los teléfonos instalados
    cd ~/Android/Sdk/tools/
    emulator -avd nexus // he renombrado el nombre por defecto que el avd manager le da al dispositivo y lo he llamado nexus para que sea más fácil de escribir

    En mi caso, una vez sé el nombre del dispositivo, podría ejecutar:

    /home/monty/Android/Sdk/tools/emulator -avd nexus

    Crear una aplicación con react native

    Para crear nuestras aplicaciones para React Native tenemos dos opciones oficialmente soportadas por facebook: Expo y Create React App (https://facebook.github.io/react-native/docs/getting-started).

    La diferencia de React Native con React se refiere sólo a la capa de presentación. React Native tiene varios de componentes específicos para desarrollo móvil.

    Estas dos herramientas nos permiten crear cómodamente una aplicación con React Native en la que todo está configurado y listo para empezar a modificar.

    React Native Dev Tool

    Mostraremos su menú pulsando CTRL + M

    En una versión de linux no me funcionó, así que en la terminal ejecuté:

    adb shell input keyevent 82
    Dev Mode Android Native

    Desde aquí podemos:

    • Refrescar el emulador con los últimos cambios (R + R, o reload)
    • Enable Hot Reloading y Enable Live Reload
      • No debes tener la opción Enable Hot Realoading habilitada si tienes la opción Enable Live Reload habilitada. Hacen conflicto.
      • Por regla general, sólo habilitaremos la opción Enable Live Reload en lugar de la Enable Hot Realoading.
    • Ver los logs de la aplicación desde chrome (Remote JS Debugging)

    Añadiendo los códigos anteriores al package.json

    
      "scripts": {
      	...
        "start": "node node_modules/react-native/local-cli/cli.js start &
     /home/monty/Android/Sdk/tools/emulator -avd nexus & sudo react-native run-android",
        "panel": "/home/monty/Android/Sdk/platform-tools/adb shell input keyevent 82"
      
        ...
      }
      

    El plugin npm scripts de egamma para Visual Studio Code permite ejecutar un script del package.json haciendo click sobre él.

    A veces un servicio de nodejs se queda abierto y no permite volver a desplegar otro en el mismo puerto. Podemos cerrar todos los servicios de nodejs ejecutando el comando:

    killall node

    Configuración importante para mejorar el flujo de trabajo desde el Android Studio

    Para deshabilitar Enable Hot Reloading por defecto:

    1. Abrimos Android Studio
    2. File -> Settings ->
      1. HotSwap -> Dehabilitamos Enable hot-swap agent for Groovy code
      2. Instant run -> Enable instant run to hot swap code/resource changes on deploy (default enabled)
    3. Android Virtual Device Manager -> En uno de los emuladores pulsamos sobre la flechita que apunta hacia abajo -> Wipe data (por si hubiese quedado guardada una configuración diferente de la que acabamos de establecer).

    Para ejecutar siempre el dispositivo en Cold Boot

    1. Abrimos el Android Studio.
    2. Vamos al Android Virtual Device Manager.
    3. Pulsamos en el lápiz del dispositivo que queremos editar.
    4. Show advanced Settins
    5. Emulated Performance -> Boot Option: Cold Boot

    Componente inicial

    import React from 'react';
    import { View, Text, Button } from 'react-native';
    
    const btnPulsado = _ => {
      alert(111);
    }
    
    const App = () => {
      return (
        <View style={styles.container}>
          <Text style={styles.title}>Restaurant Review </Text>
          <Text style={styles.title}>Restaurant Review </Text>
          <Button title="Botón" onPress={btnPulsado}/>
        </View>
      );
    };
    
    export default App;

    Componentes de react native

    • Equivalente a los div de HTML. Sirve para agrupar componentes.
      <View><View>
    • <Button title="Cerrar" onPress={ () => {btnPulsado()}}/>
    • <Text>El texto<Text>
    • FlatList / mainList (listas de elementos)
    • AsyncStorage (equivalente a localStorage)
    • const imagen = require ('./images/picture.jpg');
      <Image source={imagen} />
      
      //Con la imagen podríamos usar los situientes estios:
      imgExplicacion:{
      	width: '100%',
      	resizeMode: 'contain'
      }
    • Cargar un combo desplegable:
      <Picker style={styles.container} 
        selectedValue={microCicloActivo}
        onValueChange={valor => setValor(valor)}>
          <Picker.Item key=0 value=0 label="titulo 0"/>
          <Picker.Item key=1 value=1 label="titulo 1"/>
          <Picker.Item key=2 value=2 label="titulo 2"/>
      </Picker>
    • Cargar un cuadro de diálogo. Cargaremos una librería externa para hacerlo.

    Estilos

    En React Native es como si todos los elmentos tuviesen aplicado el estilo css display:flex por defecto.

    const App = () => {
    	return (
    		<View style={styles.wrapper}>
    			<View style={styles.container}>
    			<Text style={styles.col}>COL1</Text>
    			<Text style={styles.col}>COL2</Text> 
    		</View>
    		<Text style={styles.fila_abajo}>FILA ABAJO</Text>
    	</View>
    	);
    };d
    
    const styles = StyleSheet.create({
      container: {
        backgroundColor: 'orange',
        flexDirection: 'row',
        justifyContent: 'center',//flex-start, flex-end
      },
      col: {
        fontSize:30,
        borderColor:'black',
        borderWidth:3,
        padding:20
      },
      wrapper:{
        height: '100%',
        backgroundColor:'pink',
        flexDirection:'column',
        justifyContent: 'space-between'
    
      },
      fila_abajo: {
        textAlign:'center',
        backgroundColor:'green'
      }
    });
    styles en react native
    Descargar ejemplo estilos React Native

    Hacer peticiones desde el teléfono móvil a mi ordenador

    IP privada: identifica a un dispositivo dentro de mi red. Cuando ejecutamos la aplicación con Expo, esta ip identifica a mi ordenador. Tendré que usar esta ip en lugar de localhost para poder acceder a la información desde mi movil, ya que 127.0.0.1 es una ip local que sólo funciona cuando accedo desde el propio dispositivo.

    React Native Navigation

    Documentación oficial.

    Un stack es un grupo de páginas (puede haber 1 o más)

    Para cambiar de página utilizando la navegación que hemos implementado, usaremos:

    onPress={() => { props.navigation.navigate({ routeName: 'Calendar' })}}

    Navegación con StackNavigator

    npm i react-navigation --save 
    sudo npm i react-navigation-stack --save
    sudo npm i react-native-gesture-handler --save
    import React from 'react';
    import AppContainer from './application/AppContainer';
    
    export default App = () => <AppContainer/>

    El siguiente módulo carga:

    Todo ello en un solo documento. Más adelante veremos una forma apropiada de segmentar el proyecto.

    ./application/AppContainer.jsimport React from 'react';
    import { createAppContainer } from 'react-navigation';
    import { createStackNavigator } from 'react-navigation-stack';
    
    import HomeScreen from '../screens/home';
    import Page2Screen from '../screens/page2';
    
    
    const AppNavigator = createStackNavigator(
      {
        Home: HomeScreen,
        Page2: Page2Screen,
      },
      {
        initialRouteName: 'Home',
    
        defaultNavigationOptions: {
          headerStyle: {
            backgroundColor: 'pink',
          },
          headerTintColor: '#fff',
          headerTitleStyle: {
            fontWeight: 'bold',
          },
        },
      }
    );
    
    export default createAppContainer(AppNavigator);
    screens/Homeimport React from 'react';
    import { StyleSheet, Text, View, Button } from 'react-native';
    
    export default function Home(props) {
      return (
        <View>
          <Text>Esta es la pagina Home2</Text>
    
          <Button
            title="Go to Page2"
            onPress={() => props.navigation.navigate('Page2')} />
    
        </View>
      );
    }
    Descargar recurso

    Navegación con StackNavigator Reestructurada

    Carga del módulo de navegación principal

    Reubicamos el App.js dentro de la nueva carpeta src. Para ello tendremos que redefinir la ruta en ./node_modules/expo/AppEntry.js.

    ./src/App.jsimport React from 'react';
    import Navigation from './navigation/navigationContainer';
    
    export default _ => <Navigation />

    Cargamos la navegación principal.

    ./src/navigation/navigationContainer.jsimport { createSwitchNavigator, createAppContainer } from 'react-navigation';
    import MainNavigator from './MainNavigator/mainNavigator';
    
    export default createAppContainer(createSwitchNavigator(
        {
            MainNavigator
        },{
            
        }
    ))

    Definimos la navegación principal.

    ./src/navigation/MainNavigator/mainNavigator.jsimport { createStackNavigator } from 'react-navigation-stack';
    import { config } from '../NavigationConfigs/stackConfig';
    import simpleStacks from '../ScreenStacks/ScreenStacks';
    
    const {Home,Page2} = simpleStacks;
    
    export default createStackNavigator(
      {
        Home,
        Page2,
      },
     config
    );

    Definición del módulo de navegación principal

    La anterior navegación principal ha cargado dos cosas cuyo código mostramos a continuación:

    ./src/ScreenStacks/ScreenStacks.jsimport screens from "./screens";
    import stackConfig from "../NavigationConfigs/stackConfig";
    import { createStackNavigator } from 'react-navigation-stack';
    
    // Deconstruimos las screens
    const {
        HomePage,
        Page2Page
    } = screens;
    
    // Cargamos cada Stack con su correspondiente configuración
    const Home = createStackNavigator(HomePage, stackConfig);
    const Page2 = createStackNavigator(Page2Page, stackConfig);
    
    // Exportamos los stacks
    export default {
        Home,
        Page2
    }
    ./src/navigation/ScreenStacks/screens.js// Importamos todos los screens que hemos definido en la carpeta screens
    
    import Home from "../../screens/home";
    import Page2 from "../../screens/page2";
    
    // Exportamos un objeto en el que vinculamos cada page a su correspondiente screen
    export default {
        HomePage: {
            Home, 
        },
        Page2Page:{
            Page2
        }
    }
    ./src/navigation/NavigationConfigs/stackConfig.jsexport const withHeader ={
    	header: {
    		title: 'title'
    	}
    }
    
    export default {
    	headerStyle: {
    		backgroundColor: 'pink',
    	},
    	headerTintColor: '#fff',
    	headerTitleStyle: {
    		fontWeight: 'bold',
    	},
    }
    Descargar recurso

    Navegación con BottomNavigator

    ./src/navigation/MainNavigator/mainNavigator.jsimport { createStackNavigator } from 'react-navigation-stack';
    import { config } from '../NavigationConfigs/stackConfig';
    import simpleStacks from '../ScreenStacks/ScreenStacks';
    import bottomStacks from '../BottomNavigation/bottomNavigation';
    
    const {Home, Page2} = simpleStacks;
    
    export default createStackNavigator(
      {
        Home,
        bottomStacks
      },
     config
    );
    ./src/navigation/BottomNavigation/bottomNavigation.jsimport {bottomScreenStacks} from "../ScreenStacks/ScreenStacks";
    import Config from '../NavigationConfigs/bottomNavigationConfig';
    import { createBottomTabNavigator } from 'react-navigation-tabs';
    
    export default createBottomTabNavigator(bottomScreenStacks, Config);
    ./src/navigation/ScreenStacks/ScreenStacks.js...
    //bottom stack screens
    export const bottomScreenStacks = {
        bottom1,
        bottom2
    }
    ./src/navigation/NavigationConfigs/bottomNavigationConfig.jsimport React from 'react';
    import Icon from 'react-native-vector-icons/Ionicons';
    import constants from "../../utils/constants";
    
    const {
    
    	myInitialRouteName,
    
    	//styles for the bottom navigator
    	backgroundBottomColor,
    	activeButtonColor,
    	inactiveButtonColor,
    	//icons
    	iconSize,
    	calendarIcon,
    	fridgeIcon,
    	gamesIcon,
    	profileIcon,
    	weeklyIcon
    } = constants
    
    export default {
    
    	defaultNavigationOptions: ({ navigation }) => ({
    		tabBarIcon: ({ focused, horizontal, tintColor }) => {
    			const { routeName } = navigation.state;
    			let iconName =
    				routeName == 'bottom1' ? calendarIcon :
    					routeName == 'bottom2' ? fridgeIcon : null ;
    
                // You can return any component that you like here!
    			return <Icon name={iconName} size={iconSize} color={tintColor} />;
    		},
    	}),
    
    	tabBarOptions: {
    		activeTintColor: activeButtonColor,
    		inactiveTintColor: inactiveButtonColor,
    		style: {
    			backgroundColor: backgroundBottomColor,
    		},
    		initialRouteName: myInitialRouteName
    	},
    }
    ./src/utls/constants.jsexport default {
    
    	myInitialRouteName: 'Calendar',
    
    	//styles for the bottom navigator
    	backgroundBottomColor: 'black',
    	activeButtonColor: 'orange',
    	inactiveButtonColor: 'white',
    
    	//icons
    	iconSize: 25,
    	calendarIcon:'ios-home' ,
    	fridgeIcon: 'ios-settings'     
    }
    Descargar recurso

    Navegación con DrawerNavigation

    appcontainer.jsimport React from 'react';
    import { createAppContainer } from 'react-navigation';
    import { createDrawerNavigator } from 'react-navigation-drawer';
    
    import HomeScreen from '../screens/home';
    import Page2Screen from '../screens/page2';
    
      
    
    const MyDrawerNavigator = createDrawerNavigator({
      Home: {
        screen: HomeScreen,
      },
      Notifications: {
        screen: Page2Screen,
      },
    });
    
    export default createAppContainer(MyDrawerNavigator);
    Descargar recurso

    Compilar con Expo en la nube

    1. Creamos una cuenta en Expo
    2. Añadimos al app.js los datos que definen el nombre del paquete en Android e Ios:
      "expo": {
        ...
        "ios": {
          "bundleIdentifier": "com.yourcompany.yourappname"
        },
        "android": {
          "package": "com.yourcompany.yourappname"
        }
      }
    3. Ejecutamos el código
      expo build:android

    Empaquetar con NPM

    1. Creamos una carpeta en nuestro ordenador en la que añadiremos el código del módulo. Dentro de esta carpeta, crearemos la carpeta src, que es donde almacenaremos los ficheros fuente que vamos a compilar para que estos compilados sean accesibles a través de npm.
    2. npm init
    3. Creamos el fichero cuyas funciones queremos exportar
      ./index.jsimport Paneles from './components/paneles';
      
      // En la aplicación que utilice el images, importaré sus funciones con el siguiente código:
      // import { images } from 'termine-core';
      import * as images from './caras';
      
      // Tal como hemos configurado el webpack.config.js debemos importar las imágenes que estén en formato svg para poder usarlas
      import './assets/img/svg/fondo.svg';
      
      export {
          Paneles, 
          images
      };
    4. Nos loqueamos con nuestra cuenta de npm:
      npm login
    5. npm run build
      npm publish
      Tras ejecutar este comando aparecerá el paquete en nuestra cuenta de npm, en la sección packages. Cada vez que publiquemos, será necesario aumentar el número de versión.
        "version": "1.0.0",
      • El primer número implica cambios que hacen mi API incompatible
      • El segundo es para cambios que tienen compatibilidad hacia atrás
      • El tercer número es para corrección de errores
    6. Ya podemos instalar el módulo en otro proyecto:
      npm i modulo --save
    7. Cargar nuestro módulo en otro proyecto

      Si el proyecto donde vamos a cargar nuetro módulo ya tiene React (lo habitual), tendremos que tener el siguiente código en el webpack.config.js para evitar exportar React en el bundle.

      webpack.config.jsmodule.exports = {
        entry: path.join(paths.SRC, 'index.js'),
        output: {
          // ..
          /* La siguiente línea hace la traducción de ES6 a commonjs
          commonJS es lo que utilizabamos para importar un módulo (mediante require) antes de ES6 
          */
          libraryTarget: 'commonjs2',
        },
        //..
        /*
      Si el proyecto que el que vamos a importar nuestro módulo
      ya utiliza React, debemos excluir estas librerías de la exportación utilizando el siguiente código:
      */ 
      if (!development) {
        // Don't bundle react or react-dom
        module.exports.externals = {
          react: 'react',
          'react-dom': 'react-dom',
        };
      }

      Además hemos de especificar en el package.json que donde vayamos a importar nuestro módulo debe tener estas dependencias. Si no lo hacemos el módulo no funcionará en el caso de que fuese importado en un proyecto que no tuviese estas dependencias:

      "peerDependencies": {
        "react": "16.9.0",
        "react-dom": "16.9.0"
      },

      Carar las imágenes empaquetadas en un módulo

      El siguiente código fuente del módulo hace un import(el cual se encarga de añadir la imágen al paquete final) y un export (que se encarga de exportar la ruta de la misma). Esta ruta, funcionará dentro del módulo, pero webpack se encarga de cambiarla al hacer el procesamiento, por tanto, no funcionará.

      Este código no exporta correctamente la fotoimport fondo from './assets/img/fondo.svg';
      export {
        fondo 
      }
      Código webpack.config.js encargado de ubicar las imágenes en la carpeta dist{
        test: /\.(jpg|jpeg|gif|png|wav)$/,
        loader: 'file-loader',
        options: {
          publicPath: '/malabares/statics/images/',
          outputPath: 'statics/images/',
          name: '[name].[ext]',
        },
      },
      {
        test: /\.(eot|svg|ttf|woff|woff2|otf)$/,
        loader: 'file-loader',
        options: {
          publicPath: '/malabares/statics/vectors/',
          outputPath: 'statics/vectors/',
          name: '[name].[ext]',
        },
      },

      Para solventar esto, tenemos dos opciones:

      Importar la foto en el módulo para que sea añadida al paquete

      Este código no exporta correctamente la foto

      import fondo from './assets/img/fondo.svg';

      Importar la foto desde la aplicación que utiliza el módulo:

      import fondo from 'malabares-core/dist/statics/vectors/fondo.svg'

      Cargar un módulo localmente (sin publicarlo en npm)

      Los pasos descritos anteriormente son comunes tanto para publicar en internet como para cargar un módulo directamente desde nuestro ordenador.

      En el caso de que querramos cargar un módulo local:

      1. Tomamos nota del nombre que queremos importar. Este nombre, está en el package.json.
        {
          "name": "malabares-core",
      2. Ejecutamos el siguiente comando en la raíz del módulo que queremos importar
        npm link
      3. En la raíz de la aplicación en la que queremos importar nuestro módulo ejecutamos el siguiente comando:
        npm link malabares-core

      Importar función que usa Prototype

      utils
      const getWeek = function () {
      
      };
      
      window.Date.prototype.getWeek = getWeek;
      import './utils';
      
      const miFuncion = cumplido => {
          const currentWeek = new Date().getWeek();
      }
      
    icono de mandar un mailPreguntame lo que quieras!
    Pablo Monteserín
    contacta conmigoPablo Monteserín

    El servicio de resolución de dudas técnicas es sólo para los usuarios premium. Si tienes cualquier otra duda, usa el formulario de contacto. ¡Gracias!