Conversor de medidas en ReactJS
Switch branches/tags
Nothing to show
Clone or download
Latest commit 0dbbd22 Nov 2, 2018
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.vscode Initial commit Sep 14, 2017
images Enhanced explanation Jun 12, 2018
public Initial commit Sep 14, 2017
src Added link in README Nov 2, 2018
video Adding video to README Sep 14, 2017
.gitignore Initial commit Sep 14, 2017
.travis.yml Adding travis file Jun 12, 2018
README.md Added link in README Nov 2, 2018
package-lock.json Fixing vulnerabilities + bind explanation Nov 1, 2018
package.json Fixing vulnerabilities + bind explanation Nov 1, 2018

README.md

Conversor ReactJS

video

Este proyecto fue generado con el script Create React App.

Arquitectura general

image

El dominio es un objeto que recibe un número que representa las millas y devuelve su valor convertido a kilómetros. No tiene variables de instancia. Se puede ver en el archivo conversor.js del directorio src:

export default class Conversor {
    convertir(millas) {
        return millas * 1.60934
    }
}

La vista tiene

  • como estado una sola clave: "kilometros" que apunta al valor convertido.
  • un input type text cuyo evento onChange dispara la conversión
  • al convertir se actualiza el state del componente generando un nuevo conversor y llamando al convertir. El valor resultante va a parar a la única variable kilometros.

Esto puede verse en el archivo App.js del directorio src:

class App extends Component {
  constructor() {
    super()
    this.state = { kilometros: "<Ingrese millas>" }
    this.convertir = this.convertir.bind(this)
  }
  
  convertir(event) {
    this.setState({
      kilometros: new Conversor().convertir(event.target.value)
    })
  }

  render() {
    return (
      <div className="App">
        <div className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1>Conversor <small>React JS</small></h1>
        </div>
        <p>Ingrese millas:</p>
        <input type="text" name="millas" id="millas" onChange={this.convertir} />
        <p>Ingrese kilómetros:</p>
        <p id="kms">{this.state.kilometros.toLocaleString('es')}</p>
      </div>
    );
  }
}

Entendiendo el binding de eventos

En este articulo se explica que cuando definimos una función en Javascript, la variable this se refiere al contexto de ejecución de dicha función:

// esto se puede ejecutar en cualquier browser
let frog = {
  RUN_SOUND: "POP!!",
  run: function() { 
    console.log('this es ', this)
    return this.RUN_SOUND
  }
}

Si frog es un objeto, y vemos run() como un método de dicho objeto, lo natural es que pensemos en enviar el mensaje de la siguiente manera:

> frog.run() 
this es  {RUN_SOUND: "POP!!", run: ƒ}
"POP!!"

Pero ECMAScript es también un lenguaje funcional, entonces puedo definir una variable y construir una función a partir del método definido en frog:

> const f = frog.run

Ojo que al no pasarle paréntesis, no estamos invocando a la función, sino referenciando con la variable f a la función frog.run, que no recibe parámetros y devuelve un string.

Cuando invocamos a f, nuestra sorpresa:

> f()
this es  Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}
undefined

La variable this no está ligada a frog, sino a window (nuestro browser). Al extraer f como variable separada del objeto frog, perdimos el contexto de ejecución de this. Para poder recuperarlo, necesitamos la función bind:

> const fParaFrog = f.bind(frog)
> fParaFrog()
this es  {RUN_SOUND: "POP!!", run: ƒ}
"POP!!"
// o bien...
> f.bind(frog)() // ...que produce el mismo resultado

Por ese motivo, queremos que al invocar a convertir las millas en kilómetros, this referencie a nuestro componente React y no a window. Entonces aplicamos el bind en el constructor:

class App extends Component {
  constructor() {
    ...
    this.convertir = this.convertir.bind(this)
  }

Por qué lo hacemos? Porque en la función render asociamos el evento onChange a la referencia convertir de nuestra App, que de otra forma sería una función sin contexto asociado:

        <input type="text" name="millas" id="millas" onChange={this.convertir} />

Otros artículos que recomendamos leer:

Ciclo de vida

image

Testing

Para testear el componente probamos

  • que la aplicación levanta correctamente
  • que inicialmente el valor en kilómetros dice "<Ingrese millas>"
  • que al escribir el valor "10" en millas eso convierte a "16.093"

Dado que estaremos usando los mocks de Enzyme, no se convierte el punto decimal a coma.

Vemos los tests en el archivo App.test.js del directorio src:

it('App levanta', () => {
  shallow(<App />)
})
it('convertir 10 millas a kilómetros', () => {
  const wrapper = shallow(<App/>)
  const kms = wrapper.find('#kms')
  expect(kms.text()).toBe("<Ingrese millas>")
})
it('convertir 10 millas a kilómetros', () => {
  const wrapper = shallow(<App/>)
  const millas = wrapper.find('#millas')
  millas.simulate('change', { 'target': { value: '10'}})
  const kms = wrapper.find('#kms')
  expect(kms.text()).toBe("16.093")
})