Log 0.6

El último escalón en la escalera que comencé hace unos meses para llegar al punto de salida del proyecto Heres es conocer TypeScript.

TypeScript es un lenguaje de programación desarrollado y mantenido por Microsoft. Su gran utilidad radica en que se trata de un lenguaje sintácticamente estricto que añade tipado estricto a JavaScript.

El desarrollo de Heres-I se va a realizar totalmente en TypeScript por una serie de razones:

Definición de tipos

Algo clave en el desarrollo complejo de un producto software es definir claramente qué es lo que vamos a utilizar y cuál es su contenido. JavaScript nos permite dejar esos conceptos sin definir, abriendo así la puerta a futuros problemas de compilado. Con TypeScript esto no sucede porque, antes de ponernos a trabajar, debemos definir nuestras variables: cómo están formadas y qué tipo de datos van a contener.

Declaración de variables de entrada y de salida a funciones

TypeScript nos “obliga” a utilizar tipos también en las entradas y salidas de nuestras funciones, de nuestros componentes. Esto hace de mecanismo de control para establecer claramente qué esperamos recibir y qué esperamos obtener en cada llamada a una función.

De nuevo, TypeScript limita mucho los posibles errores de desarrollo asociados a la ambigüedad inherente en JavaScript.

Un ejemplo de una función en TypeScript sería:

import React from 'react';
import { Entry } from '../types';


const HospitalEntry: React.FC<{ entry: Entry }> = ({ entry }) => {

  return(
    <div>
      <h5>Hospital Check Entry</h5> 
      {entry.description}
    </div>
  );
};

export default HospitalEntry;

Es en la propia definición de la función donde ya declaramos el tipo de la entrada y de la salida, con lo que nos aseguramos en todo momento de llevar un control del flujo de datos.

Heres I, en marcha

Una vez he finalizado la parte de TypeScript y, a falta de concluir con la sección de React-Native para desarrollo móvil, ya me siento en condiciones de dar el paso con Heres I y empezar con un proyecto personal de mayor envergadura.

Los próximos devlogs estarán enfocados a una parte más teórica y estructural del desarrollo con la definición de requisitos y planificación del desarrollo de forma algo más exhaustiva.

Log 0.5

Nota: Este log hace referencia a la parte 8d del curso https://fullstackopen.com/en/part8/fragments_and_subscriptions

Suscripciones en GraphQL

Resulta interesante este concepto para permitir la actualización/notificación de cambios desde el servidor hacia el cliente.

El servidor Apollo hace uso de WebSockets para ello y nos permite informar a quien esté suscrito, es decir, a los componentes que nosotros decidamos, si se produce algún cambio en nuestra base de datos.

Se programa, sobre todo, en la parte de cliente, que es quien se va a suscribir: para eso hacemos uso de una función propia de Apollo, useSubscription:

  export const BOOK_ADDED = gql`
  subscription{
    bookAdded{
      title
    author{
      name
      born
    }
    published
    genres
    id
    }
  }
`
useSubscription(BOOK_ADDED, {
  onSubscriptionData: ({ subscriptionData }) => {
   const addedBook = subscriptionData.data.bookAdded
   updateCacheWith(addedBook)
  }
})
  

En la parte del backend debemos “suscribirnos” a este suscriptor que ya habremos previamente definido en nuestro schema.

Nuestro mutation que añade libros debe cambiar, puesto que en el proceso de cambiar, debemos lanzar una llamada a nuestro suscriptor para avisarle que se va a producir un cambio y, en esa llamada, facilitarle qué estamos cambiando.

Mutation: {
    addBook: async (root, args, context) => {
      const author = await Author.findOne({name: args.author })
      const currentUser = context.currentUser
      
      if(!currentUser){
        throw new AuthenticationError("not authenticated")
      }
            
      const book = new Book({...args, author: author
      })
      try{
        await book.save()
        // Añadimos la suscripción -------------
        pubsub.publish('BOOK_ADDED', { bookAdded: book })
        // -------------------------------------
        return book 
      }catch (error) {
        throw new UserInputError(error.message, {
          invalidArgs: args,
        })
      }

Una vez se produce un cambio generado por el useMutation(ADD_BOOK…) salta el onSubscriptionData y podemos, por tanto, usar una función callback para gestionar qué hacemos con los datos añadidos:

const updateCacheWith = (addedBook) => {
    const includedIn = (set, object) => 
      set.map(p => p.id).includes(object.id)

    const dataInStore = client.readQuery({ query: ALL_INFO })
    if(!includedIn(dataInStore.allBooks, addedBook)) {
      try{
      client.writeQuery({
        query: ALL_INFO,
        data: { allBooks: dataInStore.allBooks.concat(addedBook)}
      })}catch(error){
        console.log(error)
      }
    }
  }

Este es un ejemplo de función que lo que hace es actualizar la caché de GraphQL con los datos añadidos, puesto que uno de los problemas que presenta GraphQL es precisamente la falta de actualización automática cuando se produce un cambio en la base de datos.

Utilizando este mecanismo nos aseguramos, por un lado, que la caché de GraphQL está siempre actualizada con los últimos datos que tiene la base de datos y, por otro, que todos los usuarios que simultáneamente están accediendo al servicio reciben esta actualización de forma inmediata.

GraphQL me ha parecido una solución tremendamente interesante para implementar Heres I, aunque todavía estoy en la parte más inicial del proyecto y tengo algo de margen para decidir qué camino escoger en relación a la forma que voy a utilizar para obtener los datos desde el servidor.

Log 0.4

GraphQL

Esta sección corresponde con la parte 8 del curso.

GraphQL plantea una forma de interactuar con los datos distinta a la que conocemos como REST.

Para poner en marcha un servicio de GraphQL hacemos uso del servidor Apolo

npm i apollo-server

Y montamos un archivo que lanzaremos con node que incluya tanto la definición de las estructuras de los datos como la de las llamadas:

Definimos las estructuras y las consultas:

const typeDefs = gql`
  type Book {
    title: String!
    published: Int!
    author: String!
    id: ID!
    genres: [String!]!
  }
  type Query {
    bookCount: Int!
     allBooks(author: String, genre: String): [Book!]!
  }
`

Además, debemos definir lo que se conoce como resolvers, que hacen uso de las estructuras de consulta que hemos definido para responder:

const resolvers = {
  Query: {
    bookCount: () => books.length,
    allBooks: (root, args) => {
      if(!args.author && !args.genre){
        return books
      } if (!args.genre) {
      return(books.filter(book => book.author === args.author))
      }
      if (!args.author){
        return(books.filter(book => book.genres.includes(args.genre) ))
      }
      return(books.filter(book => book.author === args.author && book.genres.includes(args.genre) ))
      },
    }

En estos resolvers podemos incluir toda la lógica que queramos para responder en función de nuestras necesidades.

Con ello, GraphQL opera como traductor entre la consulta y los datos adaptando las respuestas a las necesidades, previamente definidas, del cliente.

Además, durante el desarrollo, si trabajamos con el servidor Apollo, tendremos un entorno de pruebas donde realizar las consultas y ver qué responde el servidor de forma rápida y cómoda.

Conclusiones

Pese a que en un principio era algo reticente y consideraba que mis proyectos irían todos enfocados a servicios REST, esta solución me está pareciendo cada vez más interesante y más útil y es posible que me plantee incluirla en Heres I.

Log 0.3

Objetivos:

  • Traslado de Hooks a Redux
  • Interfaz

Migrando un proyecto a Redux

Pese a que no creo que utilice en mis proyectos la solución de Redux, el curso me ha venido bien para entender su concepto.

Uno de los problemas que puede presentar trabajar con Hooks en React es que la información suele viajar en un único sentido: de los componentes padre a los componentes hijo.

Existen alternativas para resolver esta unidireccionalidad, pero originalmente este mecanismo no estaba pensado para ir en otra dirección.

Así, cuando queremos emplear un valor en distintas partes de nuestra aplicación de forma generalizada, trabajar con Hooks puede resultar engorroso.

Redux es, simplificándolo, un gestor de variables globales que puede accederse desde cualquier punto de tu aplicación.

La forma de acceder a lo que se conoce por Store se hace en dos fases: Primero definimos la store en el nivel más alto de nuestra aplicación:

//..
import store from './store'
import { Provider } from 'react-redux'

ReactDOM.render(
  <Provider store={store}>
    <App ></App>
  </Provider>,
  document.getElementById('root')
)

De esta forma, la store está disponible en toda nuestra aplicación, siendo tan sencillo acceder a ella como:

//..
import { useDispatch, useSelector } from 'react-redux'

const users = useSelector(state => state.persons)
const dispatch = useDispatch()

En el primer caso, useSelector nos extrae la información de la store (en este caso el objeto ‘persons’).

En el segundo, creamos una variable dispatch que va a ser la encargada de lanzar los que son conocidos como “reducers”, funciones que permiten acceder al contenido de la store para modificarlo:

import userService from '../services/users.js'


const personsReducer = (state = [], action) => {
  switch(action.type){
    case 'SET_PERSONS': {
      return action.data
    }
    default:
      return state
  }

}


export const setPersons = () => {
  return async dispatch => {
    const persons = await userService.getAll()
    dispatch({
      type: 'SET_PERSONS',
      data: persons
    })
  }
}

export default personsReducer

Este es un ejemplo básico de reducer, en el que ya incluimos el método para acceder a él y lo exportamos directamente. De esta forma podemos usar dispatch llamando directamente al método del reducer y dejar que éste se encargue de toda la lógica: tanto la de la store de redux como la de la base de datos.

La migración ha sido sencilla en general, aunque tienes que cambiar un poco la perspectiva cuando pasas de Hooks a Redux porque ni funcionan igual ni se usan para lo mismo.

Log 0.2

Objetivos:

  • Custom Hooks
  • Webpack
  • Class components

Webpack

Una de las cosas nuevas que he aprendido estas semanas de estudio ha sido el concepto de transpilación. Aquí está mucho mejor explicado pero, a grandes rasgos, transpilar un código es trasladarlo a otro código con un nivel similar de abstracción: lo que vendría a ser, en lingüística, traducir de un idioma a otro.

El objetivo de esta parte del curso es entender el concepto empezando desde cero y sin usar el create-react-app que te monta todas las dependencias y la estructura de carpetas de forma automática.

Estructura de carpetas básica

Empezamos con esta estructura simple y sobre ella trabajaremos.

Lo primero es definir el package.json con las instrucciones y ajustes básicos.

{
  "name": "webpack-part7",
  "version": "0.0.1",
  "description": "practising webpack",
  "scripts": {},
  "license": "MIT"
}

Para luego añadir a nuestra instalación los archivos de webpack:

npm install --save-dev webpack webpack-cli

Mientras que el archivo de configuración de Webpack quedaría:

const path = require('path')

const config = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: 'main.js'
  }
}
module.exports = config

El problema con el que te encuentras al intentar transpilar React es que Webpack no está preparado para ello, así que habría que configurarlo de la siguiente manera:

const config = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'build'),
    filename: 'main.js',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        query: {
          presets: ['@babel/preset-react'],
        },
      },
    ],
  },
}

Lo mismo sucede cuando queremos transpilar código en CSS, debemos actualizar nuestra configuración de Webpack:

    {
      test: /\.css$/,
      loaders: ['style-loader', 'css-loader'],
    },

Para evitar tener que estar constantemente transpilando, Webpack ofrece una solución sencilla, webpack-dev-server:

npm install --save-dev webpack-dev-server

Existen muchas y distintas opciones que nos permiten configurar Webpack en condiciones para desarrollar con un flujo de trabajo rápido y eficiente. Una posible versión final de las configuraciones de webpack.config.js y package.json podrían ser:

const path = require('path')
const webpack = require('webpack')

const config = (env,argv) => {
  console.log('argv', argv.mode)

  const backend_url = argv.mode === 'production'
    ? 'https://blooming-atoll-75500.herokuapp.com/api/notes'
    : 'http://localhost:3001/api/notes'
  
  return{
    entry: ['@babel/polyfill','./src/index.js'],
    output: {
      path: path.resolve(__dirname, 'build'),
      filename: 'main.js'
    },
    module: {
      rules: [
        {
          test: /\.js$/,
          loader: 'babel-loader',
          query: {
            presets: ['@babel/preset-react', '@babel/preset-env'],
          },
        },
        {
          test: /\.css$/,
          loaders: ['style-loader', 'css-loader'],
        },
      ],
    },
    devServer: {
      contentBase: path.resolve(__dirname, 'build'),
      compress: true,
      port: 3000,
    },
    devtool: 'source-map',
    plugins:[
      new webpack.DefinePlugin({
        BACKEND_URL: JSON.stringify(backend_url)
      })
    ]
  }
}


module.exports = config
{
  "name": "webpack-part7",
  "version": "0.0.1",
  "description": "practising webpack",
  "scripts": {
    "build": "webpack --mode=production",
    "start": "webpack-dev-server --mode=development"
  },
  "license": "MIT",
  "devDependencies": {
    "@babel/core": "^7.11.6",
    "@babel/preset-env": "^7.11.5",
    "@babel/preset-react": "^7.10.4",
    "babel-loader": "^8.1.0",
    "css-loader": "^4.3.0",
    "style-loader": "^1.3.0",
    "webpack": "^4.44.2",
    "webpack-cli": "^3.3.12",
    "webpack-dev-server": "^3.11.0"
  },
  "dependencies": {
    "@babel/polyfill": "^7.11.5",
    "axios": "^0.20.0",
    "react": "^16.13.1",
    "react-dom": "^16.13.1"
  }
}

No obstante, soluciones como create-react-app ahorran esta tarea manual y permiten comenzar a trabajar prácticamente de forma inmediata sin a penas configurar nada.

Log 0.1

Objetivos:

  • Custom Hooks
  • Webpack
  • Class components

Aunque este es el primer episodio de lo que he decidido llamar ‘temporada 0’, en realidad estaríamos casi terminando esta parte.

Llevo unas cuantas semanas siguiendo un curso (muy recomendado) de Full Stack: https://fullstackopen.com/en/ y los objetivos hacen referencia a la Parte 7.

Usando librerías de estilos

La parte de la UI se puede gestionar con distintas librerías. Este curso hace uso de Bootstrap, aunque son innumerables las que ahora mismo cohabitan en el mercado.

La forma de instalar la versión asociada con React es bastante sencilla:

npm install react-bootstrap

Me parece realmente interesante el mundo de los frameworks de estilo: ayudan enormemente en la labor de diseño proporcionando una plataforma estandarizada con la que gestionar la parte visual de tu sitio.

Una tabla pre-formateada usando Bootstrap y React

La gracia de esta unión Bootstrap – React es que hace uso de la metodología por componentes de la segunda para poder desarrollar la interfaz.

Este es un ejemplo de formulario de acceso:

<Form onSubmit={onSubmit}>
   <Form.Group>
      <Form.Label>username:</Form.Label>
      <Form.Control
         type='text'
         name='username'
       ></Form>
       <Form.Label>password:</Form.Label>
       <Form.Control
         type='password'
        ></Form>
        <Button variant='primary' type='submit'>
          login
         </Button>
   </Form.Group>
</Form>

Existen múltiples alternativas, como Material UI de Google o, incluso, lo que se conoce como Styled Components, que es la creación de componentes con un estilo asignado:

const Boton = styled.button`
  background: Bisque;
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid Black;
  border-radius: 8px;
`
return (
    <div>
      <h2>Login</h2>
      <form onSubmit={onSubmit}>
        <Boton type='submit' primary=''>login</Boton>
      </form>
)

¡Arrancamos!

start

“Un viaje de mil millas comienza con un primer paso”

Lao-Tse

Hace ya mucho tiempo que la hormiguita de crear algo, fuera lo que fuese, venía incordiándome.

Hoy, por fin, pongo la primera piedra.

Este dev.log pretende convertirse en un cuaderno de bitácora donde ir anotando las idas y venidas de un viaje que confío tenga de todo lo que uno espera al lanzarse a la aventura.

ReactDOM.render(<App ></App>, document.getElementById('root'))