Facilitando la búsqueda por precio dentro de nuestra app de reservas 🏨
Tags: reactnative, mobile, react
Published: 2022-03-23 19:45:00

_Para el usuario objetivo de Ayenda, el precio de una habitación juega un papel importante en la toma de decisiones dentro de su recorrido de compra, por tal motivo decidimos añadir un selector de rango de precios dentro de nuestro listado de hoteles, con el fin de mejorar la experiencia en la búsqueda y filtrado, y así proveer mejores resultados._

En este post vamos a compartir cómo fue el proceso de creación de esta característica que facilita al usuario filtrar por precios, permitiendo elegir entre un valor mínimo y máximo.


**DE LOS INSIGHTS AL DISEÑO 🎨**

Ayenda añade constantemente nuevos hoteles dentro de la cadena. A día de hoy contamos con más de 500 hoteles a disposición de los usuarios en 3 diferentes países y dentro de su propuesta de valor, está ofrecer la mejor experiencia a la hora de reservar a través de nuestra app. A medida que crece el número de hoteles, es importante mejorar la experiencia de búsqueda y compra de los usuarios para ofrecerles resultados relevantes.

Actualmente la app de reservas brinda opciones de ordenamiento y filtrado sobre el listado de hoteles, el selector de rango de precios haría parte del grupo de filtrado. Después de explorar varios diseños y validarlos con usuarios, se definió qué la mejor experiencia era usar un slider múltiple, es decir, con dos marcadores que permitan al usuario establecer un precio mínimo y máximo para filtrar los hoteles. Adicionalmente, se añadió un texto que permite visualizar el rango seleccionado por el usuario. El diseño final se ve como se muestra a continuación:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6y9vwysmxxki7wppb5hi.png)


**DEL DISEÑO A LA SOLUCIÓN 👩‍💻👨‍💻**

Para materializar estas ideas en un selector de rango de precios en React Native teníamos dos opciones; hacer un componente con múltiples sliders por nuestra cuenta o usar un componente de un tercero que se ajustara a estos requerimientos.

Después de revisar varios componentes, encontramos uno que cumple con la condición de soportar los dos marcadores que indican el valor mínimo y máximo del rango dentro de un slider, además de tener un buen performance tanto para Android como para iOS  y ese fue `@ptomasroos/react-native-multi-slider`.

Para su instalación ejecutamos`npm install --save @ptomasroos/react-native-multi-slider` o `yarn install @ptomasroos/react-native-multi-slider` dependiendo de tus preferencias.

Una vez instalada esta dependencia, creamos un nuevo archivo llamado `PriceSelector.js` que contendrá la lógica y los elementos visuales del componente.

```javascript
import MultiSlider from '@ptomasroos/react-native-multi-slider';

function PriceSelector({ max, min, onChange, selectedRangePrice }) {
  // Nuestro código irá aquí
}

export default PriceSelector;
```
Este componente necesitará las siguientes [props](https://es.reactjs.org/docs/components-and-props.html):

**max:** Valor máximo que el usuario podrá seleccionar.
**min:** Valor mínimo que el usuario podrá seleccionar.
**onChange:** Función callback que funciona como listener cuando el usuario ha terminado de mover el slider.
**selectedRangePrice:** Dado el contexto de nuestra app de reservas, el rango de precios puede estar preestablecido antes de renderizar el componente por primera vez o puede ser modificado de forma externa, por lo que se debe reflejar este cambio tomando como referencia los valores de esta prop.

Ahora veamos cuales props necesita el componente `react-native-multi-slider` qué acabamos de instalar.

```javascript
<MultiSlider
  values={[minValue, maxValue]}
  min={min}
  max={max}
  onValuesChange={}
  onValuesChangeFinish={}
/>
```

**values:** Valores fijados para establecer el rango.
**min:** Valor mínimo que puede ser seleccionado.
**max:** Valor máximo que puede ser seleccionado.
**onValuesChange:** Callback ejecutado mientras el valor cambia.
**onValuesChangeFinish:** Callback ejecutado cuando el valor deja de cambiar.

Iremos agregando lo necesario en `PriceSelector` para que pueda funcionar en conjunto con el componente react-native-multi-slider. Lo primero que añadiremos será un estado para controlar el cambio de valores dentro del componente.

```javascript
function PriceSelector({ max, min, onChange, selectedRangePrice }) {
  const [priceRange, setPriceRange] = useState({
    minValue: min,
    maxValue: max
  });

  // ...
}
```
La primera vez que `PriceSelector` sea renderizado tendrá definido el rango  establecido por las propiedades min y max. Es decir, si el min es igual a $20.000 COP y el max es igual a $50.000 COP, deberíamos visualizar el rango de la siguiente manera:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q8cj1izq7opdqrble0tv.png)

A continuación añadiremos la función que nos permitirá cambiar el estado que acabamos de agregar:

```javascript
function handleChangePrice(values) {
  setPriceRange({
    minValue: values[0],
    maxValue: values[1]
  });
}
```

Esta será ejecutada cada vez que los valores en el estado sean modificados.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tbindlvog3wgwxpdhn92.gif)

Ahora agregaremos la función que se ejecutará cuando el usuario deje de mover el marcador sobre el slider y ejecutará el `onChange` que recibimos desde las props.

```javascript
function handleValuesChangeFinish() {
  onChange(priceRange);
}
```

Esto lo hacemos para que el componente padre pueda saber cuando el usuario ha cambiado el rango de precios en nuestro selector.

Al unir todo, el componente debería verse así:

```javascript
function PriceFilter({ max, min, onChange, selectedRangePrice }) {
  const [priceRange, setPriceRange] = useState({
    minValue: min,
    maxValue: max
  });

  function handleChangePrice(values) {
    setPriceRange({
      minValue: values[0],
      maxValue: values[1]
    });
  }

  function handleValuesChangeFinish() {
    onChange(priceRange);
  }

  return <MultiSlider
           values={[minValue, maxValue]}
           min={min}
           max={max}
           onValuesChange={handleChangePrice}
           onValuesChangeFinish={handleValuesChangeFinish}
         />;
}
```

Por último agregaremos un [useEffect](https://es.reactjs.org/docs/hooks-effect.html) que se encargará de detectar y actualizar el componente `PriceSelector` cuando las props `selectedRangePrice`, `min` o `max` hayan cambiado.

```javascript
useEffect(() => {
  setPriceRange({
    minValue: selectedRangePrice?.minValue || min,
    maxValue: selectedRangePrice?.maxValue || max
  });

  if (!selectedRangePrice) {
    onChange({ minValue: min, maxValue: max });
  }
}, [selectedRangePrice, onChange, min, max]);
```

¿Por qué queremos detectar si estas props han cambiado? En el caso de las props `min` y `max`, dentro del contexto de nuestra app de reservas, estos valores pueden cambiar cuando el usuario cambia su rango de búsqueda  en cuanto a ubicación, como por ejemplo cuando busca hoteles en otra ciudad.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s8vc5jzk2wjjmk1kdzpm.gif)

La prop `selectedRangePrice` puede cambiar por ejemplo cuando el usuario limpia los filtros o  cambia la ubicación en la qué está buscando, y en ese caso es necesario reflejar visualmente los cambios pertinentes, así como para el componente padre.

Añadiremos una validación para cuando las props `min` y `max` aún no están definidas, de manera que el componente `react-native-multi-slider` trabaje correctamente, ya que necesita establecer estos valores por defecto. Además agregaremos el rango seleccionado y un texto para que nuestro componente quede más amigable.

```javascript
return (
  <>
    <Subtitle size={1}>
      <Subtitle size={1} style={styles.greenText}>
        {' '}
        Rango{' '}
      </Subtitle>{' '}
      de precios:
    </Subtitle>
    <Subtitle size={2} style={styles.prices}>
      <Currency value={priceRange.minValue} /> -{' '}
      <Currency value={priceRange.maxValue} />
    </Subtitle>
    {min && max && (
      <MultiSlider
        values={[minValue, maxValue]}
        min={min}
        max={max}
        onValuesChange={}
        onValuesChangeFinish={}
      />
    )}
  </>
```

**MEJORANDO NUESTRO COMPONENTE 🤩**

La versión actual de la app ha liberado este nuevo filtro que acabamos de construir en estas líneas. Ahora debemos revisar nuestras métricas y observar que se logre el impacto esperado. Probablemente pienses que tengamos varias oportunidades de mejora de cara a la experiencia de usuario con este filtro, déjanos tus impresiones en los comentarios.