diff --git a/docs/book/src/server_side_rendering.md b/docs/book/src/server_side_rendering.md
new file mode 100644
index 0000000..ace770e
--- /dev/null
+++ b/docs/book/src/server_side_rendering.md
@@ -0,0 +1,49 @@
+# Server-Side Rendering
+
+When using together with server-side rendering (SSR) you have to enable the feature `ssr` similar to
+how you do it for `leptos`.
+
+In your Cargo.toml file add the following:
+
+```toml
+...
+
+[features]
+hydrate = [
+ "leptos/hydrate",
+ ...
+]
+ssr = [
+ ...
+ "leptos/ssr",
+ ...
+ "leptos-use/ssr" # add this
+]
+
+...
+```
+
+Please see the `ssr` example in the examples folder for a simple working demonstration.
+
+Many functions work differently on the server and on the client. If that's the case you will
+find information about these differences in their respective docs under the section "Server-Side Rendering".
+If you don't find that section, it means that the function works exactly the same on both, the client
+and the server.
+
+## Functions with Target Elements
+
+A lot of functions like `use_event_listener` and `use_element_size` are only useful when a target HTML/SVG element is
+available. This is not the case on the server. You can simply wrap them in `create_effect` which will cause them to
+be only called in the browser.
+
+```rust
+create_effect(
+ cx,
+ move |_| {
+ // window() doesn't work on the server
+ use_event_listener(cx, window(), "resize", move |_| {
+ // ...
+ })
+ },
+);
+```
\ No newline at end of file
diff --git a/examples/.cargo/config.toml b/examples/.cargo/config.toml
index c841b61..c87f326 100644
--- a/examples/.cargo/config.toml
+++ b/examples/.cargo/config.toml
@@ -1,6 +1,2 @@
[build]
rustflags = ["--cfg=web_sys_unstable_apis", "--cfg=has_std"]
-
-[unstable]
-build-std = ["std", "panic_abort", "core", "alloc"]
-build-std-features = ["panic_immediate_abort"]
\ No newline at end of file
diff --git a/examples/Cargo.toml b/examples/Cargo.toml
index 293966b..eabb6fd 100644
--- a/examples/Cargo.toml
+++ b/examples/Cargo.toml
@@ -38,6 +38,10 @@ members = [
"watch_throttled",
]
+exclude = [
+ "ssr"
+]
+
[package]
name = "leptos-use-examples"
version = "0.3.3"
diff --git a/examples/ssr/.gitignore b/examples/ssr/.gitignore
new file mode 100644
index 0000000..a1e76e1
--- /dev/null
+++ b/examples/ssr/.gitignore
@@ -0,0 +1,14 @@
+# Generated by Cargo
+# will have compiled files and executables
+/target/
+pkg
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# node e2e test tools and outputs
+node_modules/
+test-results/
+end2end/playwright-report/
+playwright/.cache/
diff --git a/examples/ssr/Cargo.toml b/examples/ssr/Cargo.toml
new file mode 100644
index 0000000..1479522
--- /dev/null
+++ b/examples/ssr/Cargo.toml
@@ -0,0 +1,100 @@
+[package]
+name = "start-axum"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[dependencies]
+axum = { version = "0.6.4", optional = true }
+console_error_panic_hook = "0.1"
+console_log = "1"
+cfg-if = "1"
+leptos = { version = "0.4", features = ["nightly"] }
+leptos_axum = { version = "0.4", optional = true }
+leptos_meta = { version = "0.4", features = ["nightly"] }
+leptos_router = { version = "0.4", features = ["nightly"] }
+leptos-use = { path = "../..", features = ["storage"] }
+log = "0.4"
+simple_logger = "4"
+tokio = { version = "1.25.0", optional = true }
+tower = { version = "0.4.13", optional = true }
+tower-http = { version = "0.4", features = ["fs"], optional = true }
+wasm-bindgen = "=0.2.87"
+thiserror = "1.0.38"
+tracing = { version = "0.1.37", optional = true }
+http = "0.2.8"
+
+[features]
+hydrate = ["leptos/hydrate", "leptos_meta/hydrate", "leptos_router/hydrate"]
+ssr = [
+ "dep:axum",
+ "dep:tokio",
+ "dep:tower",
+ "dep:tower-http",
+ "dep:leptos_axum",
+ "leptos/ssr",
+ "leptos_meta/ssr",
+ "leptos_router/ssr",
+ "dep:tracing",
+ "leptos-use/ssr"
+]
+
+[package.metadata.cargo-all-features]
+denylist = ["axum", "tokio", "tower", "tower-http", "leptos_axum"]
+skip_feature_sets = [["ssr", "hydrate"]]
+
+[package.metadata.leptos]
+# The name used by wasm-bindgen/cargo-leptos for the JS/WASM bundle. Defaults to the crate name
+output-name = "start-axum"
+
+# The site root folder is where cargo-leptos generate all output. WARNING: all content of this folder will be erased on a rebuild. Use it in your server setup.
+site-root = "target/site"
+
+# The site-root relative folder where all compiled output (JS, WASM and CSS) is written
+# Defaults to pkg
+site-pkg-dir = "pkg"
+
+# [Optional] The source CSS file. If it ends with .sass or .scss then it will be compiled by dart-sass into CSS. The CSS is optimized by Lightning CSS before being written to //app.css
+style-file = "style/main.scss"
+# Assets source dir. All files found here will be copied and synchronized to site-root.
+# The assets-dir cannot have a sub directory with the same name/path as site-pkg-dir.
+#
+# Optional. Env: LEPTOS_ASSETS_DIR.
+assets-dir = "public"
+
+# The IP and port (ex: 127.0.0.1:3000) where the server serves the content. Use it in your server setup.
+site-addr = "127.0.0.1:3000"
+
+# The port to use for automatic reload monitoring
+reload-port = 3001
+
+# The browserlist query used for optimizing the CSS.
+browserquery = "defaults"
+
+# Set by cargo-leptos watch when building with that tool. Controls whether autoreload JS will be included in the head
+watch = false
+
+# The environment Leptos will run in, usually either "DEV" or "PROD"
+env = "DEV"
+
+# The features to use when compiling the bin target
+#
+# Optional. Can be over-ridden with the command line parameter --bin-features
+bin-features = ["ssr"]
+
+# If the --no-default-features flag should be used when compiling the bin target
+#
+# Optional. Defaults to false.
+bin-default-features = false
+
+# The features to use when compiling the lib target
+#
+# Optional. Can be over-ridden with the command line parameter --lib-features
+lib-features = ["hydrate"]
+
+# If the --no-default-features flag should be used when compiling the lib target
+#
+# Optional. Defaults to false.
+lib-default-features = false
diff --git a/examples/ssr/README.md b/examples/ssr/README.md
new file mode 100644
index 0000000..cab9181
--- /dev/null
+++ b/examples/ssr/README.md
@@ -0,0 +1,56 @@
+# Leptos-Use SSR Example
+
+## Running the example
+
+```bash
+cargo leptos watch
+```
+
+## Installing Additional Tools
+
+By default, `cargo-leptos` uses `nightly` Rust, `cargo-generate`, and `sass`. If you run into any trouble, you may need to install one or more of these tools.
+
+1. `rustup toolchain install nightly --allow-downgrade` - make sure you have Rust nightly
+2. `rustup target add wasm32-unknown-unknown` - add the ability to compile Rust to WebAssembly
+3. `cargo install cargo-generate` - install `cargo-generate` binary (should be installed automatically in future)
+4. `npm install -g sass` - install `dart-sass` (should be optional in future
+
+## Compiling for Release
+```bash
+cargo leptos build --release
+```
+
+Will generate your server binary in target/server/release and your site package in target/site
+
+## Testing Your Project
+```bash
+cargo leptos end-to-end
+```
+
+```bash
+cargo leptos end-to-end --release
+```
+
+Cargo-leptos uses Playwright as the end-to-end test tool.
+Tests are located in end2end/tests directory.
+
+## Executing a Server on a Remote Machine Without the Toolchain
+After running a `cargo leptos build --release` the minimum files needed are:
+
+1. The server binary located in `target/server/release`
+2. The `site` directory and all files within located in `target/site`
+
+Copy these files to your remote server. The directory structure should be:
+```text
+start-axum
+site/
+```
+Set the following environment variables (updating for your project as needed):
+```text
+LEPTOS_OUTPUT_NAME="start-axum"
+LEPTOS_SITE_ROOT="site"
+LEPTOS_SITE_PKG_DIR="pkg"
+LEPTOS_SITE_ADDR="127.0.0.1:3000"
+LEPTOS_RELOAD_PORT="3001"
+```
+Finally, run the server binary.
diff --git a/examples/ssr/public/favicon.ico b/examples/ssr/public/favicon.ico
new file mode 100644
index 0000000..2ba8527
Binary files /dev/null and b/examples/ssr/public/favicon.ico differ
diff --git a/examples/ssr/rust-toolchain.toml b/examples/ssr/rust-toolchain.toml
new file mode 100644
index 0000000..5d56faf
--- /dev/null
+++ b/examples/ssr/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "nightly"
diff --git a/examples/ssr/src/app.rs b/examples/ssr/src/app.rs
new file mode 100644
index 0000000..99647ed
--- /dev/null
+++ b/examples/ssr/src/app.rs
@@ -0,0 +1,79 @@
+use crate::error_template::{AppError, ErrorTemplate};
+use leptos::ev::{keypress, KeyboardEvent};
+use leptos::*;
+use leptos_meta::*;
+use leptos_router::*;
+use leptos_use::storage::use_local_storage;
+use leptos_use::{
+ use_debounce_fn, use_event_listener, use_intl_number_format, UseIntlNumberFormatOptions,
+};
+
+#[component]
+pub fn App(cx: Scope) -> impl IntoView {
+ // Provides context that manages stylesheets, titles, meta tags, etc.
+ provide_meta_context(cx);
+
+ view! {
+ cx,
+
+
+
+
+
+
+ }
+ .into_view(cx)
+ }>
+
+
+ }/>
+
+
+
+ }
+}
+
+/// Renders the home page of your application.
+#[component]
+fn HomePage(cx: Scope) -> impl IntoView {
+ // Creates a reactive value to update the button
+ let (count, set_count, _) = use_local_storage(cx, "count-state", 0);
+ let on_click = move |_| set_count.update(|count| *count += 1);
+
+ let nf = use_intl_number_format(
+ UseIntlNumberFormatOptions::default().locale("zh-Hans-CN-u-nu-hanidec"),
+ );
+
+ let zh_count = nf.format::(cx, count);
+
+ let (key, set_key) = create_signal(cx, "".to_string());
+
+ create_effect(cx, move |_| {
+ // window() doesn't work on the server
+ let _ = use_event_listener(cx, window(), keypress, move |evt: KeyboardEvent| {
+ set_key(evt.key())
+ });
+ });
+
+ let (debounce_value, set_debounce_value) = create_signal(cx, "not called");
+
+ let debounced_fn = use_debounce_fn(
+ move || {
+ set_debounce_value("called");
+ },
+ 2000.0,
+ );
+ debounced_fn();
+
+ view! { cx,
+
+ }
+}
diff --git a/examples/ssr/src/error_template.rs b/examples/ssr/src/error_template.rs
new file mode 100644
index 0000000..3a947fa
--- /dev/null
+++ b/examples/ssr/src/error_template.rs
@@ -0,0 +1,76 @@
+use cfg_if::cfg_if;
+use http::status::StatusCode;
+use leptos::*;
+use thiserror::Error;
+
+#[cfg(feature = "ssr")]
+use leptos_axum::ResponseOptions;
+
+#[derive(Clone, Debug, Error)]
+pub enum AppError {
+ #[error("Not Found")]
+ NotFound,
+}
+
+impl AppError {
+ pub fn status_code(&self) -> StatusCode {
+ match self {
+ AppError::NotFound => StatusCode::NOT_FOUND,
+ }
+ }
+}
+
+// A basic function to display errors served by the error boundaries.
+// Feel free to do more complicated things here than just displaying the error.
+#[component]
+pub fn ErrorTemplate(
+ cx: Scope,
+ #[prop(optional)] outside_errors: Option,
+ #[prop(optional)] errors: Option>,
+) -> impl IntoView {
+ let errors = match outside_errors {
+ Some(e) => create_rw_signal(cx, e),
+ None => match errors {
+ Some(e) => e,
+ None => panic!("No Errors found and we expected errors!"),
+ },
+ };
+ // Get Errors from Signal
+ let errors = errors.get();
+
+ // Downcast lets us take a type that implements `std::error::Error`
+ let errors: Vec = errors
+ .into_iter()
+ .filter_map(|(_k, v)| v.downcast_ref::().cloned())
+ .collect();
+ println!("Errors: {errors:#?}");
+
+ // Only the response code for the first error is actually sent from the server
+ // this may be customized by the specific application
+ cfg_if! { if #[cfg(feature="ssr")] {
+ let response = use_context::(cx);
+ if let Some(response) = response {
+ response.set_status(errors[0].status_code());
+ }
+ }}
+
+ view! {cx,
+
{if errors.len() > 1 {"Errors"} else {"Error"}}
+ {error_code.to_string()}
+
"Error: " {error_string}
+ }
+ }
+ />
+ }
+}
diff --git a/examples/ssr/src/fileserv.rs b/examples/ssr/src/fileserv.rs
new file mode 100644
index 0000000..07fade7
--- /dev/null
+++ b/examples/ssr/src/fileserv.rs
@@ -0,0 +1,40 @@
+use cfg_if::cfg_if;
+
+cfg_if! { if #[cfg(feature = "ssr")] {
+ use axum::{
+ body::{boxed, Body, BoxBody},
+ extract::State,
+ response::IntoResponse,
+ http::{Request, Response, StatusCode, Uri},
+ };
+ use axum::response::Response as AxumResponse;
+ use tower::ServiceExt;
+ use tower_http::services::ServeDir;
+ use leptos::*;
+ use crate::app::App;
+
+ pub async fn file_and_error_handler(uri: Uri, State(options): State, req: Request) -> AxumResponse {
+ let root = options.site_root.clone();
+ let res = get_static_file(uri.clone(), &root).await.unwrap();
+
+ if res.status() == StatusCode::OK {
+ res.into_response()
+ } else {
+ let handler = leptos_axum::render_app_to_stream(options.to_owned(), move |cx| view!{cx, });
+ handler(req).await.into_response()
+ }
+ }
+
+ async fn get_static_file(uri: Uri, root: &str) -> Result, (StatusCode, String)> {
+ let req = Request::builder().uri(uri.clone()).body(Body::empty()).unwrap();
+ // `ServeDir` implements `tower::Service` so we can call it with `tower::ServiceExt::oneshot`
+ // This path is relative to the cargo root
+ match ServeDir::new(root).oneshot(req).await {
+ Ok(res) => Ok(res.map(boxed)),
+ Err(err) => Err((
+ StatusCode::INTERNAL_SERVER_ERROR,
+ format!("Something went wrong: {err}"),
+ )),
+ }
+ }
+}}
diff --git a/examples/ssr/src/lib.rs b/examples/ssr/src/lib.rs
new file mode 100644
index 0000000..7190a49
--- /dev/null
+++ b/examples/ssr/src/lib.rs
@@ -0,0 +1,21 @@
+use cfg_if::cfg_if;
+pub mod app;
+pub mod error_template;
+pub mod fileserv;
+
+cfg_if! { if #[cfg(feature = "hydrate")] {
+ use leptos::*;
+ use wasm_bindgen::prelude::wasm_bindgen;
+ use crate::app::*;
+
+ #[wasm_bindgen]
+ pub fn hydrate() {
+ // initializes logging using the `log` crate
+ _ = console_log::init_with_level(log::Level::Debug);
+ console_error_panic_hook::set_once();
+
+ leptos::mount_to_body(move |cx| {
+ view! { cx, }
+ });
+ }
+}}
diff --git a/examples/ssr/src/main.rs b/examples/ssr/src/main.rs
new file mode 100644
index 0000000..1930efc
--- /dev/null
+++ b/examples/ssr/src/main.rs
@@ -0,0 +1,43 @@
+#[cfg(feature = "ssr")]
+#[tokio::main]
+async fn main() {
+ use axum::{routing::post, Router};
+ use leptos::*;
+ use leptos_axum::{generate_route_list, LeptosRoutes};
+ use start_axum::app::*;
+ use start_axum::fileserv::file_and_error_handler;
+
+ simple_logger::init_with_level(log::Level::Debug).expect("couldn't initialize logging");
+
+ // Setting get_configuration(None) means we'll be using cargo-leptos's env values
+ // For deployment these variables are:
+ //
+ // Alternately a file can be specified such as Some("Cargo.toml")
+ // The file would need to be included with the executable when moved to deployment
+ let conf = get_configuration(None).await.unwrap();
+ let leptos_options = conf.leptos_options;
+ let addr = leptos_options.site_addr;
+ let routes = generate_route_list(|cx| view! { cx, }).await;
+
+ // build our application with a route
+ let app = Router::new()
+ .route("/api/*fn_name", post(leptos_axum::handle_server_fns))
+ .leptos_routes(&leptos_options, routes, |cx| view! { cx, })
+ .fallback(file_and_error_handler)
+ .with_state(leptos_options);
+
+ // run our app with hyper
+ // `axum::Server` is a re-export of `hyper::Server`
+ log!("listening on http://{}", &addr);
+ axum::Server::bind(&addr)
+ .serve(app.into_make_service())
+ .await
+ .unwrap();
+}
+
+#[cfg(not(feature = "ssr"))]
+pub fn main() {
+ // no client-side main function
+ // unless we want this to work with e.g., Trunk for a purely client-side app
+ // see lib.rs for hydration function instead
+}
diff --git a/examples/ssr/style/main.scss b/examples/ssr/style/main.scss
new file mode 100644
index 0000000..e4538e1
--- /dev/null
+++ b/examples/ssr/style/main.scss
@@ -0,0 +1,4 @@
+body {
+ font-family: sans-serif;
+ text-align: center;
+}
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
index b0a89db..6008a83 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,8 @@
// #![feature(doc_cfg)]
//! Collection of essential Leptos utilities inspired by SolidJS USE / VueUse
+use cfg_if::cfg_if;
+
pub mod core;
#[cfg(feature = "docs")]
pub mod docs;
@@ -10,15 +12,13 @@ pub mod math;
pub mod storage;
pub mod utils;
-#[cfg(web_sys_unstable_apis)]
-mod use_element_size;
-#[cfg(web_sys_unstable_apis)]
-mod use_resize_observer;
+cfg_if! { if #[cfg(web_sys_unstable_apis)] {
+ mod use_element_size;
+ mod use_resize_observer;
-#[cfg(web_sys_unstable_apis)]
-pub use use_element_size::*;
-#[cfg(web_sys_unstable_apis)]
-pub use use_resize_observer::*;
+ pub use use_element_size::*;
+ pub use use_resize_observer::*;
+}}
mod on_click_outside;
mod use_active_element;
diff --git a/src/on_click_outside.rs b/src/on_click_outside.rs
index 5167099..6f4dd01 100644
--- a/src/on_click_outside.rs
+++ b/src/on_click_outside.rs
@@ -45,6 +45,10 @@ static IOS_WORKAROUND: RwLock = RwLock::new(false);
/// which is **not** supported by IE 11, Edge 18 and below.
/// If you are targeting these browsers, we recommend you to include
/// [this code snippet](https://gist.github.com/sibbng/13e83b1dd1b733317ce0130ef07d4efd) on your project.
+///
+/// ## Server-Side Rendering
+///
+/// Please refer to ["Functions with Target Elements"](https://leptos-use.rs/server_side_rendering.html#functions-with-target-elements)
pub fn on_click_outside(cx: Scope, target: El, handler: F) -> impl FnOnce() + Clone
where
El: Clone,
diff --git a/src/storage/use_storage.rs b/src/storage/use_storage.rs
index 511275d..d517ccf 100644
--- a/src/storage/use_storage.rs
+++ b/src/storage/use_storage.rs
@@ -1,8 +1,11 @@
+#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))]
+
use crate::utils::{CloneableFn, CloneableFnWithArg, FilterOptions};
use crate::{
filter_builder_methods, use_event_listener, watch_pausable_with_options, DebounceOptions,
ThrottleOptions, WatchOptions, WatchPausableReturn,
};
+use cfg_if::cfg_if;
use default_struct_builder::DefaultBuilder;
use js_sys::Reflect;
use leptos::*;
@@ -144,6 +147,10 @@ const CUSTOM_STORAGE_EVENT_NAME: &str = "leptos-use-storage";
///
/// You can specify `debounce` or `throttle` options for limiting writes to storage.
///
+/// ## Server-Side Rendering
+///
+/// On the server this falls back to a `create_signal(cx, default)` and an empty remove function.
+///
/// ## See also
///
/// * [`use_local_storage`]
@@ -188,192 +195,196 @@ where
let (data, set_data) = create_signal(cx, defaults.get_untracked());
- let storage = storage_type.into_storage();
+ cfg_if! { if #[cfg(feature = "ssr")] {
+ let remove: Box = Box::new(|| {});
+ } else {
+ let storage = storage_type.into_storage();
- let remove: Box = match storage {
- Ok(Some(storage)) => {
- let on_err = on_error.clone();
+ let remove: Box = match storage {
+ Ok(Some(storage)) => {
+ let on_err = on_error.clone();
- let store = storage.clone();
- let k = key.to_string();
+ let store = storage.clone();
+ let k = key.to_string();
- let write = move |v: &T| {
- match serde_json::to_string(&v) {
- Ok(ref serialized) => match store.get_item(&k) {
- Ok(old_value) => {
- if old_value.as_ref() != Some(serialized) {
- if let Err(e) = store.set_item(&k, serialized) {
- on_err(UseStorageError::StorageAccessError(e));
- } else {
- let mut event_init = web_sys::CustomEventInit::new();
- event_init.detail(
- &StorageEventDetail {
- key: Some(k.clone()),
- old_value,
- new_value: Some(serialized.clone()),
- storage_area: Some(store.clone()),
- }
- .into(),
- );
+ let write = move |v: &T| {
+ match serde_json::to_string(&v) {
+ Ok(ref serialized) => match store.get_item(&k) {
+ Ok(old_value) => {
+ if old_value.as_ref() != Some(serialized) {
+ if let Err(e) = store.set_item(&k, serialized) {
+ on_err(UseStorageError::StorageAccessError(e));
+ } else {
+ let mut event_init = web_sys::CustomEventInit::new();
+ event_init.detail(
+ &StorageEventDetail {
+ key: Some(k.clone()),
+ old_value,
+ new_value: Some(serialized.clone()),
+ storage_area: Some(store.clone()),
+ }
+ .into(),
+ );
- // importantly this should _not_ be a StorageEvent since those cannot
- // be constructed with a non-built-in storage area
- let _ = window().dispatch_event(
- &web_sys::CustomEvent::new_with_event_init_dict(
- CUSTOM_STORAGE_EVENT_NAME,
- &event_init,
- )
- .expect("Failed to create CustomEvent"),
- );
+ // importantly this should _not_ be a StorageEvent since those cannot
+ // be constructed with a non-built-in storage area
+ let _ = window().dispatch_event(
+ &web_sys::CustomEvent::new_with_event_init_dict(
+ CUSTOM_STORAGE_EVENT_NAME,
+ &event_init,
+ )
+ .expect("Failed to create CustomEvent"),
+ );
+ }
}
}
- }
- Err(e) => {
- on_err.clone()(UseStorageError::StorageAccessError(e));
- }
- },
- Err(e) => {
- on_err.clone()(UseStorageError::SerializationError(e));
- }
- }
- };
-
- let store = storage.clone();
- let on_err = on_error.clone();
- let k = key.to_string();
- let def = defaults.clone();
-
- let read = move |event_detail: Option| -> Option {
- let raw_init = match serde_json::to_string(&def.get_untracked()) {
- Ok(serialized) => Some(serialized),
- Err(e) => {
- on_err.clone()(UseStorageError::DefaultSerializationError(e));
- None
- }
- };
-
- let raw_value = if let Some(event_detail) = event_detail {
- event_detail.new_value
- } else {
- match store.get_item(&k) {
- Ok(raw_value) => match raw_value {
- Some(raw_value) => {
- Some(merge_defaults(&raw_value, &def.get_untracked()))
+ Err(e) => {
+ on_err.clone()(UseStorageError::StorageAccessError(e));
}
- None => raw_init.clone(),
},
Err(e) => {
- on_err.clone()(UseStorageError::StorageAccessError(e));
- None
+ on_err.clone()(UseStorageError::SerializationError(e));
}
}
};
- match raw_value {
- Some(raw_value) => match serde_json::from_str(&raw_value) {
- Ok(v) => Some(v),
+ let store = storage.clone();
+ let on_err = on_error.clone();
+ let k = key.to_string();
+ let def = defaults.clone();
+
+ let read = move |event_detail: Option| -> Option {
+ let raw_init = match serde_json::to_string(&def.get_untracked()) {
+ Ok(serialized) => Some(serialized),
Err(e) => {
- on_err.clone()(UseStorageError::SerializationError(e));
+ on_err.clone()(UseStorageError::DefaultSerializationError(e));
None
}
- },
- None => {
- if let Some(raw_init) = &raw_init {
- if write_defaults {
- if let Err(e) = store.set_item(&k, raw_init) {
- on_err(UseStorageError::StorageAccessError(e));
+ };
+
+ let raw_value = if let Some(event_detail) = event_detail {
+ event_detail.new_value
+ } else {
+ match store.get_item(&k) {
+ Ok(raw_value) => match raw_value {
+ Some(raw_value) => {
+ Some(merge_defaults(&raw_value, &def.get_untracked()))
}
- }
- }
-
- Some(def.get_untracked())
- }
- }
- };
-
- let WatchPausableReturn {
- pause: pause_watch,
- resume: resume_watch,
- ..
- } = watch_pausable_with_options(
- cx,
- move || data.get(),
- move |data, _, _| write.clone()(data),
- WatchOptions::default().filter(filter),
- );
-
- let k = key.to_string();
- let store = storage.clone();
-
- let update = move |event_detail: Option| {
- if let Some(event_detail) = &event_detail {
- if event_detail.storage_area != Some(store) {
- return;
- }
-
- match &event_detail.key {
- None => {
- set_data.set(defaults.get_untracked());
- return;
- }
- Some(event_key) => {
- if event_key != &k {
- return;
+ None => raw_init.clone(),
+ },
+ Err(e) => {
+ on_err.clone()(UseStorageError::StorageAccessError(e));
+ None
}
}
};
- }
- pause_watch();
+ match raw_value {
+ Some(raw_value) => match serde_json::from_str(&raw_value) {
+ Ok(v) => Some(v),
+ Err(e) => {
+ on_err.clone()(UseStorageError::SerializationError(e));
+ None
+ }
+ },
+ None => {
+ if let Some(raw_init) = &raw_init {
+ if write_defaults {
+ if let Err(e) = store.set_item(&k, raw_init) {
+ on_err(UseStorageError::StorageAccessError(e));
+ }
+ }
+ }
- if let Some(value) = read(event_detail.clone()) {
- set_data.set(value);
- }
+ Some(def.get_untracked())
+ }
+ }
+ };
- if event_detail.is_some() {
- // use timeout to avoid inifinite loop
- let resume = resume_watch.clone();
- let _ = set_timeout_with_handle(resume, Duration::ZERO);
- } else {
- resume_watch();
- }
- };
-
- let upd = update.clone();
- let update_from_custom_event =
- move |event: web_sys::CustomEvent| upd.clone()(Some(event.into()));
-
- let upd = update.clone();
- let update_from_storage_event =
- move |event: web_sys::StorageEvent| upd.clone()(Some(event.into()));
-
- if listen_to_storage_changes {
- let _ = use_event_listener(cx, window(), ev::storage, update_from_storage_event);
- let _ = use_event_listener(
+ let WatchPausableReturn {
+ pause: pause_watch,
+ resume: resume_watch,
+ ..
+ } = watch_pausable_with_options(
cx,
- window(),
- ev::Custom::new(CUSTOM_STORAGE_EVENT_NAME),
- update_from_custom_event,
+ move || data.get(),
+ move |data, _, _| write.clone()(data),
+ WatchOptions::default().filter(filter),
);
+
+ let k = key.to_string();
+ let store = storage.clone();
+
+ let update = move |event_detail: Option| {
+ if let Some(event_detail) = &event_detail {
+ if event_detail.storage_area != Some(store) {
+ return;
+ }
+
+ match &event_detail.key {
+ None => {
+ set_data.set(defaults.get_untracked());
+ return;
+ }
+ Some(event_key) => {
+ if event_key != &k {
+ return;
+ }
+ }
+ };
+ }
+
+ pause_watch();
+
+ if let Some(value) = read(event_detail.clone()) {
+ set_data.set(value);
+ }
+
+ if event_detail.is_some() {
+ // use timeout to avoid inifinite loop
+ let resume = resume_watch.clone();
+ let _ = set_timeout_with_handle(resume, Duration::ZERO);
+ } else {
+ resume_watch();
+ }
+ };
+
+ let upd = update.clone();
+ let update_from_custom_event =
+ move |event: web_sys::CustomEvent| upd.clone()(Some(event.into()));
+
+ let upd = update.clone();
+ let update_from_storage_event =
+ move |event: web_sys::StorageEvent| upd.clone()(Some(event.into()));
+
+ if listen_to_storage_changes {
+ let _ = use_event_listener(cx, window(), ev::storage, update_from_storage_event);
+ let _ = use_event_listener(
+ cx,
+ window(),
+ ev::Custom::new(CUSTOM_STORAGE_EVENT_NAME),
+ update_from_custom_event,
+ );
+ }
+
+ update(None);
+
+ let k = key.to_string();
+
+ Box::new(move || {
+ let _ = storage.remove_item(&k);
+ })
}
-
- update(None);
-
- let k = key.to_string();
-
- Box::new(move || {
- let _ = storage.remove_item(&k);
- })
- }
- Err(e) => {
- on_error(UseStorageError::NoStorage(e));
- Box::new(move || {})
- }
- _ => {
- // do nothing
- Box::new(move || {})
- }
- };
+ Err(e) => {
+ on_error(UseStorageError::NoStorage(e));
+ Box::new(move || {})
+ }
+ _ => {
+ // do nothing
+ Box::new(move || {})
+ }
+ };
+ }}
(data, set_data, move || remove.clone()())
}
diff --git a/src/use_active_element.rs b/src/use_active_element.rs
index d66d498..c60aabd 100644
--- a/src/use_active_element.rs
+++ b/src/use_active_element.rs
@@ -1,4 +1,7 @@
+#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))]
+
use crate::use_event_listener_with_options;
+use cfg_if::cfg_if;
use leptos::ev::{blur, focus};
use leptos::html::{AnyElement, ToHtmlElement};
use leptos::*;
@@ -27,42 +30,51 @@ use web_sys::AddEventListenerOptions;
/// # view! { cx, }
/// # }
/// ```
-
+///
+/// ## Server-Side Rendering
+///
+/// On the server this returns a `Signal` that always contains the value `None`.
pub fn use_active_element(cx: Scope) -> Signal