Skip to content

2020_08 Formulario interactivo con react en una aplicación que usa sip

Vladimir Támara Patiño edited this page Sep 24, 2020 · 3 revisions

El ejercicio por realizar es agregar un registro a la tabla Sip::Ubicación mediante un formulario. Pero al editar cuando se elija un centro poblado debe mostrar automáticamente el tipo de centro poblado.

Para hacerlo se realizan varias etapas de interconexión entre Rails y React.

1. Actualice fuentes

Actualice a ruby, rails y node más recients, así como a las gemas más recientes y a los paquetes yarn más recientes y verifique:

bundle update
bundle install
CXX=c++ yarn upgrade
CXX=c++ yarn install
bin/rails webpacker:info

Incluso podría requerir aumentar manualmente la versión de webpacker y de webpacker-dev-server en package.json. En Agosto de 2020 las versiones más recientes disponibles en adJ 6.7a1 son:

$ bin/rails webpacker:info
Ruby: ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-openbsd]
Rails: 6.0.3.2                          
Webpacker: 5.2.1                        
Node: v12.16.1                          
Yarn: 1.22.4

@rails/webpacker:                       
/home/.../miap/
└── @rails/[email protected] 

Si durante la instalación con yarn ocurren errores procure resolverlos antes de continuar, porque aunque el proceso continúe y parezca exitoso, podrían reaparecer posteriormente con errores crípticos como Command webpack not found

Actualice archivos de configuración de webpacker (se recomienda analizar cada diferencia --con d-- y aplicar cambios a archivos que usted no ha modificado --con y-- pero no en el caso de archivos que usted ha modificar --con n-- para dejar el suyo o con m para mezclar).

CXX=c++ bin/rails webpacker:install

Esta orden entre otras ejecutará bin/rails webpacker:binstubs y actualizará archivos importantes como babel.config.js

Utilice webpacker para instalar parte de lo necesario para desarrollar en react (entre otras .jsx en config/webpacker.yml y paquetes yarn como react, react-domm, @babel/preset-react, prop-types, babel-plugin-transform-react-remove-prop-types):

bin/rails webpacker:install:react

2. Hola mundo con react puro

Se trata del presentado en [LEARNETTO2020], sólo que traduciendo lo que es posible a español como es estándar en Pasos de Jesús y empleando class en lugar de funciones para definir componentes.

Renombre el archivo app/javascript/packs/hello_react.jsx por app/javascript/packs/hola_react.jsx y remplace Hello por Hola y name por nombre para obtener:

import React from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
class Hola extends React.Component {
  render () {
    return <div>Holaa {this.props.nombre}!</div>
  }

  defaultProps = {
    nombre: 'David'
  }
}

Hola.propTypes = {
  nombre: PropTypes.string
}

export default Hola

document.addEventListener('DOMContentLoaded', () => {
  ReactDOM.hydrate(
    <Hola nombre="React" />,
    document.querySelector('div.caso_ubicacion_tclase'),
  )
})

Note que el componente Hola tiene un atributo nombre (llamado prop en react).

Cree:

  • Un nuevo controlador app/controllers/holareact_controller.rb con:
class HolareactController < ApplicationController
  def index
    render 'index', layout: nil
  end
end
  • Una nueva vista que cargue el javascript generado a partir de jsx, por ejemplo app/views/holareact/index.html.erb con
<%= javascript_pack_tag 'hola_react' %>
  • Y finalmente agregue una ruta al nuevo controlador por ejemplo la ruta raíz en config/routes.rb
root 'holareact#index'

También podrá incluir el <%= javascript_pack_tag 'hola_react' %> en una vista existente pero puede que tenga que cargarla y recargarla para ver el mensaje insertado al final de la página. Se debe a incompatibilidad entre React y Turbolinks. Por lo mismo una solución es deshabilitar Turbolinks cuando va a cargar la página donde usa React, por ejemplo desde una opción del menú puede hacerlo con:

<%= opcion_menu "Validar casos", sivel2_gen.validarcasos_path,
  'data-turbolinks' => false,
  desplegable: true %>

o desde un enlace con:

<%= link_to 'Edita', edit_caso_path(caso_id), 
 data: { turbolinks: false },
 :class => 'btn btn-sm' %>

Si desea incluir el componente de react en un elemento de una vista servidor por rails (por ejemplo en un formulario), es más apropiado usar ReactDOM.hydrate en lugar de ReactDOM.render (ver [hydrate-2020]), como se presenta en el siguiente ejemplo:

ReactDOM.hydrate( 
  <Hola nombre="React" />,
  document.querySelector('div.caso_ubicacion_tclase'),
)

Para depurar aplicaciones que usen react recomendamos la extensión React Developers Tools disponibles en la tienda de Chrome. Al inspeccionar unas fuentes presentará dos nuevos menús Components y Profiler que permiten examinar los componentes presentes en una página y sus props y estado.

3. Hola mundo con react-rails y webpacker.

react-rails incluye un ayudador para agregar un componente react desde una vista y propone un directorio para ubicar los componentes: app/javascript/components.

Después de haber incluido react como se explicó en la sección anterior, incluya la gema react-rails en su `Gemfile:

gem 'react-rails'

Instale y ejecute un instalador

bundle install
rails generate react:install

Esto agregará el paquete yarn react_ujs y en su su archivo app/javascript/packs/application.js agregará las líneas siguientes:

var componentRequireContext = require.context("components", true);
var ReactRailsUJS = require("react_ujs");
ReactRailsUJS.useContext(componentRequireContext);

que indican que la ruta a componentes será components y que cargan el paquete react_ujs.

Cómo indica [REACTRAILS2020] puede generar un componente con:

bin/rails g react:component HolaMundo saludo:string

que creará el archivo app/javascript/components/HolaMundo.js (siempre usa la ruta app/javascript/components/) con el siguiente contenido:

import React from "react"
import PropTypes from "prop-types"
class HolaMundo extends React.Component {
  render () {
    return (
      <React.Fragment>
        Saludo: {this.props.saludo}
      </React.Fragment>
    );
  }
}

HolaMundo.propTypes = {
  saludo: PropTypes.string
};
export default HolaMundo

Y que puede incrustarse en vistas usando el ayudador:

<%= react_component('HolaMundo', {saludo: 'Hola desde react-rails'}) %>

teniendo en cuenta la incompatibilidad con Turbolinks y las posibilidades para mitigar el conflicto dadas en la sección anterior.

4. Un formulario sin conexión a la base de datos pero con estado

Un ejemplo basado en [CRUZ2020] de un componente (digamos Tclase) con dos campos de un formulario cuyo valor se mantiene en el estado del componente, puede definirse en app/javascript/component/Tclase.jsx:

import React, {useState, Fragment} from 'react'
import ReactDOM from 'react-dom'

class Tclase extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      clase: '',
      tclase: ''
    }
    this.manejaCambio = this.manejaCambio.bind(this);
  }

  manejaCambio(event) {
    var ce = {}
    ce[event.target.name] = event.target.value
    this.setState(ce)
  }

  render() {
    return (
      <Fragment>
      <label htmlFor="clase">Centro poblado</label>
      <select
      className="form-control"
      id="clase"
      name="clase"
      onChange={this.manejaCambio} >
      <option>Mosquera</option>
      <option>Madrid</option>
      <option>Facatativa</option>
      </select>
      <label htmlFor="tclase">Tipo de centro poblado</label>
      <select className="form-control"
      id="tclase"
      name="tclase"
      onChange={this.manejaCambio} >
      <option>Cabecera Municipal</option>
      <option>Centro poblado</option>
      </select>
      <p>{JSON.stringify(this.state)}</p>
      </Fragment>
    )
  }

}

export default Tclase

Note que:

  1. Como se explica en [REACTESTADO-2020] el estado se mantiene en this.state, y para que la función manejaCambio tenga acceso se requiere en la constructora la asignación:
this.manejaCambio = this.manejaCambio.bind(this);
  1. El estado se actualiza mediante la función setState.

Y en el formulario donde se va a usar se agrega:

<%= react_component('Tclase') %>

5. Referencias

Clone this wiki locally