فهرست منبع

Merge branch 'DioxusLabs:master' into cli-stuff

Miles Murgaw 1 سال پیش
والد
کامیت
bfbca2653f

+ 3 - 1
.github/workflows/macos.yml

@@ -33,4 +33,6 @@ jobs:
       - uses: dtolnay/rust-toolchain@stable
       - uses: Swatinem/rust-cache@v2
       - uses: actions/checkout@v3
-      - run: cargo test --all --tests
+      - run: |
+          cargo test --all --tests
+          cargo test --package fermi --release

+ 1 - 0
.github/workflows/windows.yml

@@ -85,4 +85,5 @@ jobs:
           set RUST_BACKTRACE=1
           cargo build --all --tests --examples
           cargo test --all --tests
+          cargo test --package fermi --release
         shell: cmd

+ 0 - 5
Cargo.toml

@@ -115,8 +115,3 @@ fern = { version = "0.6.0", features = ["colored"] }
 thiserror = "1.0.30"
 env_logger = "0.10.0"
 simple_logger = "4.0.0"
-
-[profile.release]
-opt-level = 3
-lto = true
-debug = true

+ 2 - 0
docs/guide/src/en/SUMMARY.md

@@ -21,10 +21,12 @@
   - [Hooks & Component State](interactivity/hooks.md)
   - [User Input](interactivity/user_input.md)
   - [Sharing State](interactivity/sharing_state.md)
+  - [Memoization](interactivity/memoization.md)
   - [Custom Hooks](interactivity/custom_hooks.md)
   - [Dynamic Rendering](interactivity/dynamic_rendering.md)
   - [Routing](interactivity/router.md)
 - [Async](async/index.md)
+  - [UseEffect](async/use_effect.md)
   - [UseFuture](async/use_future.md)
   - [UseCoroutine](async/use_coroutine.md)
   - [Spawning Futures](async/spawn.md)

+ 5 - 10
docs/guide/src/en/__unused/advanced-guides/12-signals.md

@@ -94,8 +94,10 @@ Calling `deref` or `deref_mut` is actually more complex than it seems. When a va
 
 Sometimes you want a signal to propagate across your app, either through far-away siblings or through deeply-nested components. In these cases, we use Dirac: Dioxus's first-class state management toolkit. Dirac atoms automatically implement the Signal API. This component will bind the input element to the `TITLE` atom.
 
+
 ```rust, no_run
-const TITLE: Atom<String> = || "".to_string();
+const TITLE: Atom<String> = Atom(|| "".to_string());
+
 const Provider: Component = |cx|{
     let title = use_signal(cx, &TITLE);
     render!(input { value: title })
@@ -131,7 +133,8 @@ By default, Dioxus is limited when you use iter/map. With the `For` component, y
 Dioxus automatically understands how to use your signals when mixed with iterators through `Deref`/`DerefMut`. This lets you efficiently map collections while avoiding the re-rendering of lists. In essence, signals act as a hint to Dioxus on how to avoid un-necessary checks and renders, making your app faster.
 
 ```rust, no_run
-const DICT: AtomFamily<String, String> = |_| {};
+const DICT: AtomFamily<String, String> = AtomFamily(|_| {});
+
 const List: Component = |cx|{
     let dict = use_signal(cx, &DICT);
     cx.render(rsx!(
@@ -142,14 +145,6 @@ const List: Component = |cx|{
 };
 ```
 
-## Remote Signals
-
-Apps that use signals will enjoy a pleasant hybrid of server-side and client-side rendering.
-
-```rust, no_run
-
-```
-
 ## How does it work?
 
 Signals internally use Dioxus' asynchronous rendering infrastructure to perform updates out of the tree.

+ 4 - 4
docs/guide/src/en/async/use_coroutine.md

@@ -143,7 +143,7 @@ async fn editor_service(rx: UnboundedReceiver<EditorCommand>) {
 We can combine coroutines with [Fermi](https://docs.rs/fermi/latest/fermi/index.html) to emulate Redux Toolkit's Thunk system with much less headache. This lets us store all of our app's state _within_ a task and then simply update the "view" values stored in Atoms. It cannot be understated how powerful this technique is: we get all the perks of native Rust tasks with the optimizations and ergonomics of global state. This means your _actual_ state does not need to be tied up in a system like Fermi or Redux – the only Atoms that need to exist are those that are used to drive the display/UI.
 
 ```rust, no_run
-static USERNAME: Atom<String> = |_| "default".to_string();
+static USERNAME: Atom<String> = Atom(|_| "default".to_string());
 
 fn app(cx: Scope) -> Element {
     let atoms = use_atom_root(cx);
@@ -156,7 +156,7 @@ fn app(cx: Scope) -> Element {
 }
 
 fn Banner(cx: Scope) -> Element {
-    let username = use_read(cx, USERNAME);
+    let username = use_read(cx, &USERNAME);
 
     cx.render(rsx!{
         h1 { "Welcome back, {username}" }
@@ -174,8 +174,8 @@ enum SyncAction {
 }
 
 async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
-    let username = atoms.write(USERNAME);
-    let errors = atoms.write(ERRORS);
+    let username = atoms.write(&USERNAME);
+    let errors = atoms.write(&ERRORS);
 
     while let Ok(msg) = rx.next().await {
         match msg {

+ 41 - 0
docs/guide/src/en/async/use_effect.md

@@ -0,0 +1,41 @@
+# UseEffect
+
+[`use_effect`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_effect.html) lets you run a callback that returns a future, which will be re-run when its [dependencies](#dependencies) change. This is useful to syncrhonize with external events.
+
+## Dependencies
+
+You can make the callback re-run when some value changes. For example, you might want to fetch a user's data only when the user id changes. You can provide a tuple of "dependencies" to the hook. It will automatically re-run it when any of those dependencies change.
+
+## Example
+
+```rust, no_run
+#[inline_props]
+fn Profile(cx: Scope, id: usize) -> Element {
+    let name = use_state(cx, || None);
+
+    // Only fetch the user data when the id changes.
+    use_effect(cx, (id,), |(id,)| {
+        to_owned![name];
+        async move {
+            let user = fetch_user(id).await;
+            name.set(user.name);
+        }
+    });
+
+    // Because the dependencies are empty, this will only run once.
+    // An empty tuple is always equal to an empty tuple.
+    use_effect(cx, (), |()| async move {
+        println!("Hello, World!");
+    });
+
+    let name = name.get().clone().unwrap_or("Loading...".to_string());
+
+    render!(
+        p { "{name}" }
+    )
+}
+
+fn app(cx: Scope) -> Element {
+    render!(Profile { id: 0 })
+}
+```

+ 3 - 3
docs/guide/src/en/best_practices/error_handling.md

@@ -113,14 +113,14 @@ enum InputError {
     TooShort,
 }
 
-static INPUT_ERROR: Atom<InputError> = |_| InputError::None;
+static INPUT_ERROR: Atom<InputError> = Atom(|_| InputError::None);
 ```
 
 Then, in our top level component, we want to explicitly handle the possible error state for this part of the tree.
 
 ```rust, no_run
 fn TopLevel(cx: Scope) -> Element {
-    let error = use_read(cx, INPUT_ERROR);
+    let error = use_read(cx, &INPUT_ERROR);
 
     match error {
         TooLong => return cx.render(rsx!{ "FAILED: Too long!" }),
@@ -134,7 +134,7 @@ Now, whenever a downstream component has an error in its actions, it can simply
 
 ```rust, no_run
 fn Commandline(cx: Scope) -> Element {
-    let set_error = use_set(cx, INPUT_ERROR);
+    let set_error = use_set(cx, &INPUT_ERROR);
 
     cx.render(rsx!{
         input {

+ 19 - 0
docs/guide/src/en/interactivity/memoization.md

@@ -0,0 +1,19 @@
+# Memoization
+
+[`use_memo`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_memo.html) let's you memorize values and thus save computation time. This is useful for expensive calculations.
+
+```rust, no_run
+#[inline_props]
+fn Calculator(cx: Scope, number: usize) -> Element {
+    let bigger_number = use_memo(cx, (number,), |(number,)| {
+        // This will only be calculated when `number` has changed.
+        number * 100
+    });
+    render!(
+        p { "{bigger_number}" }
+    )
+}
+fn app(cx: Scope) -> Element {
+    render!(Calculator { number: 0 })
+}
+```

+ 3 - 3
docs/guide/src/en/interactivity/sharing_state.md

@@ -32,7 +32,7 @@ Finally, a third component will render the other two as children. It will be res
 
 ![Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: "me waiting for a language feature"](./images/meme_editor_screenshot.png)
 
-## Using Context
+## Using Shared State
 
 Sometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient.
 
@@ -42,7 +42,7 @@ Suppose now that we want to implement a dark mode toggle for our app. To achieve
 
 Now, we could write another `use_state` in the top component, and pass `is_dark_mode` down to every component through props. But think about what will happen as the app grows in complexity – almost every component that renders any CSS is going to need to know if dark mode is enabled or not – so they'll all need the same dark mode prop. And every parent component will need to pass it down to them. Imagine how messy and verbose that would get, especially if we had components several levels deep!
 
-Dioxus offers a better solution than this "prop drilling" – providing context. The [`use_context_provider`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html) hook is similar to `use_ref`, but it makes it available through [`use_context`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html) for all children components.
+Dioxus offers a better solution than this "prop drilling" – providing context. The [`use_shared_state_provider`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_shared_state_provider.html) hook is similar to `use_ref`, but it makes it available through [`use_shared_state`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_shared_state.html) for all children components.
 
 First, we have to create a struct for our dark mode configuration:
 
@@ -62,7 +62,7 @@ As a result, any child component of `App` (direct or not), can access the `DarkM
 {{#include ../../../examples/meme_editor_dark_mode.rs:use_context}}
 ```
 
-> `use_context` returns `Option<UseSharedState<DarkMode>>` here. If the context has been provided, the value is `Some(UseSharedState<DarkMode>)`, which you can call `.read` or `.write` on, similarly to `UseRef`. Otherwise, the value is `None`.
+> `use_shared_state` returns `Option<UseSharedState<DarkMode>>` here. If the context has been provided, the value is `Some(UseSharedState<DarkMode>)`, which you can call `.read` or `.write` on, similarly to `UseRef`. Otherwise, the value is `None`.
 
 For example, here's how we would implement the dark mode toggle, which both reads the context (to determine what color it should render) and writes to it (to toggle dark mode):
 

+ 4 - 4
docs/guide/src/pt-br/async/use_coroutine.md

@@ -105,7 +105,7 @@ async fn editor_service(rx: UnboundedReceiver<EditorCommand>) {
 Podemos combinar corrotinas com `Fermi` para emular o sistema `Thunk` do **Redux Toolkit** com muito menos dor de cabeça. Isso nos permite armazenar todo o estado do nosso aplicativo _dentro_ de uma tarefa e, em seguida, simplesmente atualizar os valores de "visualização" armazenados em `Atoms`. Não pode ser subestimado o quão poderosa é essa técnica: temos todas as vantagens das tarefas nativas do Rust com as otimizações e ergonomia do estado global. Isso significa que seu estado _real_ não precisa estar vinculado a um sistema como `Fermi` ou `Redux` – os únicos `Atoms` que precisam existir são aqueles que são usados para controlar a interface.
 
 ```rust, no_run
-static USERNAME: Atom<String> = |_| "default".to_string();
+static USERNAME: Atom<String> = Atom(|_| "default".to_string());
 
 fn app(cx: Scope) -> Element {
     let atoms = use_atom_root(cx);
@@ -118,7 +118,7 @@ fn app(cx: Scope) -> Element {
 }
 
 fn Banner(cx: Scope) -> Element {
-    let username = use_read(cx, USERNAME);
+    let username = use_read(cx, &USERNAME);
 
     cx.render(rsx!{
         h1 { "Welcome back, {username}" }
@@ -134,8 +134,8 @@ enum SyncAction {
 }
 
 async fn sync_service(mut rx: UnboundedReceiver<SyncAction>, atoms: AtomRoot) {
-    let username = atoms.write(USERNAME);
-    let errors = atoms.write(ERRORS);
+    let username = atoms.write(&USERNAME);
+    let errors = atoms.write(&ERRORS);
 
     while let Ok(msg) = rx.next().await {
         match msg {

+ 3 - 3
docs/guide/src/pt-br/best_practices/error_handling.md

@@ -113,14 +113,14 @@ enum InputError {
     TooShort,
 }
 
-static INPUT_ERROR: Atom<InputError> = |_| InputError::None;
+static INPUT_ERROR: Atom<InputError> = Atom(|_| InputError::None);
 ```
 
 Então, em nosso componente de nível superior, queremos tratar explicitamente o possível estado de erro para esta parte da árvore.
 
 ```rust, no_run
 fn TopLevel(cx: Scope) -> Element {
-    let error = use_read(cx, INPUT_ERROR);
+    let error = use_read(cx, &INPUT_ERROR);
 
     match error {
         TooLong => return cx.render(rsx!{ "FAILED: Too long!" }),
@@ -134,7 +134,7 @@ Agora, sempre que um componente _downstream_ tiver um erro em suas ações, ele
 
 ```rust, no_run
 fn Commandline(cx: Scope) -> Element {
-    let set_error = use_set(cx, INPUT_ERROR);
+    let set_error = use_set(cx, &INPUT_ERROR);
 
     cx.render(rsx!{
         input {

+ 5 - 5
examples/fermi.rs

@@ -7,11 +7,11 @@ fn main() {
     dioxus_desktop::launch(app)
 }
 
-static NAME: Atom<String> = |_| "world".to_string();
+static NAME: Atom<String> = Atom(|_| "world".to_string());
 
 fn app(cx: Scope) -> Element {
     use_init_atom_root(cx);
-    let name = use_read(cx, NAME);
+    let name = use_read(cx, &NAME);
 
     cx.render(rsx! {
         div { "hello {name}!" }
@@ -21,7 +21,7 @@ fn app(cx: Scope) -> Element {
 }
 
 fn Child(cx: Scope) -> Element {
-    let set_name = use_set(cx, NAME);
+    let set_name = use_set(cx, &NAME);
 
     cx.render(rsx! {
         button {
@@ -31,10 +31,10 @@ fn Child(cx: Scope) -> Element {
     })
 }
 
-static NAMES: AtomRef<Vec<String>> = |_| vec!["world".to_string()];
+static NAMES: AtomRef<Vec<String>> = AtomRef(|_| vec!["world".to_string()]);
 
 fn ChildWithRef(cx: Scope) -> Element {
-    let names = use_atom_ref(cx, NAMES);
+    let names = use_atom_ref(cx, &NAMES);
 
     cx.render(rsx! {
         div {

+ 77 - 0
examples/shared_state.rs

@@ -0,0 +1,77 @@
+#![allow(non_snake_case)]
+
+use std::collections::HashMap;
+
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus_desktop::launch(App);
+}
+
+#[derive(Default)]
+struct CoolData {
+    data: HashMap<usize, String>,
+}
+
+impl CoolData {
+    pub fn new(data: HashMap<usize, String>) -> Self {
+        Self { data }
+    }
+
+    pub fn view(&self, id: &usize) -> Option<&String> {
+        self.data.get(id)
+    }
+
+    pub fn set(&mut self, id: usize, data: String) {
+        self.data.insert(id, data);
+    }
+}
+
+#[rustfmt::skip]
+pub fn App(cx: Scope) -> Element {
+    use_shared_state_provider(cx, || CoolData::new(HashMap::from([
+        (0, "Hello, World!".to_string()),
+        (1, "Dioxus is amazing!".to_string())
+    ])));
+
+    render!(
+        DataEditor {
+            id: 0
+        }
+        DataEditor {
+            id: 1
+        }
+        DataView {
+            id: 0
+        }
+        DataView {
+            id: 1
+        }
+    )
+}
+
+#[inline_props]
+fn DataEditor(cx: Scope, id: usize) -> Element {
+    let cool_data = use_shared_state::<CoolData>(cx).unwrap().read();
+
+    let my_data = &cool_data.view(id).unwrap();
+
+    render!(p {
+        "{my_data}"
+    })
+}
+
+#[inline_props]
+fn DataView(cx: Scope, id: usize) -> Element {
+    let cool_data = use_shared_state::<CoolData>(cx).unwrap();
+
+    let oninput = |e: FormEvent| cool_data.write().set(*id, e.value.clone());
+
+    let cool_data = cool_data.read();
+    let my_data = &cool_data.view(id).unwrap();
+
+    render!(input {
+        oninput: oninput,
+        value: "{my_data}"
+    })
+}

+ 10 - 10
packages/fermi/README.md

@@ -1,4 +1,3 @@
-
 <div align="center">
   <h1>Fermi ⚛</h1>
   <p>
@@ -6,7 +5,6 @@
   </p>
 </div>
 
-
 <div align="center">
   <!-- Crates version -->
   <a href="https://crates.io/crates/fermi">
@@ -30,21 +28,21 @@
   </a>
 </div>
 
------
+---
 
 Fermi is a global state management solution for Dioxus that's as easy as `use_state`.
 
 Inspired by atom-based state management solutions, all state in Fermi starts as an `atom`:
 
 ```rust, ignore
-static NAME: Atom<&str> = |_| "Dioxus";
+static NAME: Atom<&str> = Atom(|_| "Dioxus");
 ```
 
 From anywhere in our app, we can read the value of our atom:
 
-```rust, ignores
+```rust, ignore
 fn NameCard(cx: Scope) -> Element {
-    let name = use_read(cx, NAME);
+    let name = use_read(cx, &NAME);
     cx.render(rsx!{ h1 { "Hello, {name}"} })
 }
 ```
@@ -53,7 +51,7 @@ We can also set the value of our atom, also from anywhere in our app:
 
 ```rust, ignore
 fn NameCard(cx: Scope) -> Element {
-    let set_name = use_set(cx, NAME);
+    let set_name = use_set(cx, &NAME);
     cx.render(rsx!{
         button {
             onclick: move |_| set_name("Fermi"),
@@ -66,10 +64,10 @@ fn NameCard(cx: Scope) -> Element {
 If needed, we can update the atom's value, based on itself:
 
 ```rust, ignore
-static COUNT: Atom<i32> = |_| 0;
+static COUNT: Atom<i32> = Atom(|_| 0);
 
 fn Counter(cx: Scope) -> Element {
-    let mut count = use_atom_state(cx, COUNT);
+    let mut count = use_atom_state(cx, &COUNT);
 
     cx.render(rsx!{
         p {
@@ -86,6 +84,7 @@ fn Counter(cx: Scope) -> Element {
 It's that simple!
 
 ## Installation
+
 Fermi is currently under construction, so you have to use the `master` branch to get started.
 
 ```toml
@@ -93,10 +92,10 @@ Fermi is currently under construction, so you have to use the `master` branch to
 fermi = { git = "https://github.com/dioxuslabs/dioxus" }
 ```
 
-
 ## Running examples
 
 The examples here use Dioxus Desktop to showcase their functionality. To run an example, use
+
 ```sh
 $ cargo run --example fermi
 ```
@@ -104,6 +103,7 @@ $ cargo run --example fermi
 ## Features
 
 Broadly our feature set required to be released includes:
+
 - [x] Support for Atoms
 - [x] Support for AtomRef (for values that aren't `Clone`)
 - [ ] Support for Atom Families

+ 23 - 10
packages/fermi/src/atoms/atom.rs

@@ -1,24 +1,21 @@
 use crate::{AtomId, AtomRoot, Readable, Writable};
 
-pub type Atom<T> = fn(AtomBuilder) -> T;
+pub struct Atom<T>(pub fn(AtomBuilder) -> T);
 pub struct AtomBuilder;
 
-impl<V: 'static> Readable<V> for Atom<V> {
+impl<V> Readable<V> for &'static Atom<V> {
     fn read(&self, _root: AtomRoot) -> Option<V> {
         todo!()
     }
     fn init(&self) -> V {
-        (*self)(AtomBuilder)
+        self.0(AtomBuilder)
     }
     fn unique_id(&self) -> AtomId {
-        AtomId {
-            ptr: *self as *const (),
-            type_id: std::any::TypeId::of::<V>(),
-        }
+        *self as *const Atom<V> as *const ()
     }
 }
 
-impl<V: 'static> Writable<V> for Atom<V> {
+impl<V> Writable<V> for &'static Atom<V> {
     fn write(&self, _root: AtomRoot, _value: V) {
         todo!()
     }
@@ -26,6 +23,22 @@ impl<V: 'static> Writable<V> for Atom<V> {
 
 #[test]
 fn atom_compiles() {
-    static TEST_ATOM: Atom<&str> = |_| "hello";
-    dbg!(TEST_ATOM.init());
+    static TEST_ATOM: Atom<&str> = Atom(|_| "hello");
+    dbg!((&TEST_ATOM).init());
+}
+
+#[test]
+fn atom_is_unique() {
+    static TEST_ATOM_1: Atom<&str> = Atom(|_| "hello");
+    static TEST_ATOM_2: Atom<&str> = Atom(|_| "hello");
+    assert_eq!((&TEST_ATOM_1).unique_id(), (&TEST_ATOM_1).unique_id());
+    assert_ne!((&TEST_ATOM_1).unique_id(), (&TEST_ATOM_2).unique_id());
+}
+
+#[test]
+fn atom_is_unique_2() {
+    struct S(String);
+    static TEST_ATOM_1: Atom<Vec<S>> = Atom(|_| Vec::new());
+    static TEST_ATOM_2: Atom<Vec<String>> = Atom(|_| Vec::new());
+    assert_ne!((&TEST_ATOM_1).unique_id(), (&TEST_ATOM_2).unique_id());
 }

+ 5 - 8
packages/fermi/src/atoms/atomfamily.rs

@@ -2,26 +2,23 @@ use crate::{AtomId, AtomRoot, Readable, Writable};
 use im_rc::HashMap as ImMap;
 
 pub struct AtomFamilyBuilder;
-pub type AtomFamily<K, V> = fn(AtomFamilyBuilder) -> ImMap<K, V>;
+pub struct AtomFamily<K, V>(pub fn(AtomFamilyBuilder) -> ImMap<K, V>);
 
-impl<K, V: 'static> Readable<ImMap<K, V>> for AtomFamily<K, V> {
+impl<K, V> Readable<ImMap<K, V>> for &'static AtomFamily<K, V> {
     fn read(&self, _root: AtomRoot) -> Option<ImMap<K, V>> {
         todo!()
     }
 
     fn init(&self) -> ImMap<K, V> {
-        (*self)(AtomFamilyBuilder)
+        self.0(AtomFamilyBuilder)
     }
 
     fn unique_id(&self) -> AtomId {
-        AtomId {
-            ptr: *self as *const (),
-            type_id: std::any::TypeId::of::<V>(),
-        }
+        *self as *const AtomFamily<K, V> as *const ()
     }
 }
 
-impl<K, V: 'static> Writable<ImMap<K, V>> for AtomFamily<K, V> {
+impl<K, V> Writable<ImMap<K, V>> for &'static AtomFamily<K, V> {
     fn write(&self, _root: AtomRoot, _value: ImMap<K, V>) {
         todo!()
     }

+ 6 - 9
packages/fermi/src/atoms/atomref.rs

@@ -2,27 +2,24 @@ use crate::{AtomId, AtomRoot, Readable};
 use std::cell::RefCell;
 
 pub struct AtomRefBuilder;
-pub type AtomRef<T> = fn(AtomRefBuilder) -> T;
+pub struct AtomRef<T>(pub fn(AtomRefBuilder) -> T);
 
-impl<V: 'static> Readable<RefCell<V>> for AtomRef<V> {
+impl<V> Readable<RefCell<V>> for &'static AtomRef<V> {
     fn read(&self, _root: AtomRoot) -> Option<RefCell<V>> {
         todo!()
     }
 
     fn init(&self) -> RefCell<V> {
-        RefCell::new((*self)(AtomRefBuilder))
+        RefCell::new(self.0(AtomRefBuilder))
     }
 
     fn unique_id(&self) -> AtomId {
-        AtomId {
-            ptr: *self as *const (),
-            type_id: std::any::TypeId::of::<V>(),
-        }
+        *self as *const AtomRef<V> as *const ()
     }
 }
 
 #[test]
 fn atom_compiles() {
-    static TEST_ATOM: AtomRef<Vec<String>> = |_| vec![];
-    dbg!(TEST_ATOM.init());
+    static TEST_ATOM: AtomRef<Vec<String>> = AtomRef(|_| vec![]);
+    dbg!((&TEST_ATOM).init());
 }

+ 4 - 1
packages/fermi/src/hooks/atom_ref.rs

@@ -13,7 +13,10 @@ use std::{
 ///
 ///
 ///
-pub fn use_atom_ref<T: 'static>(cx: &ScopeState, atom: AtomRef<T>) -> &UseAtomRef<T> {
+pub fn use_atom_ref<'a, T: 'static>(
+    cx: &'a ScopeState,
+    atom: &'static AtomRef<T>,
+) -> &'a UseAtomRef<T> {
     let root = use_atom_root(cx);
 
     &cx.use_hook(|| {

+ 1 - 1
packages/fermi/src/hooks/state.rs

@@ -19,7 +19,7 @@ use std::{
 /// static COUNT: Atom<u32> = |_| 0;
 ///
 /// fn Example(cx: Scope) -> Element {
-///     let mut count = use_atom_state(cx, COUNT);
+///     let mut count = use_atom_state(cx, &COUNT);
 ///
 ///     cx.render(rsx! {
 ///         div {

+ 3 - 21
packages/fermi/src/root.rs

@@ -1,21 +1,11 @@
-use std::{
-    any::{Any, TypeId},
-    cell::RefCell,
-    collections::HashMap,
-    rc::Rc,
-    sync::Arc,
-};
+use std::{any::Any, cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};
 
 use dioxus_core::ScopeId;
 use im_rc::HashSet;
 
 use crate::Readable;
 
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct AtomId {
-    pub ptr: *const (),
-    pub type_id: TypeId,
-}
+pub type AtomId = *const ();
 
 pub struct AtomRoot {
     pub atoms: RefCell<HashMap<AtomId, Slot>>,
@@ -54,15 +44,7 @@ impl AtomRoot {
         // initialize the value if it's not already initialized
         if let Some(slot) = atoms.get_mut(&f.unique_id()) {
             slot.subscribers.insert(scope);
-            match slot.value.clone().downcast() {
-                Ok(res) => res,
-                Err(e) => panic!(
-                    "Downcasting atom failed: {:?}. Has typeid of {:?} but needs typeid of {:?}",
-                    f.unique_id(),
-                    e.type_id(),
-                    TypeId::of::<V>()
-                ),
-            }
+            slot.value.clone().downcast().unwrap()
         } else {
             let value = Rc::new(f.init());
             let mut subscribers = HashSet::new();

+ 23 - 7
packages/hooks/src/useeffect.rs

@@ -9,17 +9,33 @@ use crate::UseFutureDep;
 /// If a future is pending when the dependencies change, the previous future
 /// will be allowed to continue
 ///
-/// - dependencies: a tuple of references to values that are PartialEq + Clone
+/// - dependencies: a tuple of references to values that are `PartialEq` + `Clone`
 ///
 /// ## Examples
 ///
-/// ```rust, ignore
-///
+/// ```rust, no_run
 /// #[inline_props]
-/// fn app(cx: Scope, name: &str) -> Element {
-///     use_effect(cx, (name,), |(name,)| async move {
-///         set_title(name);
-///     }))
+/// fn Profile(cx: Scope, id: usize) -> Element {
+///     let name = use_state(cx, || None);
+///
+///     // Only fetch the user data when the id changes.
+///     use_effect(cx, (id,), |(id,)| {
+///         to_owned![name];
+///         async move {
+///             let user = fetch_user(id).await;
+///             name.set(user.name);
+///         }
+///     });
+///
+///     let name = name.get().clone().unwrap_or("Loading...".to_string());
+///
+///     render!(
+///         p { "{name}" }
+///     )
+/// }
+///
+/// fn app(cx: Scope) -> Element {
+///     render!(Profile { id: 0 })
 /// }
 /// ```
 pub fn use_effect<T, F, D>(cx: &ScopeState, dependencies: D, future: impl FnOnce(D::Out) -> F)

+ 15 - 9
packages/hooks/src/usememo.rs

@@ -2,21 +2,27 @@ use dioxus_core::ScopeState;
 
 use crate::UseFutureDep;
 
-/// A hook that provides a callback that executes after the hooks have been applied
+/// A hook that provides a callback that executes if the dependencies change.
+/// This is useful to avoid running computation-expensive calculations even when the data doesn't change.
 ///
-/// Whenever the hooks dependencies change, the callback will be re-evaluated.
-///
-/// - dependencies: a tuple of references to values that are PartialEq + Clone
+/// - dependencies: a tuple of references to values that are `PartialEq` + `Clone`
 ///
 /// ## Examples
 ///
-/// ```rust, ignore
+/// ```rust, no_run
 ///
 /// #[inline_props]
-/// fn app(cx: Scope, name: &str) -> Element {
-///     use_memo(cx, (name,), |(name,)| {
-///         expensive_computation(name);
-///     }))
+/// fn Calculator(cx: Scope, number: usize) -> Element {
+///     let bigger_number = use_memo(cx, (number,), |(number,)| {
+///         // This will only be calculated when `number` has changed.
+///         number * 100
+///     });
+///     render!(
+///         p { "{bigger_number}" }
+///     )
+/// }
+/// fn app(cx: Scope) -> Element {
+///     render!(Calculator { number: 0 })
 /// }
 /// ```
 pub fn use_memo<T, D>(cx: &ScopeState, dependencies: D, callback: impl FnOnce(D::Out) -> T) -> &T