yew-hooks 0.2.0

Hooks for the Yew web framework, inspired by react hook libs like streamich/react-use and alibaba/hooks.
Documentation
use gloo::utils::window;
use wasm_bindgen::{prelude::*, JsCast};
use web_sys::{Position, PositionError};
use yew::prelude::*;

use super::use_effect_once;

pub use web_sys::PositionOptions as UseGeolocationOptions;

#[derive(PartialEq, Default, Clone)]
pub struct UseGeolocationState {
    pub loading: bool,
    pub accuracy: f64,
    pub altitude: Option<f64>,
    pub altitude_accuracy: Option<f64>,
    pub heading: Option<f64>,
    pub latitude: f64,
    pub longitude: f64,
    pub speed: Option<f64>,
    pub timestamp: f64,
    pub error: Option<PositionError>,
}

/// A sensor hook that tracks user's geographic location.
///
/// # Example
///
/// ```rust
/// # use yew::prelude::*;
/// #
/// use yew_hooks::prelude::*;
///
/// #[function_component(UseGeolocation)]
/// fn geolocation() -> Html {
///     let state = use_geolocation();
///     
///     html! {
///         <>
///             <b>{ " loading: " }</b>
///             { state.loading }
///             <b>{ " latitude: " }</b>
///             { state.latitude }
///             <b>{ " longitude: " }</b>
///             { state.longitude }
///             <b>{ " altitude: " }</b>
///             { state.altitude.unwrap_or_default() }
///             <b>{ " altitude_accuracy: " }</b>
///             { state.altitude_accuracy.unwrap_or_default() }
///             <b>{ " heading: " }</b>
///             { state.heading.unwrap_or_default() }
///             <b>{ " speed: " }</b>
///             { state.speed.unwrap_or_default() }
///             <b>{ " timestamp: " }</b>
///             { state.timestamp }
///         </>
///     }
/// }
/// ```
#[hook]
pub fn use_geolocation() -> UseGeolocationState {
    use_geolocation_with_options(UseGeolocationOptions::default())
}

/// A sensor hook that tracks user's geographic location.
/// See [`use_geolocation`]
#[hook]
pub fn use_geolocation_with_options(options: UseGeolocationOptions) -> UseGeolocationState {
    let state = use_state(|| UseGeolocationState {
        loading: true,
        ..Default::default()
    });

    {
        let state = state.clone();
        use_effect_once(move || {
            let closure = {
                let state = state.clone();
                Closure::wrap(Box::new(move |position: Position| {
                    state.set(UseGeolocationState {
                        loading: false,
                        accuracy: position.coords().accuracy(),
                        altitude: position.coords().altitude(),
                        altitude_accuracy: position.coords().altitude_accuracy(),
                        heading: position.coords().heading(),
                        latitude: position.coords().latitude(),
                        longitude: position.coords().longitude(),
                        speed: position.coords().speed(),
                        timestamp: position.timestamp(),
                        error: None,
                    });
                }) as Box<dyn Fn(Position)>)
            };

            let error_closure = {
                let state = state.clone();
                Closure::wrap(Box::new(move |error: PositionError| {
                    state.set(UseGeolocationState {
                        loading: false,
                        error: Some(error),
                        ..*state
                    });
                }) as Box<dyn Fn(PositionError)>)
            };

            window()
                .navigator()
                .geolocation()
                .unwrap_throw()
                .get_current_position_with_error_callback_and_options(
                    closure.as_ref().unchecked_ref(),
                    Some(error_closure.as_ref().unchecked_ref()),
                    &options,
                )
                .unwrap_throw();

            let watch_id = window()
                .navigator()
                .geolocation()
                .unwrap_throw()
                .watch_position_with_error_callback_and_options(
                    closure.as_ref().unchecked_ref(),
                    Some(error_closure.as_ref().unchecked_ref()),
                    &options,
                )
                .unwrap_throw();

            // Forget the closure to keep it alive
            closure.forget();
            error_closure.forget();

            move || {
                window()
                    .navigator()
                    .geolocation()
                    .unwrap_throw()
                    .clear_watch(watch_id);
            }
        });
    }

    (*state).clone()
}