ezakto code

Los contenidos de este blog están desactualizados.

Pero estoy pensando en actualizarlos y escribir más.

Si te interesa, dejame tu email (no spam, no newsletters). Si hay suficiente interés me pondré manos a la obra y te lo haré saber!


Tutorial React: Cargando y guardando datos

27 de junio 2015

Visualizando y modificando la información mostrada por la aplicación

La aplicación va tomando forma pero aún no funciona de manera útil. La siguiente tarea es darle vida. Es decir, la posibilidad de agregar y eliminar notas, y mostrarlas.

LocalStorage

De momento, ya que no estamos trabajando con un servidor web (aún!), vamos a almacenar todas las notas de la aplicación en el navegador, usando localStorage.

La interfaz de localStorage es bastante simple. window.localStorage.setItem(key, value) almacenará una cadena de texto, mientras que window.localStorage.removeItem(key, value) la eliminará. Con window.localStorage.getItem(key) la recuperamos del disco.

Para guardar una nueva nota, haremos uso de setItem() en el componente Form. Luego, nuestro componente Grid usará getItem() para cargar la lista de notas guardadas y así dibujarlas.

Form.js

Necesitamos hacer que el formulario reaccione al presional el botón "Hecho". Para ello, debemos crear un método nuevo que recolecte la información ingresada en el formulario y la guarde en localStorage.

Para acceder a los valores ingresados, es necesario hacer referencia a los tags <input> y <textarea> para así leer su propiedad value. Esto lo haremos usando las refs de react. Las refs son simples atributos que podemos agregar a los elementos devueltos en el método render() para asignarles "un nombre" con el cual podamos encontrarlos luego de montados, a través de los diferentes métodos del componente.

Entonces, modificamos ligeramente el método render() y agregamos un atributo ref a ambas etiquetas, con algún nombre descriptivo: js <input className="addnote-title" type="text" placeholder="Título" ref="title" /> <textarea className="addnote-text" placeholder="Añadir nota" ref="text" />

Ahora que tenemos las referencias en su lugar, podremos acceder a estos elementos usando this.refs.title y this.refs.text. Ya con este acceso fácil, podemos leer sus contenidos y guardarlos.

La listad de notas en nuestro localStorage será un array de objetos. Cada objeto representará una nota, teniendo dos propiedades: title (título) y text (texto). Toda la lista será codificada en una cadena de texto con JSON.stringify() antes de ser guardada, ya que localStorage sólo acepta cadenas.

Agregamos un método a nuestro componente, save(), justo después del método close. Para obtener los valores, usaremos nuevamente React.findDOMNode() para obtener los elementos DOM, pero esta vez no usaremos this como argumento, sino las refs que creamos anteriormente. Luego copiaremos la lista de notas guardadas, insertaremos la nueva y guardaremos los cambios.

    save: function() {
        // Obtenemos los valores del formulario
        var note = {
            id: new Date().getTime(), // Generamos una id rápida
            title: React.findDOMNode(this.refs.title).value,
            text: React.findDOMNode(this.refs.text).value
        };

        // Leemos la lista de notas guardadas o creamos una vacía
        var notes = window.localStorage.getItem('notes');

        if (notes === null) {
            notes = []; // Creamos una nueva lista vacía
        } else {
            notes = JSON.parse(notes); // Decodificamos la cadena
        }

        // Insertamos la nueva nota al principio de la lista
        notes.unshift(note);

        // Codificamos la lista como cadena de texto
        notes = JSON.stringify(notes);

        // Guardamos en localStorage
        window.localStorage.setItem('notes', notes);

        // Vaciamos el formulario
        React.findDOMNode(this.refs.title).value = '';
        React.findDOMNode(this.refs.text).value = '';

        // Y finalmente lo cerramos
        this.close();
    },

Ahora debemos ejecutar este nuevo método cada vez que se quiera enviar la información. Tal como hicimos con el evento focus en el <form>, agregaremos un atributo nuevo para escuchar al evento submit, pasándole una referencia a save():

            <form className={"addnote" + (this.state.open ? ' open' : '')} onFocus={this.open} onSubmit={this.save}>

Nuestro form ya funciona. Ahora debemos mostrar las notas guardadas.

Nota: lo más recomendable en campos de formulario es enlazar el atributo value al state del componente y mutarlo para reflejar cambios programáticos (como el vaciado del campo). Aquí un método más sencillo que además ayuda a ilustrar el uso de React.findDOMNode().

Grid.js

El componente Grid cargará las notas y las dibujará automática y eficientemente gracias al algoritmo de diferencias de react (diff o diffing algorithm para los amigos). Este procedimiento consiste en ejecutar el método render() de los componentes, comparar su resultado con el resultado anterior, extraer sólo las diferencias mínimas y aplicarlas en el DOM. Esto permite redibujar la menor cantidad posible cada vez que hay una actualización de datos, haciendo la aplicación más eficiente, ya que el proceso de redibujado del navegador es costoso. Este proceso es ejecutado por react automáticamente cada vez que determinados eventos ocurren, como por ejemplo cuando el state del componente es actualizado, o cuando el componente recibe nuevas props.

Diff algorithm

Las props de los componentes son la contraparte react de los atributos HTML. Un elemento react puede pasarle información a sus elementos hijos a través de ellas. Un componente puede acceder a las props que le son pasadas a través del objeto contenido en this.props.

Aplicando esto, buscaremos que el componente Grid cargue todas las notas y mediante un ciclo recorra esta lista para crear un Note pasandole su correspondiente texto via props.

Lo primero que haremos, es cargar la lista de notas que estará almacenada en localStorage o crearemos una lista vacía en caso de encontrar nada (por ahora duplicaremos un poco del código utilizado anteriormente). Usaremos el state para cargar la lista en el componente. Agregamos el método getInitialState() para realizar esta operación.

    getInitialState: function() {
        var notes = window.localStorage.getItem('notes');

        if (notes === null) {
            notes = [];
        } else {
            notes = JSON.parse(notes);
        }

        // Recordemos que es necesario devolver un objeto plano,
        // por lo que asignamos nuestro array de notas como propiedad
        return {
            notes: notes
        };
    },

Ahora estamos seguros de que Grid cargará la lista de notas guardadas cuando sea montado. Para mostrarlas, compondremos el resultado del método render() de forma estructurada. En el método render(), transformaremos la lista de notas almacenada en el state en un array de componentes, utilizando map():

        var notes = this.state.notes.map(function(note, idx){
            return (
                <Note />
            );
        });

Pero esto nos devolverá una lista de componentes iguales y vacíos. Tenemos que pasarles la información de cada nota vía props. Agregamos los atributos de esta manera:

                <Note |||id={note.id} title={note.title} text={note.text}||| />

Cabe destacar que cuando generamos un array de componentes, es necesario agregarles un atributo extra de valor único, key. Las keys sirven para que react, internamente, sepa qué componentes debe eliminar, modificar o insertar cada vez que se actualiza. Profundizaremos más adelante. Por ahora, asignaremos como key de cada elemento a su índice en el array:

                <Note id={note.id} title={note.title} text={note.text} |||key={idx}||| />

Ahora que tenemos nuestro array de Notes, elminamos esas tres etiquetas del resultado de render() y las reemplazamos por el array mismo (lo insertamos como expresión, entre llaves).

Así quedaría el componente con los cambios aplicados:

var Grid = React.createClass({

    getInitialState: function() {
        var notes = window.localStorage.getItem('notes');

        if (notes === null) {
            notes = [];
        } else {
            notes = JSON.parse(notes);
        }

        // Recordemos que es necesario devolver un objeto plano,
        // por lo que asignamos nuestro array de notas como propiedad
        return {
            notes: notes
        };
    },

    render: function() {
        var notes = this.state.notes.map(function(note, idx){
            return (
                <Note id={note.id} title={note.title} text={note.text} key={idx} />
            );
        });

        return (
            <div className="grid">
                {notes}
            </div>
        );
    }

});
Note.js

Ahora que Grid carga la lista de notas y dibuja un componente Note para cada una, es necesario retocar dicho componente para que consuma los contenidos que le son pasados via props. Reemplazamos el texto Lorem Ipsum por las variables this.props.title y this.props.text. Esta variables contendrán el título y el texto que se le pase al componente a la hora de montarlo. Recordemos que this.props.* es una expresión, por lo tanto debemos mostrarla como tal (usando llaves).

El método render() deberá verse algo así:

    render: function() {
        return (
            <div className="note">
                <div className="note-text">
                    <strong>|||{this.props.title}|||</strong>
                    <p>|||{this.props.text}|||</p>
                </div>
                <div className="note-toolbar">
                    <a className="note-btn-delete" />
                </div>
            </div>
        );
    }

Suficiente por ahora. Con los tres componentes actualizados, ya podemos compilar con browserify y agregar algunas notas!

"Probando aplicación"