mirror of
https://github.com/adoyle0/leptos-use.git
synced 2025-01-23 09:09:21 -05:00
Problem: use_storage value setter differs from existing API too much. Use WriteSignal + separate deleter fn
This commit is contained in:
parent
d56c5cf514
commit
1371b81b67
1 changed files with 67 additions and 41 deletions
|
@ -13,7 +13,7 @@ pub struct UseStorageOptions<T: 'static, C: Codec<T>> {
|
||||||
codec: C,
|
codec: C,
|
||||||
on_error: Rc<dyn Fn(UseStorageError<C::Error>)>,
|
on_error: Rc<dyn Fn(UseStorageError<C::Error>)>,
|
||||||
listen_to_storage_changes: bool,
|
listen_to_storage_changes: bool,
|
||||||
default_value: MaybeSignal<T>,
|
default_value: MaybeRwSignal<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Session handling errors returned by [`use_storage`].
|
/// Session handling errors returned by [`use_storage`].
|
||||||
|
@ -34,7 +34,9 @@ pub enum UseStorageError<Err> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Hook for using local storage. Returns a result of a signal and a setter / deleter.
|
/// Hook for using local storage. Returns a result of a signal and a setter / deleter.
|
||||||
pub fn use_local_storage<T, C>(key: impl AsRef<str>) -> (Memo<T>, impl Fn(Option<T>) -> () + Clone)
|
pub fn use_local_storage<T, C>(
|
||||||
|
key: impl AsRef<str>,
|
||||||
|
) -> (Signal<T>, WriteSignal<T>, impl Fn() -> () + Clone)
|
||||||
where
|
where
|
||||||
T: Clone + Default + PartialEq,
|
T: Clone + Default + PartialEq,
|
||||||
C: Codec<T> + Default,
|
C: Codec<T> + Default,
|
||||||
|
@ -49,7 +51,7 @@ where
|
||||||
pub fn use_local_storage_with_options<T, C>(
|
pub fn use_local_storage_with_options<T, C>(
|
||||||
key: impl AsRef<str>,
|
key: impl AsRef<str>,
|
||||||
options: UseStorageOptions<T, C>,
|
options: UseStorageOptions<T, C>,
|
||||||
) -> (Memo<T>, impl Fn(Option<T>) -> () + Clone)
|
) -> (Signal<T>, WriteSignal<T>, impl Fn() -> () + Clone)
|
||||||
where
|
where
|
||||||
T: Clone + PartialEq,
|
T: Clone + PartialEq,
|
||||||
C: Codec<T>,
|
C: Codec<T>,
|
||||||
|
@ -60,7 +62,7 @@ where
|
||||||
/// Hook for using session storage. Returns a result of a signal and a setter / deleter.
|
/// Hook for using session storage. Returns a result of a signal and a setter / deleter.
|
||||||
pub fn use_session_storage<T, C>(
|
pub fn use_session_storage<T, C>(
|
||||||
key: impl AsRef<str>,
|
key: impl AsRef<str>,
|
||||||
) -> (Memo<T>, impl Fn(Option<T>) -> () + Clone)
|
) -> (Signal<T>, WriteSignal<T>, impl Fn() -> () + Clone)
|
||||||
where
|
where
|
||||||
T: Clone + Default + PartialEq,
|
T: Clone + Default + PartialEq,
|
||||||
C: Codec<T> + Default,
|
C: Codec<T> + Default,
|
||||||
|
@ -75,7 +77,7 @@ where
|
||||||
pub fn use_session_storage_with_options<T, C>(
|
pub fn use_session_storage_with_options<T, C>(
|
||||||
key: impl AsRef<str>,
|
key: impl AsRef<str>,
|
||||||
options: UseStorageOptions<T, C>,
|
options: UseStorageOptions<T, C>,
|
||||||
) -> (Memo<T>, impl Fn(Option<T>) -> () + Clone)
|
) -> (Signal<T>, WriteSignal<T>, impl Fn() -> () + Clone)
|
||||||
where
|
where
|
||||||
T: Clone + PartialEq,
|
T: Clone + PartialEq,
|
||||||
C: Codec<T>,
|
C: Codec<T>,
|
||||||
|
@ -88,7 +90,7 @@ pub fn use_storage_with_options<T, C>(
|
||||||
storage_type: StorageType,
|
storage_type: StorageType,
|
||||||
key: impl AsRef<str>,
|
key: impl AsRef<str>,
|
||||||
options: UseStorageOptions<T, C>,
|
options: UseStorageOptions<T, C>,
|
||||||
) -> (Memo<T>, impl Fn(Option<T>) -> () + Clone)
|
) -> (Signal<T>, WriteSignal<T>, impl Fn() -> () + Clone)
|
||||||
where
|
where
|
||||||
T: Clone + PartialEq,
|
T: Clone + PartialEq,
|
||||||
C: Codec<T>,
|
C: Codec<T>,
|
||||||
|
@ -99,7 +101,7 @@ where
|
||||||
set_data.set(value);
|
set_data.set(value);
|
||||||
};
|
};
|
||||||
let value = create_memo(move |_| data.get().unwrap_or_default());
|
let value = create_memo(move |_| data.get().unwrap_or_default());
|
||||||
return (value, set_value);
|
return (value, set_value, || ());
|
||||||
} else {
|
} else {
|
||||||
// Continue
|
// Continue
|
||||||
}}
|
}}
|
||||||
|
@ -118,7 +120,7 @@ where
|
||||||
.and_then(|s| s.ok_or(UseStorageError::StorageReturnedNone));
|
.and_then(|s| s.ok_or(UseStorageError::StorageReturnedNone));
|
||||||
let storage = handle_error(&on_error, storage);
|
let storage = handle_error(&on_error, storage);
|
||||||
|
|
||||||
// Fetch initial value (undecoded)
|
// Fetch initial value
|
||||||
let initial_value = storage
|
let initial_value = storage
|
||||||
.to_owned()
|
.to_owned()
|
||||||
// Pull from storage
|
// Pull from storage
|
||||||
|
@ -129,71 +131,95 @@ where
|
||||||
handle_error(&on_error, result)
|
handle_error(&on_error, result)
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
// Decode initial value
|
|
||||||
let initial_value = decode_item(&codec, initial_value, &on_error);
|
let initial_value = decode_item(&codec, initial_value, &on_error);
|
||||||
|
|
||||||
let (data, set_data) = create_signal(initial_value);
|
// Data signal: use initial value or falls back to default value.
|
||||||
|
let (default_value, set_default_value) = default_value.into_signal();
|
||||||
|
let (data, set_data) = match initial_value {
|
||||||
|
Some(initial_value) => {
|
||||||
|
let (data, set_data) = create_signal(initial_value);
|
||||||
|
(data.into(), set_data)
|
||||||
|
}
|
||||||
|
None => (default_value, set_default_value),
|
||||||
|
};
|
||||||
|
|
||||||
// Update storage value
|
// If data is removed from browser storage, revert to default value
|
||||||
let set_value = {
|
let revert_data = move || {
|
||||||
|
set_data.set(default_value.get_untracked());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update storage value on change
|
||||||
|
{
|
||||||
let storage = storage.to_owned();
|
let storage = storage.to_owned();
|
||||||
let key = key.as_ref().to_owned();
|
|
||||||
let codec = codec.to_owned();
|
let codec = codec.to_owned();
|
||||||
|
let key = key.as_ref().to_owned();
|
||||||
let on_error = on_error.to_owned();
|
let on_error = on_error.to_owned();
|
||||||
move |value: Option<T>| {
|
let _ = watch(
|
||||||
let key = key.as_str();
|
move || data.get(),
|
||||||
// Attempt to update storage
|
move |value, _, _| {
|
||||||
let _ = storage.as_ref().map(|storage| {
|
let key = key.as_str();
|
||||||
let result = match value {
|
if let Ok(storage) = &storage {
|
||||||
// Update
|
let result = codec
|
||||||
Some(ref value) => codec
|
|
||||||
.encode(&value)
|
.encode(&value)
|
||||||
.map_err(UseStorageError::ItemCodecError)
|
.map_err(UseStorageError::ItemCodecError)
|
||||||
.and_then(|enc_value| {
|
.and_then(|enc_value| {
|
||||||
|
// Set storage -- this sends an event to other pages
|
||||||
storage
|
storage
|
||||||
.set_item(key, &enc_value)
|
.set_item(key, &enc_value)
|
||||||
.map_err(UseStorageError::SetItemFailed)
|
.map_err(UseStorageError::SetItemFailed)
|
||||||
}),
|
});
|
||||||
// Remove
|
let _ = handle_error(&on_error, result);
|
||||||
None => storage
|
}
|
||||||
.remove_item(key)
|
},
|
||||||
.map_err(UseStorageError::RemoveItemFailed),
|
false,
|
||||||
};
|
);
|
||||||
handle_error(&on_error, result)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Notify signal of change
|
|
||||||
set_data.set(value);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Listen for storage events
|
// Listen for storage events
|
||||||
// Note: we only receive events from other tabs / windows, not from internal updates.
|
// Note: we only receive events from other tabs / windows, not from internal updates.
|
||||||
if listen_to_storage_changes {
|
if listen_to_storage_changes {
|
||||||
let key = key.as_ref().to_owned();
|
let key = key.as_ref().to_owned();
|
||||||
|
let on_error = on_error.to_owned();
|
||||||
let _ = use_event_listener_with_options(
|
let _ = use_event_listener_with_options(
|
||||||
use_window(),
|
use_window(),
|
||||||
leptos::ev::storage,
|
leptos::ev::storage,
|
||||||
move |ev| {
|
move |ev| {
|
||||||
|
let mut deleted = false;
|
||||||
// Update storage value if our key matches
|
// Update storage value if our key matches
|
||||||
if let Some(k) = ev.key() {
|
if let Some(k) = ev.key() {
|
||||||
if k == key {
|
if k == key {
|
||||||
let value = decode_item(&codec, ev.new_value(), &on_error);
|
match decode_item(&codec, ev.new_value(), &on_error) {
|
||||||
set_data.set(value)
|
Some(value) => set_data.set(value),
|
||||||
|
None => deleted = true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// All keys deleted
|
// All keys deleted
|
||||||
set_data.set(None)
|
deleted = true;
|
||||||
|
}
|
||||||
|
if deleted {
|
||||||
|
revert_data();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
UseEventListenerOptions::default().passive(true),
|
UseEventListenerOptions::default().passive(true),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Apply default value
|
// Remove from storage fn
|
||||||
let value = create_memo(move |_| data.get().unwrap_or_else(|| default_value.get()));
|
let remove = {
|
||||||
|
let key = key.as_ref().to_owned();
|
||||||
|
move || {
|
||||||
|
let _ = storage.as_ref().map(|storage| {
|
||||||
|
let result = storage
|
||||||
|
.remove_item(key.as_ref())
|
||||||
|
.map_err(UseStorageError::RemoveItemFailed);
|
||||||
|
let _ = handle_error(&on_error, result);
|
||||||
|
revert_data();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
(value, set_value)
|
(data, set_data, remove)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calls the on_error callback with the given error. Removes the error from the Result to avoid double error handling.
|
/// Calls the on_error callback with the given error. Removes the error from the Result to avoid double error handling.
|
||||||
|
@ -224,7 +250,7 @@ impl<T: Default + 'static, C: Codec<T> + Default> Default for UseStorageOptions<
|
||||||
codec: C::default(),
|
codec: C::default(),
|
||||||
on_error: Rc::new(|_err| ()),
|
on_error: Rc::new(|_err| ()),
|
||||||
listen_to_storage_changes: true,
|
listen_to_storage_changes: true,
|
||||||
default_value: MaybeSignal::default(),
|
default_value: MaybeRwSignal::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -235,7 +261,7 @@ impl<T: Clone + Default, C: Codec<T>> UseStorageOptions<T, C> {
|
||||||
codec,
|
codec,
|
||||||
on_error: Rc::new(|_err| ()),
|
on_error: Rc::new(|_err| ()),
|
||||||
listen_to_storage_changes: true,
|
listen_to_storage_changes: true,
|
||||||
default_value: MaybeSignal::default(),
|
default_value: MaybeRwSignal::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,7 +281,7 @@ impl<T: Clone + Default, C: Codec<T>> UseStorageOptions<T, C> {
|
||||||
|
|
||||||
pub fn default_value(self, values: impl Into<MaybeRwSignal<T>>) -> Self {
|
pub fn default_value(self, values: impl Into<MaybeRwSignal<T>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
default_value: values.into().into_signal().0.into(),
|
default_value: values.into(),
|
||||||
..self
|
..self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue