Fixed SSR of use_timestamp, use_raf_fn and use_idle

Fixes #45
This commit is contained in:
Maccesch 2023-11-09 23:26:47 +00:00
parent 7084b91c0a
commit 9c3970a950
10 changed files with 136 additions and 70 deletions

View file

@ -3,6 +3,15 @@
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.8.2] - 2023-11-09
### Fixes 🍕
- Fixed SSR for
- use_timestamp
- use_raf_fn
- use_idle
## [0.8.1] - 2023-10-28 ## [0.8.1] - 2023-10-28
### Fixes 🍕 ### Fixes 🍕

View file

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

View file

@ -5,8 +5,8 @@ use leptos_meta::*;
use leptos_router::*; use leptos_router::*;
use leptos_use::storage::use_local_storage; use leptos_use::storage::use_local_storage;
use leptos_use::{ use leptos_use::{
use_color_mode, use_debounce_fn, use_event_listener, use_intl_number_format, use_window, use_color_mode, use_debounce_fn, use_event_listener, use_intl_number_format, use_timestamp,
ColorMode, UseColorModeReturn, UseIntlNumberFormatOptions, use_window, ColorMode, UseColorModeReturn, UseIntlNumberFormatOptions,
}; };
#[component] #[component]
@ -65,6 +65,8 @@ fn HomePage() -> impl IntoView {
let UseColorModeReturn { mode, set_mode, .. } = use_color_mode(); let UseColorModeReturn { mode, set_mode, .. } = use_color_mode();
let timestamp = use_timestamp();
view! { view! {
<h1>Leptos-Use SSR Example</h1> <h1>Leptos-Use SSR Example</h1>
<button on:click=on_click>Click Me: {count}</button> <button on:click=on_click>Click Me: {count}</button>
@ -75,5 +77,6 @@ fn HomePage() -> impl IntoView {
<button on:click=move |_| set_mode.set(ColorMode::Light)>Change to Light</button> <button on:click=move |_| set_mode.set(ColorMode::Light)>Change to Light</button>
<button on:click=move |_| set_mode.set(ColorMode::Dark)>Change to Dark</button> <button on:click=move |_| set_mode.set(ColorMode::Dark)>Change to Dark</button>
<button on:click=move |_| set_mode.set(ColorMode::Auto)>Change to Auto</button> <button on:click=move |_| set_mode.set(ColorMode::Auto)>Change to Auto</button>
<p>{timestamp}</p>
} }
} }

16
src/core/datetime.rs Normal file
View file

@ -0,0 +1,16 @@
use cfg_if::cfg_if;
/// SSR safe `Date.now()`.
#[inline(always)]
pub(crate) fn now() -> f64 {
cfg_if! { if #[cfg(feature = "ssr")] {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as f64
} else {
js_sys::Date::now()
}}
}

View file

@ -179,6 +179,7 @@ where
{ {
fn from(target: &'a str) -> Self { fn from(target: &'a str) -> Self {
cfg_if! { if #[cfg(feature = "ssr")] { cfg_if! { if #[cfg(feature = "ssr")] {
let _ = target;
Self::Static(None) Self::Static(None)
} else { } else {
Self::Static(document().query_selector(target).unwrap_or_default()) Self::Static(document().query_selector(target).unwrap_or_default())
@ -201,6 +202,7 @@ where
{ {
fn from(signal: Signal<String>) -> Self { fn from(signal: Signal<String>) -> Self {
cfg_if! { if #[cfg(feature = "ssr")] { cfg_if! { if #[cfg(feature = "ssr")] {
let _ = signal;
Self::Dynamic(Signal::derive(|| None)) Self::Dynamic(Signal::derive(|| None))
} else { } else {
Self::Dynamic( Self::Dynamic(

View file

@ -1,4 +1,5 @@
mod connection_ready_state; mod connection_ready_state;
mod datetime;
mod direction; mod direction;
mod element_maybe_signal; mod element_maybe_signal;
mod elements_maybe_signal; mod elements_maybe_signal;
@ -10,6 +11,7 @@ mod ssr_safe_method;
mod storage; mod storage;
pub use connection_ready_state::*; pub use connection_ready_state::*;
pub(crate) use datetime::*;
pub use direction::*; pub use direction::*;
pub use element_maybe_signal::*; pub use element_maybe_signal::*;
pub use elements_maybe_signal::*; pub use elements_maybe_signal::*;

View file

@ -1,8 +1,10 @@
use crate::core::now;
use crate::utils::{create_filter_wrapper, DebounceOptions, FilterOptions, ThrottleOptions}; use crate::utils::{create_filter_wrapper, DebounceOptions, FilterOptions, ThrottleOptions};
use crate::{ use crate::{
filter_builder_methods, use_document, use_event_listener, use_event_listener_with_options, filter_builder_methods, use_document, use_event_listener, use_event_listener_with_options,
UseEventListenerOptions, UseEventListenerOptions,
}; };
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::ev::{visibilitychange, Custom}; use leptos::ev::{visibilitychange, Custom};
use leptos::leptos_dom::helpers::TimeoutHandle; use leptos::leptos_dom::helpers::TimeoutHandle;
@ -54,6 +56,18 @@ use std::time::Duration;
/// # view! { } /// # view! { }
/// # } /// # }
/// ``` /// ```
///
/// ## Server-Side Rendering
///
/// On the server this will always return static signals
///
/// ```ignore
/// UseIdleReturn{
/// idle: Signal(initial_state),
/// last_active: Signal(now),
/// reset: || {}
/// }
/// ```
pub fn use_idle(timeout: u64) -> UseIdleReturn<impl Fn() + Clone> { pub fn use_idle(timeout: u64) -> UseIdleReturn<impl Fn() + Clone> {
use_idle_with_options(timeout, UseIdleOptions::default()) use_idle_with_options(timeout, UseIdleOptions::default())
} }
@ -71,8 +85,15 @@ pub fn use_idle_with_options(
} = options; } = options;
let (idle, set_idle) = create_signal(initial_state); let (idle, set_idle) = create_signal(initial_state);
let (last_active, set_last_active) = create_signal(js_sys::Date::now()); let (last_active, set_last_active) = create_signal(now());
cfg_if! { if #[cfg(feature = "ssr")] {
let reset = || ();
let _ = timeout;
let _ = events;
let _ = listen_for_visibility_change;
let _ = filter;
} else {
let reset = { let reset = {
let timer = Cell::new(None::<TimeoutHandle>); let timer = Cell::new(None::<TimeoutHandle>);
@ -122,6 +143,7 @@ pub fn use_idle_with_options(
} }
reset.clone()(); reset.clone()();
}}
UseIdleReturn { UseIdleReturn {
idle: idle.into(), idle: idle.into(),

View file

@ -1,10 +1,9 @@
use crate::utils::Pausable; use crate::utils::Pausable;
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use leptos::*; use leptos::*;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::rc::Rc; use std::rc::Rc;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
/// Call function on every requestAnimationFrame. /// Call function on every requestAnimationFrame.
/// With controls of pausing and resuming. /// With controls of pausing and resuming.
@ -34,6 +33,10 @@ use wasm_bindgen::JsCast;
/// ///
/// You can use `use_raf_fn_with_options` and set `immediate` to `false`. In that case /// You can use `use_raf_fn_with_options` and set `immediate` to `false`. In that case
/// you have to call `resume()` before the `callback` is executed. /// you have to call `resume()` before the `callback` is executed.
///
/// ## Server-Side Rendering
///
/// On the server this does basically nothing. The provided closure will never be called.
pub fn use_raf_fn( pub fn use_raf_fn(
callback: impl Fn(UseRafFnCallbackArgs) + 'static, callback: impl Fn(UseRafFnCallbackArgs) + 'static,
) -> Pausable<impl Fn() + Clone, impl Fn() + Clone> { ) -> Pausable<impl Fn() + Clone, impl Fn() + Clone> {
@ -54,6 +57,12 @@ pub fn use_raf_fn_with_options(
let loop_ref = Rc::new(RefCell::new(Box::new(|_: f64| {}) as Box<dyn Fn(f64)>)); let loop_ref = Rc::new(RefCell::new(Box::new(|_: f64| {}) as Box<dyn Fn(f64)>));
let request_next_frame = { let request_next_frame = {
cfg_if! { if #[cfg(feature = "ssr")] {
move || ()
} else {
use wasm_bindgen::JsCast;
use wasm_bindgen::closure::Closure;
let loop_ref = Rc::clone(&loop_ref); let loop_ref = Rc::clone(&loop_ref);
let raf_handle = Rc::clone(&raf_handle); let raf_handle = Rc::clone(&raf_handle);
@ -72,6 +81,7 @@ pub fn use_raf_fn_with_options(
.ok(), .ok(),
); );
} }
}}
}; };
let loop_fn = { let loop_fn = {

View file

@ -1,3 +1,4 @@
use crate::core::now;
use crate::utils::Pausable; use crate::utils::Pausable;
use crate::{ use crate::{
use_interval_fn_with_options, use_raf_fn_with_options, UseIntervalFnOptions, UseRafFnOptions, use_interval_fn_with_options, use_raf_fn_with_options, UseIntervalFnOptions, UseRafFnOptions,
@ -47,7 +48,8 @@ use std::rc::Rc;
/// ///
/// ## Server-Side Rendering /// ## Server-Side Rendering
/// ///
/// On the server this function will simply be ignored. /// On the server this function will return a signal with the milliseconds since the Unix epoch.
/// But the signal will never update (as there's no `request_animation_frame` on the server).
pub fn use_timestamp() -> Signal<f64> { pub fn use_timestamp() -> Signal<f64> {
use_timestamp_with_controls().timestamp use_timestamp_with_controls().timestamp
} }
@ -71,10 +73,10 @@ pub fn use_timestamp_with_controls_and_options(options: UseTimestampOptions) ->
callback, callback,
} = options; } = options;
let (ts, set_ts) = create_signal(js_sys::Date::now() + offset); let (ts, set_ts) = create_signal(now() + offset);
let update = move || { let update = move || {
set_ts.set(js_sys::Date::now() + offset); set_ts.set(now() + offset);
}; };
let cb = { let cb = {

View file

@ -1,8 +1,8 @@
#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))] #![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
use crate::core::now;
use cfg_if::cfg_if; use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder; use default_struct_builder::DefaultBuilder;
use js_sys::Date;
use leptos::leptos_dom::helpers::TimeoutHandle; use leptos::leptos_dom::helpers::TimeoutHandle;
use leptos::{set_timeout_with_handle, MaybeSignal, SignalGetUntracked}; use leptos::{set_timeout_with_handle, MaybeSignal, SignalGetUntracked};
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
@ -51,7 +51,7 @@ where
move |mut _invoke: Rc<dyn Fn() -> R>| { move |mut _invoke: Rc<dyn Fn() -> R>| {
let duration = ms.get_untracked(); let duration = ms.get_untracked();
let elapsed = Date::now() - last_exec.get(); let elapsed = now() - last_exec.get();
let last_return_val = Rc::clone(&last_return_value); let last_return_val = Rc::clone(&last_return_value);
let invoke = move || { let invoke = move || {
@ -65,13 +65,13 @@ where
clear(); clear();
if duration <= 0.0 { if duration <= 0.0 {
last_exec.set(Date::now()); last_exec.set(now());
invoke(); invoke();
return Rc::clone(&last_return_value); return Rc::clone(&last_return_value);
} }
if elapsed > duration && (options.leading || !is_leading.get()) { if elapsed > duration && (options.leading || !is_leading.get()) {
last_exec.set(Date::now()); last_exec.set(now());
invoke(); invoke();
} else if options.trailing { } else if options.trailing {
cfg_if! { if #[cfg(not(feature = "ssr"))] { cfg_if! { if #[cfg(not(feature = "ssr"))] {
@ -80,7 +80,7 @@ where
timer.set( timer.set(
set_timeout_with_handle( set_timeout_with_handle(
move || { move || {
last_exec.set(Date::now()); last_exec.set(now());
is_leading.set(true); is_leading.set(true);
invoke(); invoke();
clear(); clear();