yew_hooks/hooks/
use_geolocation.rs

1use gloo::utils::window;
2use wasm_bindgen::{prelude::*, JsCast};
3use web_sys::{Position, PositionError};
4use yew::prelude::*;
5
6use super::use_effect_once;
7
8pub use web_sys::PositionOptions as UseGeolocationOptions;
9
10#[derive(PartialEq, Default, Clone)]
11pub struct UseGeolocationState {
12    pub loading: bool,
13    pub accuracy: f64,
14    pub altitude: Option<f64>,
15    pub altitude_accuracy: Option<f64>,
16    pub heading: Option<f64>,
17    pub latitude: f64,
18    pub longitude: f64,
19    pub speed: Option<f64>,
20    pub timestamp: f64,
21    pub error: Option<PositionError>,
22}
23
24/// A sensor hook that tracks user's geographic location.
25///
26/// # Example
27///
28/// ```rust
29/// # use yew::prelude::*;
30/// #
31/// use yew_hooks::prelude::*;
32///
33/// #[function_component(UseGeolocation)]
34/// fn geolocation() -> Html {
35///     let state = use_geolocation();
36///     
37///     html! {
38///         <>
39///             <b>{ " loading: " }</b>
40///             { state.loading }
41///             <b>{ " latitude: " }</b>
42///             { state.latitude }
43///             <b>{ " longitude: " }</b>
44///             { state.longitude }
45///             <b>{ " altitude: " }</b>
46///             { state.altitude.unwrap_or_default() }
47///             <b>{ " altitude_accuracy: " }</b>
48///             { state.altitude_accuracy.unwrap_or_default() }
49///             <b>{ " heading: " }</b>
50///             { state.heading.unwrap_or_default() }
51///             <b>{ " speed: " }</b>
52///             { state.speed.unwrap_or_default() }
53///             <b>{ " timestamp: " }</b>
54///             { state.timestamp }
55///         </>
56///     }
57/// }
58/// ```
59#[hook]
60pub fn use_geolocation() -> UseGeolocationState {
61    use_geolocation_with_options(UseGeolocationOptions::default())
62}
63
64/// A sensor hook that tracks user's geographic location.
65/// See [`use_geolocation`]
66#[hook]
67pub fn use_geolocation_with_options(options: UseGeolocationOptions) -> UseGeolocationState {
68    let state = use_state(|| UseGeolocationState {
69        loading: true,
70        ..Default::default()
71    });
72
73    {
74        let state = state.clone();
75        use_effect_once(move || {
76            let closure = {
77                let state = state.clone();
78                Closure::wrap(Box::new(move |position: Position| {
79                    state.set(UseGeolocationState {
80                        loading: false,
81                        accuracy: position.coords().accuracy(),
82                        altitude: position.coords().altitude(),
83                        altitude_accuracy: position.coords().altitude_accuracy(),
84                        heading: position.coords().heading(),
85                        latitude: position.coords().latitude(),
86                        longitude: position.coords().longitude(),
87                        speed: position.coords().speed(),
88                        timestamp: position.timestamp(),
89                        error: None,
90                    });
91                }) as Box<dyn Fn(Position)>)
92            };
93
94            let error_closure = {
95                let state = state.clone();
96                Closure::wrap(Box::new(move |error: PositionError| {
97                    state.set(UseGeolocationState {
98                        loading: false,
99                        error: Some(error),
100                        ..*state
101                    });
102                }) as Box<dyn Fn(PositionError)>)
103            };
104
105            window()
106                .navigator()
107                .geolocation()
108                .unwrap_throw()
109                .get_current_position_with_error_callback_and_options(
110                    closure.as_ref().unchecked_ref(),
111                    Some(error_closure.as_ref().unchecked_ref()),
112                    &options,
113                )
114                .unwrap_throw();
115
116            let watch_id = window()
117                .navigator()
118                .geolocation()
119                .unwrap_throw()
120                .watch_position_with_error_callback_and_options(
121                    closure.as_ref().unchecked_ref(),
122                    Some(error_closure.as_ref().unchecked_ref()),
123                    &options,
124                )
125                .unwrap_throw();
126
127            // Forget the closure to keep it alive
128            closure.forget();
129            error_closure.forget();
130
131            move || {
132                window()
133                    .navigator()
134                    .geolocation()
135                    .unwrap_throw()
136                    .clear_watch(watch_id);
137            }
138        });
139    }
140
141    (*state).clone()
142}