Una pequeña aplicación con React

En este post vamos a hacer una pequeña aplicación en React la cual se conectara a la API de Github para obtener información sobre la organización de Rails.

Instalando nuestro entorno de desarrollo

Para ello debemos tener instalado lo siguiente para poder correr JavaScript:

  • Node.js
  • npm v5.2 o superior

Si aún no tienes instalado Node.js puedes descargar el instalador desde su página oficial Node.js, esto también te instalará una versión de npm solo verifica que tu versión de npm sea 5.2 o superior corriendo el siguiente comando en tu terminal:

> npm -v

Si la versión es inferior puedes hacer el upgrade con el comando:

> npm update -g npm

La razón por la cual necesitamos la versión 5.2 de npm es porque viene con la herramienta npx la cual nos facilitara la instalación de los paquetes necesarios para nuestra aplicación.

Una vez que tengamos nuestro ambiente de desarrollo listo, el siguiente paso es crear nuestra aplicación de React de una forma sencilla utilizando su CLI que nos provee las configuraciones necesarias de WebPack, Babel y React, para ello ejecutaremos el siguiente comando en la terminal:

> npx create-react-app github-app

Esto nos creará un directorio llamado "github-app" el cual contiene la estructura inicial de nuestra aplicación.

A continuación nos movemos al directorio "github-app" y inicialiamos nuestra aplicación con el comando "npm start"

> cd github-app
> npm start

Después de ejecutar este comando se abrirá una aplicación básica de React en tu explorador.

Vamos a necesitar react-router-dom para el manejo de rutas y bootstrap para darle estilo a nuestra aplicación.

> npx install react-router-dom@4.3.1 -s
> npx install bootstrap -s

Finalmente levantamos nuestro servidor de desarrollo ejecutando el comando:

> npm start

Nota: Con esto cada vez que hagamos un cambio a nuestra aplicación estos se verán reflejados de manera automática en nuestro navegador, sin la necesidad de recargar la página o el servidor.

Deberías ver una página como la siguiente:

Creando nuestra aplicación

Una vez instalado estos paquetes vamos a hacer uso de ellos, primero vamos a agregar algunas rutas con la librería de react-router la cual provee componentes de React para su manejo.

Ahora vamos a conectar nuestra aplicación al componente principal de react-router llamado "BrowserRouter". Este va a administrar que componentes deben ser renderizados con base a la URL, esto lo hacemos desde el componente raíz, en este caso "App" el cual se encuentra dentro del archivo "index.js".

// index.js

import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

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

serviceWorker.unregister();

A continuación, vamos a crear un nuevo archivo donde se alojará nuestro componente llamado Repos en el cual mostraremos la lista de repositorios que existen en la organización de Rails:

// src/Repos.js

import React, { Component } from 'react';

export class Repos extends Component {
   render() {
     return <div>Aqui se mostrara la lista de repositorios</div>;
   }
}

Y en nuestro archivo "App.js" vamos a importar nuestro componente y asignarle una ruta "/repos":

// App.js
import React, { Component } from 'react';
import { Route } from 'react-router-dom';
import logo from './logo.svg';
import './App.css';
import 'bootstrap/dist/css/bootstrap.css';
import { Repos } from './Repos';

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className='App-title'>Explora Rails org en Github</h1>
        </header>
        <Route path="/repos" component={Repos}></Route>
      </div>
    );
  }
}

export default App;

Para mostrar nuestro componente "Repos" en el root path solo debemos cambiar la configuración de esta ruta en el archivo "App.js" de la siguiente manera:

<Route path="/" component={Repos}></Route>

Al guardar el archivo deberías poder ver el este mensaje "Aquí se mostrará la lista de repositorios"

Tip: Para que el logo de React con fondo negro no tome toda la pantalla visible, elimina la propiedad de css "min-height: 100vh;" de la clase ".App-header" en el archivo "App.css".

A continuación, vamos a agregar la lógica para que nuestro componente "Repos" muestre los repositorios que existen dentro de la organización Rails en GitHub.

Primero vamos a crear un nuevo archivo llamado "githubData.js" en el cual tendremos toda la lógica para la interacción con el API de GitHub.

Básicamente abstraemos las peticiones al API de GitHub en una clase.

// githubData.js

class GithubData {
  org = 'rails';
  orgBaseUrl = 'https://api.github.com/orgs/'

  constructor() {
    this.headers = new Headers();
    this.headers.append('Accept', 'application/vnd.github.v3+json');
  }

  getRepos() {
    let request = new Request(`${this.orgBaseUrl}${this.org}/repos`, { headers: this.headers });

    return fetch(request).then((res) => {
      return res.json();
    });
  }
}

export let githubData = new GithubData()

Ahora necesitamos agregar la funcionalidad para mostrar la lista de repos dentro de nuestro componente "Repos", para ello vamos agregar una simple tabla y haremos uso del método "componentDidMount" para agregar elementos a la tabla una vez que se haya renderizado el componente, hacemos uso de este método debido a que en este punto nuestro componente ya se montó en la aplicación y no importa cuando se resuelva la petición al API de GitHub se podrá visualizar la información correctamente

// Repos.js

import React, { Component } from 'react';
import{ githubData } from './githubData';

export class Repos extends Component {
  constructor() {
    super();
    this.state = {
      repos: []
    };
  }

  componentDidMount() {
    githubData
      .getRepos()
      .then(repos => this.setState({ repos }));
  }

  renderRepoInfo(repo) {
    const link = repo.homepage ? (<a href={repo.homepage} target='_blank'>Home Page</a>) : (<span>-</span>)
    return(
      <tr key={repo.id}>
        <td> {repo.name} </td>
        <td> {repo.stargazers_count} </td>
        <td> {repo.watchers_count} </td>
        <td> {repo.forks_count} </td>
        <td> {repo.open_issues_count} </td>
        <td>{link}</td>
      </tr>
    );
  }

  render() {
    return (
      <div>
        <h3>Repos in the org {githubData.org}</h3>
        <table className='table'>
          <thead>
            <tr>
            <th>Name</th>
            <th>Stars</th>
            <th>Watches</th>
            <th>Forks</th>
            <th>issues</th>
            <th>Home Page</th>
          </tr>
          </thead>
          <tbody>{this.state.repos.map(this.renderRepoInfo)}</tbody>
        </table>
      </div>
      );
  }
}

Una vez hecho esto deberíamos ver la lista de repositorios que existen en la organización Rails en GitHub

Como podemos observar crear una pequeña aplicación usando React no es tan complicado, ahora agreguemos un poco más de funcionalidad, por ejemplo, una nueva ruta para mostrar los miembros de la organización.

Para ello agregaremos un par de métodos a nuestra clase "GithubData" para hacer el request a la API de GitHub y obtener la lista de miembros.

// githubData.js

class GithubData {
  org = 'rails';
  orgBaseUrl = 'https://api.github.com/orgs/'
  userBaseUrl = 'https://api.github.com/users/
  
  ...
  
  getRepos() {
    ...
  }

  getMembers() {
    let request = new Request(`${this.orgBaseUrl}${this.org}/members`, { headers: this.headers });
    return fetch(request)
      .then((res) => { return res.json()
      .then(members => { return members; });
    });
  }

  getMemberDetails(memberLogin) {
    let userRequest = new Request(`${this.userBaseUrl}${memberLogin}`, { headers: this.headers });
    let userReposRequest = new Request(`${this.userBaseUrl}${memberLogin}/repos`, { headers: this.headers });

    return Promise.all([fetch(userRequest), fetch(userReposRequest)])
      .then(([member, repos]) => {
        return Promise.all([member.json(), repos.json()]);
      })
      .then(([memberValue, reposValue]) => {
        return {
          member: memberValue,
          repos: reposValue
        }
      });
  }
}

export let githubData = new GithubData();

El método "getMembers" nos devolverá una lista de los miembros de la organización, mientras que el método "getMemberDetails", el cual espera como parámetro el login id de un miembro de la organización, devolverá los detalles del miembro seleccionado.

Ahora necesitamos un nuevo componente el cual se encargará de mostrar la lista de miembros, para esto vamos a crear un nuevo archivo llamado "Member.js"

// src/Members.js

import React, { Component } from 'react';
import { githubData } from './githubData';
import { Link } from 'react-router-dom';

export class Members extends Component {
  constructor() {
    super();
    this.state = {
      members: []
    };
  }

  componentDidMount() {
    githubData
      .getMembers()
      .then(members => this.setState({ members }));
 }

 renderMember(member) {
   return(
    <div className='col-md-3 card' key={member.login}>
      <div className='card-body'>
        <div>
          <img
          src={member.avatar_url}
          alt={member.login}
          className='avatar'
          />
        </div>
        <div>
          <a target='_blank' className='text-left' href={member.html_url}>
            {member.login}
          </a>
          <br />
          <a target='_blank' href={member.html_url + '?tab=repositories'}>
            Repos
          </a>
          <br />
          <Link to={`/member/${member.login}`}>Ver Detalles</Link>
        </div>
      </div>
    </div>
   );
 }

 render() {
   return(
     <div>
       <h3>Miembros de la organización: {githubData.org}</h3>
       <div className='row'>
         {this.state.members.map(this.renderMember)}
       </div>
     </div>
   );
 }
}

Una vez resuelta nuestra promesa se actualizará el estado de nuestro componente "Members" con la respuesta del API de GitHub, en automático React invocará el método "render".

Nota: El método "setState" se ejecuta de forma asíncrona por lo cual no podemos acceder al estado de forma inmediata, como consejo procura que sea la última acción a ejecutar en tu método.

Vamos a darle estilo a nuestra lista de miembros agregando las siguientes reglas en el archivo "App.css"

/* App.css */

.avatar{
  height: 170px;
  width: 170px;
}

.tile{
  margin-top: 10px;
}

Para poder acceder a esta nueva lista necesitamos agregar una nueva ruta en el archivo "App.js" como lo vimos anteriormente.

import React, { Component } from 'react';
import { Route, Switch } from 'react-router-dom';
import logo from './logo.svg';
import './App.css';
import 'bootstrap/dist/css/bootstrap.css';
import { Repos } from './Repos';
import { Members } from './Members';

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className='App-title'>Explora Rails org en Github</h1>
        </header>
        <Switch>
          <Route path="/" exact component={Repos}></Route>
          <Route path="/members" component={Members}></Route>
        </Switch>
      </div>
    );
  }
}

export default App;

Como habrás notado hemos incluido un nuevo componente Switch el cual indica que renderice el primer componente que sea  igual que el nombre de la ruta, esto para evitar que se renderice más de un componente.

Si vistamos la url "http://localhost:3000/members" deberíamos ver la lista de miembros de la organización Rails

Para no tener que estar cambiando la url manualmente vamos a agregar un menú, para ellos necesitamos importar otro componente de "react-router-dom" llamado Link

// App.js

import React, { Component } from 'react';
import { Link, Route, Switch } from 'react-router-dom';
import logo from './logo.svg';
import './App.css';
import 'bootstrap/dist/css/bootstrap.css';
import { Repos } from './Repos';
import { Members } from './Members';

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className='App-title'>Explore the Facebook org on Github</h1>
        </header>
        <section className='content'>
          <div className='row'>
            <div className='col-md-2'>
              <nav className='navbar'>
                <ul className='navbar-nav'>
                  <li className='nav-item'>
                    <Link className='nav-link' to='/'>Repositorios</Link>
                  </li>
                  <li className='nav-item'>
                    <Link className='nav-link' to='/members'>Miembros</Link>
                  </li>
                </ul>
              </nav>
            </div>
            <div className='col-md-9'>
              <Switch>
                <Route path="/" exact component={Repos}></Route>
                <Route path="/members" component={Members}></Route>
              </Switch>
            </div>
          </div>
        </section>
      </div>
    );
  }
}

export default App;

Si hacemos click en cualquiera de las dos opciones de nuestro menú nos mostrará la lista ya sea de Repositorios o de Miembros de la organización.

Por último, vamos a agregar la página en la cual se mostrará la información de un miembro seleccionado. Para esta vamos a crear un nuevo archivo llamado "MemberDetails.js" y agregamos un componente llamado "MemberDetails".

// src/MembersDetails.js

import React, { Component } from 'react';
import { githubData } from './githubData';

export class MemberDetails extends Component {
  constructor(props) {
    super(props);
    this.state = {
      member: null,
      repos: []
    };
  }

  get Login() {
    return this.props.match.params.login;
  }

  componentDidMount() {
    githubData
      .getMemberDetails(this.Login)
      .then(({ member, repos }) => {
        this.setState({ member, repos })
      });
  }

  renderMemberDetails() {
    const{ member, repos } = this.state;

    if(!member) {
      return null;
    }

    return (
      <div className='row'>
        <div className='col-md-6'>
          <div className='row'>
            <div className='col-md-6'>Name: </div>
            <div className='col-md-6'>{member.name}</div>
            <div className='col-md-6'>Company: </div>
            <div className='col-md-6'>{member.company}</div>
            <div className='col-md-6'>Number of Repos: </div>
            <div className='col-md-6'>{member.public_repos}</div>
            <div className='col-md-6'>Number of Followers: </div>
            <div className='col-md-6'>{member.followers}</div>
            <div className='col-md-6'>Following Count: </div>
            <div className='col-md-6'>{member.following}</div>
          </div>
        </div>
        <div className='col-md-6'>
          <img
          src={member.avatar_url}
          alt={`${member.name}’s pic`}
          height='150'
          width='150' />
        </div>
        <h4>Repositorios de {member.name}</h4>
        <table className='table'>
          <thead>
            <tr>
            <th>Name</th>
            <th>Stars</th>
            <th>Watches</th>
            <th>Forks</th>
            <th>issues</th>
            <th>Home Page</th>
            </tr>
          </thead>
          <tbody>{repos.map(this.renderRepo)}</tbody>
        </table>
      </div>
    );
  }

  renderRepo(repo) {
    let link = repo.homepage ? (<a href={repo.homepage} target='_blank'>Home Page</a>) : (<span>-</span>);
    return (
      <tr key={repo.id}>
        <td> {repo.name} </td>
        <td> {repo.stargazers_count} </td>
        <td> {repo.watchers_count} </td>
        <td> {repo.forks_count} </td>
        <td> {repo.open_issues_count} </td>
        <td>{link}</td>
      </tr>
      );
    }

  render() {
    return (
      <div>{this.renderMemberDetails()}</div>
    );
  }
}

Y en el archivo "App.js" agregamos la ruta para la página de detalles del miembro seleccionado

// App.js

...

<Switch>
  <Route path="/" exact component={Repos}></Route>
  <Route path="/members" component={Members}></Route>
  <Route path="/member/:login" component={MemberDetails}></Route>
</Switch>
...

Ahora al hacer click en cualquiera de los links "Ver Detalles" nos mostrará información del miembro seleccionado

Espero que este pequeño blog te haya ayudado a conocer un poco acerca de React y de algunas cosas que se pueden hacer, aún quedan muchas cosas por descubrir las cuales iremos revisando en futuros post.

Conclusiones

  • Podemos ver que es muy fácil crear componente en React y componerlos para crear vistas complejas.
  • El uso del paquete "react-router-dom" nos facilita el manejo de rutas sin necesidad de hacer un request al servidor, esto hace más dinámicos tus sitios.
  • React nos facilita la creación de elementos en el DOM de forma optimizada y sin tanto código.
  • Para proyectos pequeños, React puede ser no muy eficiente por la cantidad de paquetes que son necesarios, para ello necesitamos experimentar para darnos cuenta si nuestra aplicación es lo suficientemente compleja como para utilizar alguna herramienta como React.
  • React es una librería simple que nos permite la creación de componentes y su interacción, si necesitamos más funcionalidad podemos buscar paquetes especializados como son "react-router-dom", "redux-form", "reactstrap", entre otros.
  • El uso de herramientas como React nos reduce la tarea de tener que preocuparnos por mantener tu entorno de desarrollo actualizado, ya que lo hacen de manera automática y transparente para el desarrollador.



comments powered by Disqus

Siguenos

Boletí de noticias