Problem: use_storage value setter differs from existing API too much. Use WriteSignal + separate deleter fn

This commit is contained in:
Joshua McQuistan 2023-10-27 19:51:02 +01:00
parent d56c5cf514
commit 1371b81b67

View file

@ -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
} }
} }