Improve the custom hooks chapter

This commit is contained in:
Evan Almloff committed 2023-04-24 11:16:53 -05:00
1 parent 73a2beb327
commit 61dba2825c
5 files changed
+217 -3

No files matched your search

@@ -0,0 +1,38 @@
#![allow(unused)]
use dioxus::prelude::*;
fn main() {}
// ANCHOR: non_clone_state
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
struct UseState<'a, T> {
value: &'a RefCell<T>,
update: Arc<dyn Fn()>,
}
fn my_use_state<T: 'static>(cx: &ScopeState, init: impl FnOnce() -> T) -> UseState<T> {
// The update function will trigger a re-render in the component cx is attached to
let update = cx.schedule_update();
// Create the initial state
let value = cx.use_hook(|| RefCell::new(init()));
UseState { value, update }
}
impl<T: Clone> UseState<'_, T> {
fn get(&self) -> T {
self.value.borrow().clone()
}
fn set(&self, value: T) {
// Update the state
*self.value.borrow_mut() = value;
// Trigger a re-render on the component the state is from
(self.update)();
}
}
// ANCHOR_END: non_clone_state
+54
View File
@@ -11,3 +11,57 @@ fn use_settings(cx: &ScopeState) -> &UseSharedState<AppSettings> {
use_shared_state::<AppSettings>(cx).expect("App settings not provided")
}
// ANCHOR_END: wrap_context
// ANCHOR: use_storage
use gloo_storage::{LocalStorage, Storage};
use serde::{de::DeserializeOwned, Serialize};
/// A persistent storage hook that can be used to store data across application reloads.
#[allow(clippy::needless_return)]
pub fn use_persistent<T: Serialize + DeserializeOwned + Default + 'static>(
cx: &ScopeState,
// A unique key for the storage entry
key: impl ToString,
// A function that returns the initial value if the storage entry is empty
init: impl FnOnce() -> T,
) -> &UsePersistent<T> {
// Use the use_ref hook to create a mutable state for the storage entry
let state = use_ref(cx, move || {
// This closure will run when the hook is created
let key = key.to_string();
let value = LocalStorage::get(key.as_str()).ok().unwrap_or_else(init);
StorageEntry { key, value }
});
// Wrap the state in a new struct with a custom API
// Note: We use use_hook here so that this hook is easier to use in closures in the rsx. Any values with the same lifetime as the ScopeState can be used in the closure without cloning.
cx.use_hook(|| UsePersistent {
inner: state.clone(),
})
}
struct StorageEntry<T> {
key: String,
value: T,
}
/// Storage that persists across application reloads
pub struct UsePersistent<T: 'static> {
inner: UseRef<StorageEntry<T>>,
}
impl<T: Serialize + DeserializeOwned + Clone + 'static> UsePersistent<T> {
/// Returns a reference to the value
pub fn get(&self) -> T {
self.inner.read().value.clone()
}
/// Sets the value
pub fn set(&self, value: T) {
let mut inner = self.inner.write();
// Write the new value to local storage
LocalStorage::set(inner.key.as_str(), &value);
inner.value = value;
}
}
// ANCHOR_END: use_storage
+57
View File
@@ -0,0 +1,57 @@
#![allow(unused)]
use dioxus::prelude::*;
fn main() {}
// ANCHOR: use_state
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Arc;
#[derive(Clone)]
struct UseState<T> {
value: Rc<RefCell<T>>,
update: Arc<dyn Fn()>,
}
fn my_use_state<T: 'static>(cx: &ScopeState, init: impl FnOnce() -> T) -> &UseState<T> {
cx.use_hook(|| {
// The update function will trigger a re-render in the component cx is attached to
let update = cx.schedule_update();
// Create the initial state
let value = Rc::new(RefCell::new(init()));
UseState { value, update }
})
}
impl<T: Clone> UseState<T> {
fn get(&self) -> T {
self.value.borrow().clone()
}
fn set(&self, value: T) {
// Update the state
*self.value.borrow_mut() = value;
// Trigger a re-render on the component the state is from
(self.update)();
}
}
// ANCHOR_END: use_state
// ANCHOR: use_context
pub fn use_context<T: 'static + Clone>(cx: &ScopeState) -> Option<&T> {
cx.use_hook(|| cx.consume_context::<T>()).as_ref()
}
pub fn use_context_provider<T: 'static + Clone>(cx: &ScopeState, f: impl FnOnce() -> T) -> &T {
cx.use_hook(|| {
let val = f();
// Provide the context state to the scope
cx.provide_context(val.clone());
val
})
}
// ANCHOR_END: use_context