Definición de rutas con React Router

En una aplicación React se utilizan “rutas” para definir URLs que, a su vez, activarán ciertos componentes.

Lo habitual es que nuestra aplicación tendrá un componente principal, comunmente llamado App, y será aquí donde realizaremos la definición de las rutas y otras configuraciones como el layout.

Como pre-requisito para utilizar esta funcionalidad, debemos importar los componentes BrowserRouter, Switch y Route con la siguiente directiva:

import {BrowserRouter, Route, Switch} from 'react-router-dom';

La implementación se realiza dentro del componente función App (o sea, que no lleva render()):

function App() {
  return (
    <BrowserRouter>
      <Layout>
        <Switch>
          <Route exact path="/" component={Home} />
          <Route exact path="/posts" component={Posts} />
          <Route exact path="/posts/new" component={PostsNew} />
          <Route exact path="/posts/:postId/edit" component={PostsEdit} />
          <Route component={NotFound} />
        </Switch>
      </Layout>
    </BrowserRouter>
  );
}

En este ejemplo, tenemos 5 rutas. Las rutas son elegidas por React en base a un elemento Switch. Según la ruta que escribamos en el navegador, si encuentra una coincidencia exacta con la propiedad path, activará el component definido en la propiedad component.

Por ejemplo, si escribimos la ruta /posts/new en el navegador, se activará el componente PostsNew (obviamente, todos los componentes que se llaman aquí deben ser importados en el archivo JS).

Tenemos también un ejemplo con una ruta que recibe una variable postId. Esta variable se indica con dos puntos adelante. También tenemos el caso por descarte, si no encuentra ninguna de las rutas indicadas, mostrará el componente NotFound.

Nótese que el componente que se active estará inserto dentro del componente Layout. Este es un componente especial que se encarga de mostrar el diseño común para todas las páginas del sitio. El código completo del componente Layout podría ser algo así:

import React from 'react';

import MyNavbar from './MyNavbar';

function Layout(props) {
  return (
    <React.Fragment>
      <h1>Centraldev</h1>
      <MyNavbar />
      {props.children}
      <footer>&copy; 2021 Centraldev - All rights reserved</footer>
    </React.Fragment>
  );
}

export default Layout;

Igualmente, tengan en cuenta que esto, a su vez, irá dentro del div#app que normalmente encontramos en el archivo public/index.html:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
  <title>Mi página</title>
</head>

<body>
  <div id="app"><!-- aquí se inserta todo --></div>
</body>

</html>

¿Por qué es esto? Esto es porque en el archivo index.js (en src) tenemos lo siguiente:

const container = document.getElementById('app');
ReactDOM.render(<App />, container);

Este diagrama explica cómo se intercomunican los distintos archivos:

Archivos básicos en React.

Consumo de una API con React

Vamos a ver un ejemplo muy simple y básico de cómo proceder a implementar el consumo de una API REST desde React. Este artículo asume que ya se tienen algunos conocimientos básicos de React.

El primer paso consiste en inicializar las variables que necesitaremos para manejar los distintos estados de nuestro componente, para guardar los datos que recibimos de la API y el número de página.

Estas variables las crearemos como variables de estado pero antes vamos a inicializarlas con sus valores predeterminados:

state = {
    loading: true,
    error: null,
    data: {
      results:[]
    },
    nextPage: 1,
  };

Este código también puede ir en el constructor.

Los datos los vamos a solicitar a la API en el evento componentDidMount(), es decir, cuando el componente se ha insertado en el DOM. Para esto vamos a llamar a una función fetchData() que también crearemos:

componentDidMount(){
  this.fetchData();
}

fetchData = async () => {
  this.setState({ loading: true, error: null });

  try {
    const response = 
      await fetch(${url}?page=${nextPage});    const data await response.json();

    this.setState({
      data: { results: [].concat(this.state.data.results, data.results) },
      loading: false,
      nextPage: this.state.nextPage + 1
    });
  }
  catch(error){
    this.setState({loading: false, error: error});
  }
}

La función fetchData es asíncrona porque a su vez llama a métodos asíncronos, como es fetch. Por eso la debemos declarar con la palabra clave async.

En la función empezamos asignando el estado de loading en true ya que queremos indicar que estamos realizando la carga de datos desde la API. Luego utilizaremos esta variable para mostrar un mensaje de “loading” o también se puede emplear una animación. La variable de estado error la asignamos en null porque no tenemos ningún error de carga.

Dentro de un bloque try llamamos a la función fetch, la cual es asíncrona, por eso debemos llamarla con la palabra clave await. El método fetch es el que realiza la petición a la API y recibe, como argumento, la URL hacia donde debemos enviar la petición. El resultado de esta petición lo guardamos en la variable response.

Para obtener los datos a partir de la variable response llamamos a otra función asíncrona que es json().

Los datos obtenidos los guardaremos en la variable de estado data, mediante el método setState(). En nuestro ejemplo, el array con los resultados son un objeto results dentro de data.

Para ir sumando los datos que obtenemos en sucesivas paginaciones utilizamos el método concat() y vamos agregando estos resultados a this.state.data.results.

Render

El listado de los resultados es muy sencillo. Lo haremos en la función render(). Para esto utilizaremos map():

<ul>
  {this.state.data.results.map(character => (
    <li key={character.id}>{character.name}</li>
  ))}
</ul>

En el método render también vamos a mostrar el texto (o animación) para el estado “loading” y el mensaje de error, si existiese:

{this.state.loading && (
  <div>Loading...</div>
)}

if (this.state.error) {
  return (
  <div>An error has occurred: {this.state.error.message}</div>
)}

Por último, no debemos de olvidarnos del botón para cargar los resultados de las siguientes páginas. Este será un botón que solo se mostrará cuando this.state.loading sea false y llamará a la función fetchData() en su evento onClick:

{!this.state.loading && (
  <button onClick={() => this.fetchData()}>Load more</button>
)}

Consulta el tamaño de las tablas en SQL Server

Cuando estamos analizando por qué una base de datos ocupa mucho espacio, una forma de investigarlo es visualizar el tamaño que ocupa cada tabla.

Esto puede obtenerse fácilmente con una consulta como esta:

USE [NombreBD] -- reemplazar nombre base de datos
GO
SELECT
s.Name AS SchemaName,
t.Name AS TableName,
p.rows AS RowCounts,
CAST(ROUND((SUM(a.used_pages) / 128.00), 2) AS NUMERIC(36, 2)) AS Used_MB,
CAST(ROUND((SUM(a.total_pages) - SUM(a.used_pages)) / 128.00, 2) AS NUMERIC(36, 2)) AS Unused_MB,
CAST(ROUND((SUM(a.total_pages) / 128.00), 2) AS NUMERIC(36, 2)) AS Total_MB
FROM sys.tables t
INNER JOIN sys.indexes i ON t.OBJECT_ID = i.object_id
INNER JOIN sys.partitions p ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
INNER JOIN sys.allocation_units a ON p.partition_id = a.container_id
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
GROUP BY t.Name, s.Name, p.Rows
ORDER BY s.Name, t.Name
GO 


Tengamos en cuenta que las bases de datos de SQL Server Express solo pueden ocupar hasta 10 GB de espacio por lo tanto es importante monitorear este dato.

SQL Table Size Query

Cómo reducir el tamaño del archivo de Log de SQL Server

SI nuestro archivo de log es muy grande probablemente tengamos problemas con el espacio en el disco más pronto que tarde.

Si nuestra base de datos tiene muchas transacciones pero maneja poco volumen de datos, es común que tengamos un archivo de datos (.mdf) relativamente pequeño y un archivo de log bastante más grande (.ldf).

Una de las operaciones más comunes para achicar el tamaño del archivo de log es recurrir a la opción Tasks > Shrink > Files del SQL Server Management Studio.

En la lista File type escogeremos entonces Log y luego nos mostrará en Available free space cuanto espacio se puede liberar. Para ejecutar la operación, hacer click en OK.

Sin embargo, es posible que ejecutes la operación Shrink File pero el tamaño del log no se reduce.

En este caso hay una alternativa que podemos emplear en el caso de bases de datos que NO son de producción: Para esto hacemos click derecho sobre la base de datos y elegimos Properties y luego Options. En la opción Recovery Mode nos aseguramos que diga Simple (no Full). Luego procedemos a hacer el Shrink como explicamos arriba.

Alternativamente podemos ejecutarlo en Transact-SQL:

ALTER DATABASE mydatabase SET RECOVERY SIMPLE
DBCC SHRINKFILE (mydatabase_Log, 1)

Tengamos en cuenta que el Recovery Mode afecta la posibilidad de restaurar una base en el futuro ya que en los logs se guarda la información de todas las transacciones. Si queremos poder volver a restaurar la base a un estado pasado en el futuro, tenemos que asegurarnos de hacer una copia de seguridad del archivo de log.