Drizzle para crear el front end de tu aplicación Blockchain

Ya sabes cómo funciona Blockchain, cómo se desarrollan los smart contracts de Ethereum, así como trabajar en local. Hoy vamos a ver cómo desarrollar una aplicación cliente, que se conecte con nuestra red e interactúe con nuestro contrato. Para este artículo, utilizaremos React.js y Drizzle, apoyándonos en el ejemplo del artículo anterior.

Crear una aplicación con React.js

Podrías utilizar cualquier otro framework, pero a mi me gusta mucho React 🙂 Si aún no lo tienes, debes instalar el modulo create-react-app a nivel global, para poder crear el esqueleto de los proyectos de una manera sencilla:

npm install -g create-react-app

Después, dentro del proyecto con Truffle, lanza el siguiente comando:

create-react-app client

Esto creará una carpeta llamada client, donde vivirá tu nueva aplicación.

Carpeta client dentro del proyecto con Truffle
Carpeta client dentro del proyecto con Truffle

Por otro lado, antes de continuar, necesitamos cambiar la ubicación donde se almacena el JSON que genera la compilación de los contratos. Por defecto se almacenan en build/contracts, pero podemos cambiar su ubicación a través del archivo truffle-config.js con lo siguiente:

const path = require('path');
module.exports = {
  contracts_build_directory: path.join(__dirname, 'client/src/contracts'),

...

A partir de este momento, cuando se compilen los contratos, el resultado irá a parar a client/src/contracts, en lugar de build/contracts, consiguiendo así que sean accesibles por la aplicación en React.js.

Instalar Drizzle

Ya tienes casi todo lo que necesitas para crear tu aplicación cliente para tu entorno de Blockchain. Hasta ahora, habíamos visto que con la librería web3 podíamos llamar los métodos de nuestros contratos desde JavaScript. Hoy me gustaría enseñarte a trabajar con Drizzle, librería que pertenece a la suite de Truffle Framework, y que se encarga de mantener sincronizados los datos de nuestros contratos, transacciones, etcétera de forma casi transparente para nosotros. Esta por debajo también utiliza web3 y Redux.

Drizzle es una librería que trabaja con web3 y Redux

Instálala dentro de la carpeta client:

npm install drizzle

Antes de continuar, ejecuta npm start para ver que todo va bien.

Registra y visualiza tus fichajes

Para este artículo, mi intención es que conozcas cómo crear aplicaciones clientes que conecten con tu red de Ethereum. El ejemplo que voy a desarrollar es simplemente la visualización de los fichajes realizados y la posibilidad de fichar. El código del ejemplo completo lo tienes en mi GitHub, pero vamos a ver el contenido de cada uno de los ficheros modificados para que entiendas qué hace cada uno.

client/src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

import { Drizzle } from 'drizzle';
import TimeControl from './contracts/TimeControl.json';

const options = {
    contracts: [TimeControl],
    web3: {
        fallback: {
            type: 'ws',
            url: 'ws://127.0.0.1:9545'
        }
    }
};

const drizzle = new Drizzle(options);

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

El archivo index.js en React.js es el punto de entrada a nuestra aplicación. En él recupero las librerías react y react-dom para poder renderizar el primer componente, que será App, y además la librería de Drizzle que acabamos de instalar. Para poder crear una instancia, lo primero que necesitamos es decir con qué smart contracts queremos trabajar, el protocolo por el cual nos vamos a conectar a nuestra red de Ethereum y la URL. Este objeto lo pasaremos como parte del componente App.

client/src/App.js

import React from 'react';
import './App.css';

import Register from "./Register";
import Registries from "./Registries";


class App extends React.Component {

  state = { loading: true, drizzleState: null };


  componentDidMount() {
    const { drizzle } = this.props;

    this.unsubscribe = drizzle.store.subscribe(() => {

      //every time the store updates, save the state from drizzle
      const drizzleState = drizzle.store.getState();

      if (drizzleState.drizzleStatus.initialized) {
        this.setState({ loading: false, drizzleState });
      }
    });
  }

  componentWillUnmount() {
    this.unsubscribe();
  }

  render() {
    if (this.state.loading) return "Loading Drizzle...";
    return (
      <div className="App">

        <Registries
          drizzle={this.props.drizzle}
          drizzleState={this.state.drizzleState}
        />
        <Register
          drizzle={this.props.drizzle}
          drizzleState={this.state.drizzleState}
        />
      </div>
    );

  }
}

export default App;

App es es el primer componente de nuestra jerarquía. En él vamos a utilizar la función componentDidMount para suscribirnos a todo lo que pase en nuestra instancia de Drizzle y, si el estado de esta cambia lo guardamos en drizzleState. También comprobamos si hemos sido capaces de conectarnos a nuestra red, ya que solo si es así podremos continuar con la carga de nuestra aplicación. Cuando el valor de loading sea false mostraremos dos nuevos componentes: Registries y Register.

client/src/Registries.js

import React from 'react';

export default class Registries extends React.Component {

    state = { ref: null };

    componentDidMount() {
        const { drizzle } = this.props;

        const contract = drizzle.contracts.TimeControl;

        const ref = contract.methods["GetMyRegistries"].cacheCall();

        this.setState({ ref });
    }

    render() {

        //get the contract state from drizzleState
        const { TimeControl } = this.props.drizzleState.contracts;

        //using the saved registries, get the variable we're interested in
        const registries = TimeControl.GetMyRegistries[this.state.ref];

        return (
            <div className="container">
                <h3>My Registries</h3>
                <small className="text-muted">Account: {this.props.drizzleState.accounts[0]}</small>
                <table className="table">
                    <thead>
                        <tr>
                            <th>EPO</th>
                            <th>Date Time</th>
                        </tr>
                    </thead>
                    <tbody>
                        {registries && registries.value.map(function (registry, index) {
                            let d = new Date(0); //The 0 there is the key, which sets the date to epoch
                            d.setUTCSeconds(registry);
                            return <tr key={index}><td>{registry}</td><td>{d.toLocaleString()}</td></tr>;
                        })}
                    </tbody>
                </table>
            </div>
        );
    }
}

Este componente muestra todos los fichajes realizados hasta ahora. Para ello, cuando el componente se ha montado, lo primero que hago es recuperar, a través de props, el objeto drizzle. A través de este puedo acceder a los contratos que configuré en index.js, en este caso a TimeControl. Aquí lo que me interesa es recuperar los fichajes que ya se han realizado que, si recuerdas, se obtenían a través de la función GetMyRegistries. Para hacer llamadas de solo lectura utilizamos la función cacheCall, la cual nos devuelve una referencia de dónde están los datos en el store que mantiene Drizzle. Por último, en render pintamos una tabla con todos los registros que tenemos, pasándolos de formato EPOCH a algo más sencillo de leer.

client/src/Register.js

import React from 'react';

export default class Register extends React.Component {
    state = { stackId: null };

    constructor(props) {
        super(props);
        this.submitRegistration = this.submitRegistration.bind(this);
    }

    submitRegistration() {

        const { drizzle, drizzleState } = this.props;
        const contract = drizzle.contracts.TimeControl;

        const stackId = contract.methods["Register"].cacheSend({ from: drizzleState.accounts[0] });

        this.setState({ stackId });
    }

    getTxStatus = () => {
        //get the transaction states from the drizzle state
        const { transactions, transactionStack } = this.props.drizzleState;

        //get the transaction hash using our saved stackId
        const txHash = transactionStack[this.state.stackId];

        if (!txHash)
            return null;

        //otherwise, return the transaction status
        return `Transaction status ${transactions[txHash] && transactions[txHash].status}`;

    }

    render() {
        return (
            <div className="container">
                {this.getTxStatus() &&
                    <div className="alert alert-info">{this.getTxStatus()}</div>
                }
                <button className="btn btn-primary" onClick={this.submitRegistration}>Register</button>
            </div>
        );
    }
}

Register es el componente encargado de fichar. En este caso verás que tenemos que tener en cuenta que estamos lidiando con una transacción, para lo que utilizamos la función cacheSend. En este caso el resultado es el hash de la transacción enviada, que nos servirá para conocer el estado de la misma, utilizando la función getTxStatus.

client/public/index.html

Por último he añadido una referencia a bootstrap para pintar un poco el resultado.

Pruébalo

Hoy te voy a contar otra forma para tener tu propia red de Ethereum de pruebas. Además de Ganache, Truffle en sí dispone del comando truffle develop que nos permite desplegar de forma rápida una red de pruebas en el terminal. En la raíz del proyecto de truffle, lanza los siguientes comandos para lanzar este entorno y desplegar en él tu contrato.

truffle develop
migrate

Ahora, en otro terminal, entra en el directorio client y ejecuta npm start para arrancar la aplicación. Intenta registrar algunos fichajes y verás que irán apareciendo, cuando las transacciones se completen.

El ejemplo completo lo tienes en mi GitHub.

¡Saludos!