fixed some SSR issues with not using use_window() in some functions

This commit is contained in:
Maccesch 2023-10-21 15:21:11 -05:00
parent dd2e88f8dd
commit f3af2ad9ea
15 changed files with 88 additions and 64 deletions

View file

@ -3,6 +3,13 @@
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.7.2] - 2023-10-21
### Fixes 🍕
- Some functions still used `window()` which could lead to panics in SSR. This is now fixed.
Specifically for `use_draggable`.
## [0.7.1] - 2023-10-02 ## [0.7.1] - 2023-10-02
### New Function 🚀 ### New Function 🚀

View file

@ -1,6 +1,6 @@
[package] [package]
name = "leptos-use" name = "leptos-use"
version = "0.7.1" version = "0.7.2"
edition = "2021" edition = "2021"
authors = ["Marc-Stefan Cassola"] authors = ["Marc-Stefan Cassola"]
categories = ["gui", "web-programming"] categories = ["gui", "web-programming"]

View file

@ -1,5 +1,5 @@
[package] [package]
name = "start-axum" name = "leptos-use-ssr"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"

View file

@ -5,8 +5,8 @@ async fn main() {
use leptos::logging::log; use leptos::logging::log;
use leptos::*; use leptos::*;
use leptos_axum::{generate_route_list, LeptosRoutes}; use leptos_axum::{generate_route_list, LeptosRoutes};
use start_axum::app::*; use leptos_use_ssr::app::*;
use start_axum::fileserv::file_and_error_handler; use leptos_use_ssr::fileserv::file_and_error_handler;
simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging"); simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
@ -18,7 +18,7 @@ async fn main() {
let conf = get_configuration(None).await.unwrap(); let conf = get_configuration(None).await.unwrap();
let leptos_options = conf.leptos_options; let leptos_options = conf.leptos_options;
let addr = leptos_options.site_addr; let addr = leptos_options.site_addr;
let routes = generate_route_list(|| view! { <App/> }).await; let routes = generate_route_list(|| view! { <App/> });
// build our application with a route // build our application with a route
let app = Router::new() let app = Router::new()

View file

@ -2,13 +2,16 @@ use leptos::html::Div;
use leptos::*; use leptos::*;
use leptos_use::core::Position; use leptos_use::core::Position;
use leptos_use::docs::demo_or_body; use leptos_use::docs::demo_or_body;
use leptos_use::{use_draggable_with_options, UseDraggableOptions, UseDraggableReturn}; use leptos_use::{use_draggable_with_options, use_window, UseDraggableOptions, UseDraggableReturn};
#[component] #[component]
fn Demo() -> impl IntoView { fn Demo() -> impl IntoView {
let el = create_node_ref::<Div>(); let el = create_node_ref::<Div>();
let inner_width = window().inner_width().unwrap().as_f64().unwrap(); let inner_width = use_window()
.as_ref()
.map(|w| w.inner_width().unwrap().as_f64().unwrap())
.unwrap_or(0.0);
let UseDraggableReturn { x, y, style, .. } = use_draggable_with_options( let UseDraggableReturn { x, y, style, .. } = use_draggable_with_options(
el, el,

View file

@ -2,11 +2,11 @@ use leptos::ev::{click, keydown};
use leptos::html::A; use leptos::html::A;
use leptos::logging::log; use leptos::logging::log;
use leptos::*; use leptos::*;
use leptos_use::use_event_listener; use leptos_use::{use_event_listener, use_window};
#[component] #[component]
fn Demo() -> impl IntoView { fn Demo() -> impl IntoView {
let _ = use_event_listener(window(), keydown, |evt| { let _ = use_event_listener(use_window(), keydown, |evt| {
log!("window keydown: '{}'", evt.key()); log!("window keydown: '{}'", evt.key());
}); });

View file

@ -5,6 +5,7 @@ mod maybe_rw_signal;
mod pointer_type; mod pointer_type;
mod position; mod position;
mod size; mod size;
mod ssr_safe_method;
mod storage; mod storage;
pub use connection_ready_state::*; pub use connection_ready_state::*;
@ -14,4 +15,5 @@ pub use maybe_rw_signal::*;
pub use pointer_type::*; pub use pointer_type::*;
pub use position::*; pub use position::*;
pub use size::*; pub use size::*;
pub(crate) use ssr_safe_method::*;
pub use storage::*; pub use storage::*;

View file

@ -0,0 +1,19 @@
macro_rules! impl_ssr_safe_method {
(
$(#[$attr:meta])*
$method:ident(&self$(, $p_name:ident: $p_ty:ty)*) -> $return_ty:ty
$(; $($post_fix:tt)+)?
) => {
$(#[$attr])*
#[inline(always)]
pub fn $method(&self, $($p_name: $p_ty),*) -> $return_ty {
self.0.as_ref()
.map(
|w| w.$method($($p_name),*)
)
$($($post_fix)+)?
}
};
}
pub(crate) use impl_ssr_safe_method;

View file

@ -1,6 +1,6 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))] #![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
use crate::{use_document, use_event_listener_with_options, UseEventListenerOptions}; use crate::{use_document, use_event_listener_with_options, use_window, UseEventListenerOptions};
use leptos::ev::{blur, focus}; use leptos::ev::{blur, focus};
use leptos::html::{AnyElement, ToHtmlElement}; use leptos::html::{AnyElement, ToHtmlElement};
use leptos::*; use leptos::*;
@ -45,7 +45,7 @@ pub fn use_active_element() -> Signal<Option<HtmlElement<AnyElement>>> {
let listener_options = UseEventListenerOptions::default().capture(true); let listener_options = UseEventListenerOptions::default().capture(true);
let _ = use_event_listener_with_options( let _ = use_event_listener_with_options(
window(), use_window(),
blur, blur,
move |event| { move |event| {
if event.related_target().is_some() { if event.related_target().is_some() {
@ -58,7 +58,7 @@ pub fn use_active_element() -> Signal<Option<HtmlElement<AnyElement>>> {
); );
let _ = use_event_listener_with_options( let _ = use_event_listener_with_options(
window(), use_window(),
focus, focus,
move |_| { move |_| {
set_active_element.update(|el| *el = get_active_element()); set_active_element.update(|el| *el = get_active_element());

View file

@ -1,4 +1,4 @@
use crate::use_media_query; use crate::{use_media_query, use_window};
use leptos::logging::error; use leptos::logging::error;
use leptos::*; use leptos::*;
use paste::paste; use paste::paste;
@ -185,7 +185,7 @@ macro_rules! impl_cmp_reactively {
impl<K: Eq + Hash + Debug + Clone> UseBreakpointsReturn<K> { impl<K: Eq + Hash + Debug + Clone> UseBreakpointsReturn<K> {
fn match_(query: &str) -> bool { fn match_(query: &str) -> bool {
if let Ok(Some(query_list)) = window().match_media(query) { if let Ok(Some(query_list)) = use_window().match_media(query) {
return query_list.matches(); return query_list.matches();
} }

View file

@ -1,6 +1,7 @@
use cfg_if::cfg_if; use cfg_if::cfg_if;
use std::ops::Deref; use std::ops::Deref;
use crate::core::impl_ssr_safe_method;
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
use leptos::*; use leptos::*;
@ -46,11 +47,15 @@ impl Deref for UseDocument {
} }
impl UseDocument { impl UseDocument {
pub fn body(&self) -> Option<web_sys::HtmlElement> { impl_ssr_safe_method!(
self.0.as_ref().and_then(|d| d.body()) /// Returns `Some(Document)` in the Browser. `None` otherwise.
} body(&self) -> Option<web_sys::HtmlElement>;
.unwrap_or_default()
);
pub fn active_element(&self) -> Option<web_sys::Element> { impl_ssr_safe_method!(
self.0.as_ref().and_then(|d| d.active_element()) /// Returns the active (focused) `Some(web_sys::Element)` in the Browser. `None` otherwise.
} active_element(&self) -> Option<web_sys::Element>;
.unwrap_or_default()
);
} }

View file

@ -1,5 +1,5 @@
use crate::core::{ElementMaybeSignal, MaybeRwSignal, PointerType, Position}; use crate::core::{ElementMaybeSignal, MaybeRwSignal, PointerType, Position};
use crate::{use_event_listener_with_options, UseEventListenerOptions}; use crate::{use_event_listener_with_options, use_window, UseEventListenerOptions, UseWindow};
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::ev::{pointerdown, pointermove, pointerup}; use leptos::ev::{pointerdown, pointermove, pointerup};
use leptos::*; use leptos::*;
@ -52,8 +52,8 @@ where
use_draggable_with_options::< use_draggable_with_options::<
El, El,
T, T,
web_sys::EventTarget, UseWindow,
web_sys::EventTarget, web_sys::Window,
web_sys::EventTarget, web_sys::EventTarget,
web_sys::EventTarget, web_sys::EventTarget,
>(target, UseDraggableOptions::default()) >(target, UseDraggableOptions::default())
@ -267,19 +267,14 @@ where
} }
impl Default impl Default
for UseDraggableOptions< for UseDraggableOptions<UseWindow, web_sys::Window, web_sys::EventTarget, web_sys::EventTarget>
web_sys::EventTarget,
web_sys::EventTarget,
web_sys::EventTarget,
web_sys::EventTarget,
>
{ {
fn default() -> Self { fn default() -> Self {
Self { Self {
exact: MaybeSignal::default(), exact: MaybeSignal::default(),
prevent_default: MaybeSignal::default(), prevent_default: MaybeSignal::default(),
stop_propagation: MaybeSignal::default(), stop_propagation: MaybeSignal::default(),
dragging_element: window().into(), dragging_element: use_window(),
handle: None, handle: None,
pointer_types: vec![PointerType::Mouse, PointerType::Touch, PointerType::Pen], pointer_types: vec![PointerType::Mouse, PointerType::Touch, PointerType::Pen],
initial_value: MaybeRwSignal::default(), initial_value: MaybeRwSignal::default(),

View file

@ -1,7 +1,7 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))] #![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
use crate::core::{ElementMaybeSignal, Position}; use crate::core::{ElementMaybeSignal, Position};
use crate::{use_event_listener_with_options, UseEventListenerOptions}; use crate::{use_event_listener_with_options, use_window, UseEventListenerOptions, UseWindow};
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::ev::{dragover, mousemove, touchend, touchmove, touchstart}; use leptos::ev::{dragover, mousemove, touchend, touchmove, touchstart};
@ -208,8 +208,7 @@ where
/// Options for [`use_mouse_with_options`]. /// Options for [`use_mouse_with_options`].
pub struct UseMouseOptions<El, T, Ex> pub struct UseMouseOptions<El, T, Ex>
where where
El: Clone, El: Clone + Into<ElementMaybeSignal<T, web_sys::EventTarget>>,
El: Into<ElementMaybeSignal<T, web_sys::EventTarget>>,
T: Into<web_sys::EventTarget> + Clone + 'static, T: Into<web_sys::EventTarget> + Clone + 'static,
Ex: UseMouseEventExtractor + Clone, Ex: UseMouseEventExtractor + Clone,
{ {
@ -232,33 +231,18 @@ where
_marker: PhantomData<T>, _marker: PhantomData<T>,
} }
cfg_if! { if #[cfg(feature = "ssr")] { impl Default for UseMouseOptions<UseWindow, web_sys::Window, UseMouseEventExtractorDefault> {
impl Default for UseMouseOptions<Option<web_sys::Window>, web_sys::Window, UseMouseEventExtractorDefault> { fn default() -> Self {
fn default() -> Self { Self {
Self { coord_type: UseMouseCoordType::<UseMouseEventExtractorDefault>::default(),
coord_type: UseMouseCoordType::<UseMouseEventExtractorDefault>::default(), target: use_window(),
target: None, touch: true,
touch: true, reset_on_touch_ends: false,
reset_on_touch_ends: false, initial_value: Position { x: 0.0, y: 0.0 },
initial_value: Position { x: 0.0, y: 0.0 }, _marker: PhantomData,
_marker: Default::default(),
}
} }
} }
} else { }
impl Default for UseMouseOptions<web_sys::Window, web_sys::Window, UseMouseEventExtractorDefault> {
fn default() -> Self {
Self {
coord_type: UseMouseCoordType::<UseMouseEventExtractorDefault>::default(),
target: window(),
touch: true,
reset_on_touch_ends: false,
initial_value: Position { x: 0.0, y: 0.0 },
_marker: Default::default(),
}
}
}
}}
/// Defines how to get the coordinates from the event. /// Defines how to get the coordinates from the event.
#[derive(Clone)] #[derive(Clone)]

View file

@ -1,3 +1,4 @@
use crate::core::impl_ssr_safe_method;
use crate::{use_document, UseDocument}; use crate::{use_document, UseDocument};
use cfg_if::cfg_if; use cfg_if::cfg_if;
use std::ops::Deref; use std::ops::Deref;
@ -48,14 +49,20 @@ impl Deref for UseWindow {
} }
impl UseWindow { impl UseWindow {
/// Returns the `Some(Navigator)` in the Browser. `None` otherwise. impl_ssr_safe_method!(
pub fn navigator(&self) -> Option<web_sys::Navigator> { /// Returns `Some(Navigator)` in the Browser. `None` otherwise.
self.0.as_ref().map(|w| w.navigator()) navigator(&self) -> Option<web_sys::Navigator>
} );
/// Returns the same as [`use_document`]. /// Returns the same as [`use_document`].
#[inline(always)] #[inline(always)]
pub fn document(&self) -> UseDocument { pub fn document(&self) -> UseDocument {
use_document() use_document()
} }
impl_ssr_safe_method!(
/// Returns the same as `window().match_media()` in the Browser. `Ok(None)` otherwise.
match_media(&self, query: &str) -> Result<Option<web_sys::MediaQueryList>, wasm_bindgen::JsValue>;
.unwrap_or(Ok(None))
);
} }

View file

@ -1,8 +1,10 @@
use crate::use_window;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use leptos::*;
lazy_static! { lazy_static! {
pub static ref IS_IOS: bool = if let Ok(user_agent) = window().navigator().user_agent() { pub static ref IS_IOS: bool = if let Some(Ok(user_agent)) =
use_window().navigator().map(|n| n.user_agent())
{
user_agent.contains("iPhone") || user_agent.contains("iPad") || user_agent.contains("iPod") user_agent.contains("iPhone") || user_agent.contains("iPad") || user_agent.contains("iPod")
} else { } else {
false false