Skip to content
master
Switch branches/tags
Code

Latest commit

 

Git stats

Files

Permalink
Failed to load latest commit information.
Type
Name
Latest commit message
Commit time
 
 
 
 
src
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Build React App coverage

Conversor ReactJS

video

Este proyecto fue generado con el script Create React App.

Arquitectura general

Dominio

image

El dominio podría ser

  • un objeto
  • o bien podemos modelarlo simplemente con una función, que recibe las millas y lo convierte a kilómetros. Se puede ver en el archivo conversor.js del directorio src:
const FACTOR_CONVERSION = 1.60934

export const convertirMillasAKms = (millas) => millas * FACTOR_CONVERSION

Vista

La vista tiene

  • como estados una sola clave: "millas" con el valor en millas
  • temporalmente tomamos las millas del estado y hacemos la conversión a kilómetros, y definimos la clase "success" / "warning" en caso de que la conversión sea exitosa o no para ver el badge verde o amarillo, respectivamente.
  • un input type text cuyo evento onChange dispara la conversión
  • al convertir se actualiza el state del componente generando un nuevo valor para la variable millas.

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

class App extends Component {
  constructor() {
    super()
    this.state = {
      millas: INITIAL_VALUE,
    }
  }

  actualizarMillas(newMillas) {
    this.setState({
      millas: newMillas,
    })
  }

  render() {
    const newMillas = this.state.millas
    const kilometros = newMillas === INITIAL_VALUE ? '<Ingrese millas>' : (isNaN(newMillas) ? '<Ingrese un valor numérico>' : convertirMillasAKms(newMillas))
    const colorConversion = newMillas === INITIAL_VALUE || isNaN(newMillas) ? 'warning' : 'success'

    return (
      <div className="App">
        <Box>
          <Heading>
            Conversor de millas a kilómetros - React
        </Heading>
          <Field>
            <Label>Millas</Label>
            <Control>
              <Input value={this.state.millas} name="millas" autoComplete="off" data-testid="millas" onChange={(event) => this.actualizarMillas(event.target.value)} />
            </Control>
          </Field>
          <Field>
            <Label>Kilómetros</Label>
            <Tag color={colorConversion} rounded>
              <Label data-testid="kms">{kilometros.toLocaleString('es')}</Label>
            </Tag>
          </Field>
        </Box>
      </div>
    )
  }
}

Reaccionando ante un cambio en las millas

Nos detenemos en la definición del evento onChange para el input de millas:

onChange={(event) => this.actualizarMillas(event.target.value)}

Definir una expresión lambda (arrow function) permite que la referencia this esté apuntando al componente React que estamos escribiendo. Podríamos pensar que una definición similar podría ser:

onChange={this.actualizarMillas}

Y modificar el método actualizarMillas para adaptar el valor recibido:

  actualizarMillas(event) {
    const newMillas = event.target.value
    this.setState({
      ...

Pero ojo que podemos llevarnos algunas sorpresas...

error, setState undefined

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
const 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 actualizarMillas, this referencie a nuestro componente React y no a window. Entonces aplicamos el bind en el constructor:

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

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

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

Otros artículos que recomendamos leer:

A partir de aquí, dejamos que establezcas tu propio criterio para elegir una opción u otra.

Ciclo de vida

Para entender el ciclo de vida

  1. render inicial
  2. el usuario escribe un 1
  3. onChange dispara un nuevo setState con el objeto { "millas": "1" }
  4. se ejecuta un nuevo render, con la conversión a kilómetros
  5. el usuario escribe un 2,
  6. onChange dispara un nuevo setState con el objeto { "millas": "12" }
  7. se ejecuta un nuevo render, con la conversión a kilómetros...

pueden escribir un console.log en cada evento.

Testing

Para testear el componente probamos

  • que inicialmente el valor en kilómetros dice "<Ingrese millas>"
  • que si escribimos un valor alfabético el valor en kilómetros mostrará el error "<Ingrese un valor numérico>"
  • que al escribir el valor "10" en millas eso convierte a "16.093"

Resolvemos los tests unitarios utilizando el framework React Testing Library (que reemplaza a Enzyme para las versiones recientes de create-react-app)

test('convierte un valor > 0 de millas a kilómetros correctamente', async () => {
  const { getByTestId } = render(<App />)
  // El usuario carga 10 en millas
  const inputMillas = getByTestId('millas')
  fireEvent.change(inputMillas, { target: { value: '10' } })
  // https://stackoverflow.com/questions/52618569/set-the-locale-for-date-prototype-tolocalestring-for-jest-tests
  expect(getByTestId('kms')).toHaveTextContent('16,093')
})

test('inicialmente pide que convirtamos de millas a kilómetros', async () => {
  const { getByTestId } = render(<App />)
  expect(getByTestId('kms')).toHaveTextContent('<Ingrese millas>')
})

test('si ingresa un valor alfabético la conversión de millas a kilómetros no se realiza', async () => {
  const { getByTestId } = render(<App />)
  const inputMillas = getByTestId('millas')
  // El usuario carga 'dos' en millas, otra variante más declarativa
  userEvent.type(inputMillas, 'dos')
  expect(getByTestId('kms')).toHaveTextContent('<Ingrese un valor numérico>')
})

Customizaciones

Font

El font-family lo configuramos en el archivo src/App.css

body {
  font-family: 'Noto Sans JP', sans-serif;
}

Y dentro de la carpeta public del raíz de este proyecto, vas a encontrar el index.html donde tenés que referenciar al typeface:

  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap" rel="stylesheet">

Favicon

En la carpeta public también se ubica el favicon.ico que podés generar a partir de un png con varios programas online, como https://convertico.com/

react-bulma-components

Para definir los estilos de la página utilizamos los componentes definidos por React Bulma Components:

Nota importante: estamos usando la versión 3.4.0 ya que de la 4 en adelante no está generado correctamente el build y no funciona.

About

Conversor de medidas en ReactJS

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published