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/),
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
### Fixes 🍕

View file

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

View file

@ -5,8 +5,8 @@ use leptos_meta::*;
use leptos_router::*;
use leptos_use::storage::use_local_storage;
use leptos_use::{
use_color_mode, use_debounce_fn, use_event_listener, use_intl_number_format, use_window,
ColorMode, UseColorModeReturn, UseIntlNumberFormatOptions,
use_color_mode, use_debounce_fn, use_event_listener, use_intl_number_format, use_timestamp,
use_window, ColorMode, UseColorModeReturn, UseIntlNumberFormatOptions,
};
#[component]
@ -65,6 +65,8 @@ fn HomePage() -> impl IntoView {
let UseColorModeReturn { mode, set_mode, .. } = use_color_mode();
let timestamp = use_timestamp();
view! {
<h1>Leptos-Use SSR Example</h1>
<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::Dark)>Change to Dark</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 {
cfg_if! { if #[cfg(feature = "ssr")] {
let _ = target;
Self::Static(None)
} else {
Self::Static(document().query_selector(target).unwrap_or_default())
@ -201,6 +202,7 @@ where
{
fn from(signal: Signal<String>) -> Self {
cfg_if! { if #[cfg(feature = "ssr")] {
let _ = signal;
Self::Dynamic(Signal::derive(|| None))
} else {
Self::Dynamic(

View file

@ -1,4 +1,5 @@
mod connection_ready_state;
mod datetime;
mod direction;
mod element_maybe_signal;
mod elements_maybe_signal;
@ -10,6 +11,7 @@ mod ssr_safe_method;
mod storage;
pub use connection_ready_state::*;
pub(crate) use datetime::*;
pub use direction::*;
pub use element_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::{
filter_builder_methods, use_document, use_event_listener, use_event_listener_with_options,
UseEventListenerOptions,
};
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::ev::{visibilitychange, Custom};
use leptos::leptos_dom::helpers::TimeoutHandle;
@ -54,6 +56,18 @@ use std::time::Duration;
/// # 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> {
use_idle_with_options(timeout, UseIdleOptions::default())
}
@ -71,8 +85,15 @@ pub fn use_idle_with_options(
} = options;
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 timer = Cell::new(None::<TimeoutHandle>);
@ -122,6 +143,7 @@ pub fn use_idle_with_options(
}
reset.clone()();
}}
UseIdleReturn {
idle: idle.into(),

View file

@ -1,10 +1,9 @@
use crate::utils::Pausable;
use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use leptos::*;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
use wasm_bindgen::closure::Closure;
use wasm_bindgen::JsCast;
/// Call function on every requestAnimationFrame.
/// 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 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(
callback: impl Fn(UseRafFnCallbackArgs) + 'static,
) -> 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 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 raf_handle = Rc::clone(&raf_handle);
@ -72,6 +81,7 @@ pub fn use_raf_fn_with_options(
.ok(),
);
}
}}
};
let loop_fn = {

View file

@ -1,3 +1,4 @@
use crate::core::now;
use crate::utils::Pausable;
use crate::{
use_interval_fn_with_options, use_raf_fn_with_options, UseIntervalFnOptions, UseRafFnOptions,
@ -47,7 +48,8 @@ use std::rc::Rc;
///
/// ## 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> {
use_timestamp_with_controls().timestamp
}
@ -71,10 +73,10 @@ pub fn use_timestamp_with_controls_and_options(options: UseTimestampOptions) ->
callback,
} = options;
let (ts, set_ts) = create_signal(js_sys::Date::now() + offset);
let (ts, set_ts) = create_signal(now() + offset);
let update = move || {
set_ts.set(js_sys::Date::now() + offset);
set_ts.set(now() + offset);
};
let cb = {

View file

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