mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-22 12:13:04 +00:00
Improve inline docs (#2460)
Improve inline docs * improve incorrect event handler return error message * Improve event handler docs * document the eval functions * document spawn and common spawn errors * fix event handler docs * add notes about how you use attributes and elements in rsx * add doc aliases for attributes and events we rename * add some more aliases for common search terms * don't doc ignore any public examples in core * don't ignore public doc examples in ssr * don't ignore examples in the dioxus package readme * add a warning when you launch without a renderer enabled * fix some outdated element docs * add a bunch of examples to resource * add notes about desktop events * add more docs for use_resource * add on_unimplemented hint to Dependency * fix some unresolved links * add examples to each of the router traits * add not implemented errors for router traits * add an example to the routable trait * expand rsx macro docs * improve memo docs * update the dioxus readme * mention dioxus crate features in the docs * fix a bunch of doc tests * fix html doc tests * fix router doc tests * fix dioxus signals doc tests * fix dioxus ssr doc tests * fix use_future example in the hooks cheat sheet * add a javascript alias for eval * fix hook explanation values * remove unused embed-doc-image dependency
This commit is contained in:
parent
054351139f
commit
0127501dbf
71 changed files with 4132 additions and 1162 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -2077,6 +2077,7 @@ name = "dioxus"
|
|||
version = "0.5.2"
|
||||
dependencies = [
|
||||
"criterion 0.3.6",
|
||||
"dioxus",
|
||||
"dioxus-config-macro",
|
||||
"dioxus-core 0.5.2",
|
||||
"dioxus-core-macro",
|
||||
|
@ -2249,12 +2250,14 @@ dependencies = [
|
|||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"rustc-hash",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-fluent-assertions",
|
||||
"tracing-subscriber",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2403,6 +2406,8 @@ dependencies = [
|
|||
"futures-channel",
|
||||
"futures-util",
|
||||
"generational-box 0.5.2",
|
||||
"reqwest",
|
||||
"rustversion",
|
||||
"slab",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
|
@ -2437,15 +2442,18 @@ name = "dioxus-html"
|
|||
version = "0.5.2"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"dioxus",
|
||||
"dioxus-core 0.5.2",
|
||||
"dioxus-html-internal-macro",
|
||||
"dioxus-rsx",
|
||||
"dioxus-web",
|
||||
"enumset",
|
||||
"euclid",
|
||||
"futures-channel",
|
||||
"generational-box 0.5.2",
|
||||
"keyboard-types",
|
||||
"rfd",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde-value",
|
||||
"serde_json",
|
||||
|
@ -2590,6 +2598,7 @@ dependencies = [
|
|||
"gloo-utils 0.1.7",
|
||||
"http 1.1.0",
|
||||
"js-sys",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
|
@ -2638,6 +2647,8 @@ dependencies = [
|
|||
"generational-box 0.5.2",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"rustc-hash",
|
||||
"serde",
|
||||
"simple_logger",
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// Information about a hook call or function.
|
||||
pub struct HookInfo {
|
||||
/// The name of the hook, e.g. `use_state`.
|
||||
/// The name of the hook, e.g. `use_signal`.
|
||||
pub name: String,
|
||||
/// The span of the hook, e.g. `use_signal(|| 0)`.
|
||||
/// The span of the hook, e.g. `use_signal`.
|
||||
pub span: Span,
|
||||
/// The span of the name, e.g. `use_state`.
|
||||
/// The span of the name, e.g. `use_signal`.
|
||||
pub name_span: Span,
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,191 @@ pub fn derive_typed_builder(input: TokenStream) -> TokenStream {
|
|||
}
|
||||
|
||||
/// The rsx! macro makes it easy for developers to write jsx-style markup in their components.
|
||||
///
|
||||
/// ## Elements
|
||||
///
|
||||
/// You can render elements with rsx! with the element name and then braces surrounding the attributes and children.
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// rsx! {
|
||||
/// div {
|
||||
/// div {}
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// <details>
|
||||
/// <summary>Web Components</summary>
|
||||
///
|
||||
///
|
||||
/// Dioxus will automatically render any elements with `-` as a untyped web component:
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// rsx! {
|
||||
/// div-component {
|
||||
/// div {}
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// You can wrap your web component in a custom component to add type checking:
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[component]
|
||||
/// fn MyDivComponent(width: i64) -> Element {
|
||||
/// rsx! {
|
||||
/// div-component {
|
||||
/// "width": width
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// </details>
|
||||
///
|
||||
/// ## Attributes
|
||||
///
|
||||
/// You can add attributes to any element inside the braces. Attributes are key-value pairs separated by a colon.
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// let width = 100;
|
||||
/// rsx! {
|
||||
/// div {
|
||||
/// // Set the class attribute to "my-class"
|
||||
/// class: "my-class",
|
||||
/// // attribute strings are automatically formatted with the format macro
|
||||
/// width: "{width}px",
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// ### Optional Attributes
|
||||
///
|
||||
/// You can include optional attributes with an unterminated if statement as the value of the attribute:
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # let first_boolean = true;
|
||||
/// # let second_boolean = false;
|
||||
/// rsx! {
|
||||
/// div {
|
||||
/// // Set the class attribute to "my-class" if true
|
||||
/// class: if first_boolean {
|
||||
/// "my-class"
|
||||
/// },
|
||||
/// // Set the class attribute to "my-other-class" if false
|
||||
/// class: if second_boolean {
|
||||
/// "my-other-class"
|
||||
/// }
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// ### Raw Attributes
|
||||
///
|
||||
/// Dioxus defaults to attributes that are type checked as html. If you want to include an attribute that is not included in the html spec, you can use the `raw` attribute surrounded by quotes:
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// rsx! {
|
||||
/// div {
|
||||
/// // Set the data-count attribute to "1"
|
||||
/// "data-count": "1"
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// ## Text
|
||||
///
|
||||
/// You can include text in your markup as a string literal:
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// let name = "World";
|
||||
/// rsx! {
|
||||
/// div {
|
||||
/// "Hello World"
|
||||
/// // Just like attributes, you can included formatted segments inside your text
|
||||
/// "Hello {name}"
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// ## Components
|
||||
///
|
||||
/// You can render any [`macro@crate::component`]s you created inside your markup just like elements. Components must either start with a capital letter or contain a `_` character.
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[component]
|
||||
/// fn HelloWorld() -> Element {
|
||||
/// rsx! { "hello world!" }
|
||||
/// }
|
||||
///
|
||||
/// rsx! {
|
||||
/// div {
|
||||
/// HelloWorld {}
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// ## If statements
|
||||
///
|
||||
/// You can use if statements to conditionally render children. The body of the for if statement is parsed as rsx markup:
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// let first_boolean = true;
|
||||
/// let second_boolean = false;
|
||||
/// rsx! {
|
||||
/// if first_boolean {
|
||||
/// div {
|
||||
/// "first"
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// if second_boolean {
|
||||
/// "second"
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// ## For loops
|
||||
///
|
||||
/// You can also use for loops to iterate over a collection of items. The body of the for loop is parsed as rsx markup:
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// let numbers = vec![1, 2, 3];
|
||||
/// rsx! {
|
||||
/// for number in numbers {
|
||||
/// div {
|
||||
/// "{number}"
|
||||
/// }
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// ## Raw Expressions
|
||||
///
|
||||
/// You can include raw expressions inside your markup inside curly braces. Your expression must implement the [`IntoDynNode`](https://docs.rs/dioxus-core/latest/dioxus_core/trait.IntoDynNode.html) trait:
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// let name = "World";
|
||||
/// rsx! {
|
||||
/// div {
|
||||
/// // Text can be converted into a dynamic node in rsx
|
||||
/// {name}
|
||||
/// }
|
||||
/// // Iterators can also be converted into dynamic nodes
|
||||
/// {(0..10).map(|n| n * n).map(|number| rsx! { div { "{number}" } })}
|
||||
/// };
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn rsx(tokens: TokenStream) -> TokenStream {
|
||||
match syn::parse::<rsx::CallBody>(tokens) {
|
||||
|
@ -40,8 +225,6 @@ pub fn rsx(tokens: TokenStream) -> TokenStream {
|
|||
}
|
||||
|
||||
/// The rsx! macro makes it easy for developers to write jsx-style markup in their components.
|
||||
///
|
||||
/// The render macro automatically renders rsx - making it unhygienic.
|
||||
#[deprecated(note = "Use `rsx!` instead.")]
|
||||
#[proc_macro]
|
||||
pub fn render(tokens: TokenStream) -> TokenStream {
|
||||
|
@ -71,7 +254,8 @@ pub fn render(tokens: TokenStream) -> TokenStream {
|
|||
///
|
||||
/// # Examples
|
||||
/// * Without props:
|
||||
/// ```rust,ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[component]
|
||||
/// fn GreetBob() -> Element {
|
||||
/// rsx! { "hello, bob" }
|
||||
|
@ -79,7 +263,8 @@ pub fn render(tokens: TokenStream) -> TokenStream {
|
|||
/// ```
|
||||
///
|
||||
/// * With props:
|
||||
/// ```rust,ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[component]
|
||||
/// fn GreetBob(bob: String) -> Element {
|
||||
/// rsx! { "hello, {bob}" }
|
||||
|
@ -101,21 +286,25 @@ pub fn component(_args: TokenStream, input: TokenStream) -> TokenStream {
|
|||
/// you would be repeating a lot of the usual Rust boilerplate.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust,ignore
|
||||
/// ```rust,no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[inline_props]
|
||||
/// fn app(bob: String) -> Element {
|
||||
/// rsx! { "hello, {bob}") }
|
||||
/// fn GreetBob(bob: String) -> Element {
|
||||
/// rsx! { "hello, {bob}" }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// // is equivalent to
|
||||
/// is equivalent to
|
||||
///
|
||||
/// #[derive(PartialEq, Props)]
|
||||
/// ```rust,no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[derive(PartialEq, Props, Clone)]
|
||||
/// struct AppProps {
|
||||
/// bob: String,
|
||||
/// }
|
||||
///
|
||||
/// fn app(props: AppProps) -> Element {
|
||||
/// rsx! { "hello, {bob}") }
|
||||
/// fn GreetBob(props: AppProps) -> Element {
|
||||
/// rsx! { "hello, {props.bob}" }
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
//! However, it has been adopted to fit the Dioxus Props builder pattern.
|
||||
//!
|
||||
//! For Dioxus, we make a few changes:
|
||||
//! - [x] Automatically implement Into<Option> on the setters (IE the strip setter option)
|
||||
//! - [x] Automatically implement a default of none for optional fields (those explicitly wrapped with Option<T>)
|
||||
//! - [x] Automatically implement [`Into<Option>`] on the setters (IE the strip setter option)
|
||||
//! - [x] Automatically implement a default of none for optional fields (those explicitly wrapped with [`Option<T>`])
|
||||
|
||||
use proc_macro2::TokenStream;
|
||||
|
||||
|
@ -532,6 +532,7 @@ mod struct_info {
|
|||
pub builder_attr: TypeBuilderAttr,
|
||||
pub builder_name: syn::Ident,
|
||||
pub conversion_helper_trait_name: syn::Ident,
|
||||
#[allow(unused)]
|
||||
pub core: syn::Ident,
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ tracing = { workspace = true }
|
|||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
tracing-subscriber = "0.3.18"
|
||||
generational-box = { workspace = true }
|
||||
rustversion = "1.0.17"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
|
@ -32,6 +33,13 @@ rand = "0.8.5"
|
|||
dioxus-ssr = { workspace = true }
|
||||
reqwest = { workspace = true}
|
||||
|
||||
[dev-dependencies.web-sys]
|
||||
version = "0.3.56"
|
||||
features = [
|
||||
"Document",
|
||||
"HtmlElement",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
serialize = ["serde"]
|
||||
|
|
|
@ -2,27 +2,30 @@
|
|||
|
||||
`dioxus-core` provides a fast and featureful VirtualDom implementation for Rust.
|
||||
|
||||
```rust, ignore
|
||||
```rust, no_run
|
||||
# tokio::runtime::Runtime::new().unwrap().block_on(async {
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_core::*;
|
||||
|
||||
let vdom = VirtualDom::new(app);
|
||||
let mut vdom = VirtualDom::new(app);
|
||||
let real_dom = SomeRenderer::new();
|
||||
|
||||
loop {
|
||||
select! {
|
||||
evt = real_dom.event() => vdom.handle_event(evt),
|
||||
tokio::select! {
|
||||
evt = real_dom.event() => vdom.handle_event("onclick", evt, ElementId(0), true),
|
||||
_ = vdom.wait_for_work() => {}
|
||||
}
|
||||
vdom.render(&mut real_dom)
|
||||
vdom.render_immediate(&mut real_dom.apply())
|
||||
}
|
||||
|
||||
# fn app() -> Element { None }
|
||||
# struct SomeRenderer; impl SomeRenderer { fn new() -> SomeRenderer { SomeRenderer; } async fn event() -> () { unimplemented!() } }
|
||||
# struct SomeRenderer; impl SomeRenderer { fn new() -> SomeRenderer { SomeRenderer } async fn event(&self) -> std::rc::Rc<dyn std::any::Any> { unimplemented!() } fn apply(&self) -> Mutations { Mutations::default() } }
|
||||
# });
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
A virtualdom is an efficient and flexible tree datastructure that allows you to manage state for a graphical user interface. The Dioxus VirtualDom is perhaps the most fully-featured virtualdom implementation in Rust and powers renderers running across Web, Desktop, Mobile, SSR, TUI, LiveView, and more. When you use the Dioxus VirtualDom, you immediately enable users of your renderer to leverage the wide ecosystem of Dioxus components, hooks, and associated tooling.
|
||||
A virtualdom is an efficient and flexible tree data structure that allows you to manage state for a graphical user interface. The Dioxus VirtualDom is perhaps the most fully-featured virtualdom implementation in Rust and powers renderers running across Web, Desktop, Mobile, SSR, TUI, LiveView, and more. When you use the Dioxus VirtualDom, you immediately enable users of your renderer to leverage the wide ecosystem of Dioxus components, hooks, and associated tooling.
|
||||
|
||||
Some features of `dioxus-core` include:
|
||||
|
||||
|
@ -40,6 +43,7 @@ If you are just starting, check out the Guides first.
|
|||
`dioxus-core` is designed to be a lightweight crate that. It exposes a number of flexible primitives without being deeply concerned about the intracices of state management itself. We proivde a number of useful abstractions built on these primitives in the `dioxus-hooks` crate as well as the `dioxus-signals` crate.
|
||||
|
||||
The important abstractions to understand are:
|
||||
|
||||
- The [`VirtualDom`]
|
||||
- The [`Component`] and its [`Properties`]
|
||||
- Handling events
|
||||
|
@ -73,17 +77,17 @@ fn main() {
|
|||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
- Check out the website [section on contributing](https://dioxuslabs.com/learn/0.5/contributing).
|
||||
- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).
|
||||
- [Join](https://discord.gg/XgGxMSkvUM) the discord and ask questions!
|
||||
|
||||
|
||||
<a href="https://github.com/dioxuslabs/dioxus/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=dioxuslabs/dioxus&max=30&columns=10" />
|
||||
</a>
|
||||
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT license].
|
||||
|
||||
[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT
|
||||
|
|
126
packages/core/docs/common_spawn_errors.md
Normal file
126
packages/core/docs/common_spawn_errors.md
Normal file
|
@ -0,0 +1,126 @@
|
|||
## Compiler errors you may run into while using spawn
|
||||
|
||||
<details>
|
||||
<summary>async block may outlive the current function, but it borrows `value`, which is owned by the current function</summary>
|
||||
|
||||
Tasks in Dioxus need only access data that can last for the entire lifetime of the application. That generally means data that is moved into the async block. **If you get this error, you may have forgotten to add `move` to your async block.**
|
||||
|
||||
Broken component:
|
||||
|
||||
```rust, compile_fail
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn App() -> Element {
|
||||
let signal = use_signal(|| 0);
|
||||
|
||||
use_hook(move || {
|
||||
// ❌ The task may run at any point and reads the value of the signal, but the signal is dropped at the end of the function
|
||||
spawn(async {
|
||||
println!("{}", signal());
|
||||
})
|
||||
});
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
Fixed component:
|
||||
|
||||
```rust, no_run
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn App() -> Element {
|
||||
let signal = use_signal(|| 0);
|
||||
|
||||
use_hook(move || {
|
||||
// ✅ The `move` keyword tells rust it can move the `state` signal into the async block. Since the async block owns the signal state, it can read it even after the function returns
|
||||
spawn(async move {
|
||||
println!("{}", signal());
|
||||
})
|
||||
});
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>use of moved value: `value`. move occurs because `value` has type `YourType`, which does not implement the `Copy` trait</summary>
|
||||
|
||||
Data in rust has a single owner. If you run into this error, you have likely tried to move data that isn't `Copy` into two different async tasks. **You can fix this issue by making your data `Copy` or calling `clone` on it before you move it into the async block.**
|
||||
|
||||
Broken component:
|
||||
|
||||
```rust, compile_fail
|
||||
# use dioxus::prelude::*;
|
||||
// `MyComponent` accepts a string which cannot be copied implicitly
|
||||
#[component]
|
||||
fn MyComponent(string: String) -> Element {
|
||||
use_hook(move || {
|
||||
// ❌ We are moving the string into the async task which means we can't access it elsewhere
|
||||
spawn(async move {
|
||||
println!("{}", string);
|
||||
});
|
||||
// ❌ Since we already moved the string, we can't move it into our new task. This will cause a compiler error
|
||||
spawn(async move {
|
||||
println!("{}", string);
|
||||
})
|
||||
});
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
You can fix this issue by either:
|
||||
|
||||
- Making your data `Copy` with `ReadOnlySignal`:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
// `MyComponent` accepts `ReadOnlySignal<String>` which implements `Copy`
|
||||
#[component]
|
||||
fn MyComponent(string: ReadOnlySignal<String>) -> Element {
|
||||
use_hook(move || {
|
||||
// ✅ Because the `string` signal is `Copy`, we can copy it into the async task while still having access to it elsewhere
|
||||
spawn(async move {
|
||||
println!("{}", string);
|
||||
});
|
||||
// ✅ Since `string` is `Copy`, we can copy it into another async task
|
||||
spawn(async move {
|
||||
println!("{}", string);
|
||||
})
|
||||
});
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
- Calling `clone` on your data before you move it into the closure:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
// `MyComponent` accepts a string which doesn't implement `Copy`
|
||||
#[component]
|
||||
fn MyComponent(string: String) -> Element {
|
||||
use_hook(move || {
|
||||
// ✅ The string only has one owner. We could move it into this closure, but since we want to use the string in other closures later, we will clone it instead
|
||||
spawn({
|
||||
// Clone the string in a new block
|
||||
let string = string.clone();
|
||||
// Then move the cloned string into the async block
|
||||
async move {
|
||||
println!("{}", string);
|
||||
}
|
||||
});
|
||||
// ✅ We don't use the string after this closure, so we can just move it into the closure directly
|
||||
spawn(async move {
|
||||
println!("{}", string);
|
||||
})
|
||||
});
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
|
@ -450,27 +450,35 @@ impl VNode {
|
|||
/// This is mostly implemented to help solve the issue where the same component is rendered under two different
|
||||
/// conditions:
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # let enabled = true;
|
||||
/// # #[component]
|
||||
/// # fn Component(enabled_sign: String) -> Element { todo!() }
|
||||
/// if enabled {
|
||||
/// rsx!{ Component { enabled_sign: "abc" } }
|
||||
/// } else {
|
||||
/// rsx!{ Component { enabled_sign: "xyz" } }
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// However, we should not that it's explicit in the docs that this is not a guarantee. If you need to preserve state,
|
||||
/// then you should be passing in separate props instead.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # #[component]
|
||||
/// # fn Component(enabled_sign: String) -> Element { todo!() }
|
||||
/// # let enabled = true;
|
||||
/// let props = if enabled {
|
||||
/// ComponentProps { enabled_sign: "abc" }
|
||||
/// ComponentProps { enabled_sign: "abc".to_string() }
|
||||
/// } else {
|
||||
/// ComponentProps { enabled_sign: "xyz" }
|
||||
/// ComponentProps { enabled_sign: "xyz".to_string() }
|
||||
/// };
|
||||
///
|
||||
/// rsx! {
|
||||
/// Component { ..props }
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub(crate) fn light_diff_templates(
|
||||
&self,
|
||||
|
@ -702,16 +710,19 @@ impl VNode {
|
|||
|
||||
/// Load all of the placeholder nodes for descendents of this root node
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # let some_text = "hello world";
|
||||
/// # let some_value = "123";
|
||||
/// rsx! {
|
||||
/// div {
|
||||
/// // This is a placeholder
|
||||
/// some_value,
|
||||
/// {some_value}
|
||||
///
|
||||
/// // Load this too
|
||||
/// "{some_text}"
|
||||
/// }
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
#[allow(unused)]
|
||||
fn load_placeholders(
|
||||
|
|
|
@ -153,13 +153,15 @@ impl ErrorBoundary {
|
|||
///
|
||||
/// The call stack is saved for this component and provided to the error boundary
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// #[component]
|
||||
/// fn app(count: String) -> Element {
|
||||
/// let id: i32 = count.parse().throw()?;
|
||||
/// let count: i32 = count.parse().throw()?;
|
||||
///
|
||||
/// rsx! {
|
||||
/// div { "Count {}" }
|
||||
/// div { "Count {count}" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
@ -178,13 +180,15 @@ pub trait Throw<S = ()>: Sized {
|
|||
/// which is what this trait shells out to.
|
||||
///
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// #[component]
|
||||
/// fn app( count: String) -> Element {
|
||||
/// let id: i32 = count.parse().throw()?;
|
||||
/// let count: i32 = count.parse().throw()?;
|
||||
///
|
||||
/// rsx! {
|
||||
/// div { "Count {}" }
|
||||
/// div { "Count {count}" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
@ -201,13 +205,15 @@ pub trait Throw<S = ()>: Sized {
|
|||
/// which is what this trait shells out to.
|
||||
///
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// #[component]
|
||||
/// fn app( count: String) -> Element {
|
||||
/// let id: i32 = count.parse().throw()?;
|
||||
/// let count: i32 = count.parse().throw()?;
|
||||
///
|
||||
/// rsx! {
|
||||
/// div { "Count {}" }
|
||||
/// div { "Count {count}" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
@ -447,13 +453,15 @@ impl<
|
|||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// rsx!{
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # fn ThrowsError() -> Element { todo!() }
|
||||
/// rsx! {
|
||||
/// ErrorBoundary {
|
||||
/// handle_error: |error| rsx! { "Oops, we encountered an error. Please report {error} to the developer of this application" }
|
||||
/// handle_error: |error| rsx! { "Oops, we encountered an error. Please report {error} to the developer of this application" },
|
||||
/// ThrowsError {}
|
||||
/// }
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// ## Usage
|
||||
|
|
|
@ -12,15 +12,15 @@ use std::{
|
|||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |evt: Event<MouseData>| {
|
||||
/// evt.cancel_bubble();
|
||||
///
|
||||
/// }
|
||||
/// evt.stop_propagation();
|
||||
/// }
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub struct Event<T: 'static + ?Sized> {
|
||||
/// The data associated with this event
|
||||
|
@ -42,15 +42,16 @@ impl<T> Event<T> {
|
|||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |evt: Event<FormData>| {
|
||||
/// let data = evt.map(|data| data.value());
|
||||
/// assert_eq!(data.inner(), "hello world");
|
||||
/// }
|
||||
/// onclick: move |evt: MouseEvent| {
|
||||
/// let data = evt.map(|data| data.client_coordinates());
|
||||
/// println!("{:?}", data.data());
|
||||
/// }
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub fn map<U: 'static, F: FnOnce(&T) -> U>(&self, f: F) -> Event<U> {
|
||||
Event {
|
||||
|
@ -63,14 +64,16 @@ impl<T> Event<T> {
|
|||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |evt: Event<MouseData>| {
|
||||
/// # #[allow(deprecated)]
|
||||
/// evt.cancel_bubble();
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
#[deprecated = "use stop_propagation instead"]
|
||||
pub fn cancel_bubble(&self) {
|
||||
|
@ -81,14 +84,15 @@ impl<T> Event<T> {
|
|||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |evt: Event<MouseData>| {
|
||||
/// evt.stop_propagation();
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub fn stop_propagation(&self) {
|
||||
self.propagates.set(false);
|
||||
|
@ -96,17 +100,18 @@ impl<T> Event<T> {
|
|||
|
||||
/// Get a reference to the inner data from this event
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |evt: Event<MouseData>| {
|
||||
/// let data = evt.inner.clone();
|
||||
/// cx.spawn(async move {
|
||||
/// let data = evt.data();
|
||||
/// async move {
|
||||
/// println!("{:?}", data);
|
||||
/// });
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub fn data(&self) -> Rc<T> {
|
||||
self.data.clone()
|
||||
|
@ -145,12 +150,13 @@ impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
|
|||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// rsx!{
|
||||
/// MyComponent { onclick: move |evt| tracing::debug!("clicked") }
|
||||
/// }
|
||||
/// };
|
||||
///
|
||||
/// #[derive(Props)]
|
||||
/// #[derive(Props, Clone, PartialEq)]
|
||||
/// struct MyProps {
|
||||
/// onclick: EventHandler<MouseEvent>,
|
||||
/// }
|
||||
|
@ -162,26 +168,28 @@ impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
|
|||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// ```
|
||||
pub struct EventHandler<T = ()> {
|
||||
pub(crate) origin: ScopeId,
|
||||
// During diffing components with EventHandler, we move the EventHandler over in place instead of rerunning the child component.
|
||||
// ```rust
|
||||
// #[component]
|
||||
// fn Child(onclick: EventHandler<MouseEvent>) -> Element {
|
||||
// rsx!{
|
||||
// button {
|
||||
// // Diffing Child will not rerun this component, it will just update the EventHandler in place so that if this callback is called, it will run the latest version of the callback
|
||||
// onclick: move |evt| cx.onclick.call(evt),
|
||||
// }
|
||||
// }
|
||||
/// During diffing components with EventHandler, we move the EventHandler over in place instead of rerunning the child component.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[component]
|
||||
/// fn Child(onclick: EventHandler<MouseEvent>) -> Element {
|
||||
/// rsx!{
|
||||
/// button {
|
||||
/// // Diffing Child will not rerun this component, it will just update the EventHandler in place so that if this callback is called, it will run the latest version of the callback
|
||||
/// onclick: move |evt| onclick(evt),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
// This is both more efficient and allows us to avoid out of date EventHandlers.
|
||||
//
|
||||
// We double box here because we want the data to be copy (GenerationalBox) and still update in place (ExternalListenerCallback)
|
||||
// This isn't an ideal solution for performance, but it is non-breaking and fixes the issues described in https://github.com/DioxusLabs/dioxus/pull/2298
|
||||
///
|
||||
/// This is both more efficient and allows us to avoid out of date EventHandlers.
|
||||
///
|
||||
/// We double box here because we want the data to be copy (GenerationalBox) and still update in place (ExternalListenerCallback)
|
||||
/// This isn't an ideal solution for performance, but it is non-breaking and fixes the issues described in <https://github.com/DioxusLabs/dioxus/pull/2298>
|
||||
pub(super) callback: GenerationalBox<Option<ExternalListenerCallback<T>>>,
|
||||
}
|
||||
|
||||
|
|
|
@ -11,10 +11,12 @@ use crate::innerlude::*;
|
|||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// let value = 1;
|
||||
/// rsx!{
|
||||
/// Fragment { key: "abc" }
|
||||
/// }
|
||||
/// Fragment { key: "{value}" }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// ## Usage
|
||||
|
@ -61,28 +63,25 @@ impl<const A: bool> FragmentBuilder<A> {
|
|||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn app() -> Element {
|
||||
/// rsx!{
|
||||
/// CustomCard {
|
||||
/// h1 {}
|
||||
/// p {}
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[derive(PartialEq, Props)]
|
||||
/// struct CardProps {
|
||||
/// children: Element
|
||||
/// }
|
||||
///
|
||||
/// fn CustomCard(cx: CardProps) -> Element {
|
||||
/// #[component]
|
||||
/// fn CustomCard(children: Element) -> Element {
|
||||
/// rsx!{
|
||||
/// div {
|
||||
/// h1 {"Title card"}
|
||||
/// {cx.children}
|
||||
/// {children}
|
||||
/// }
|
||||
/// }
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
impl Properties for FragmentProps {
|
||||
|
|
|
@ -81,16 +81,39 @@ pub fn suspend(task: Task) -> Element {
|
|||
/// }
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
#[doc = include_str!("../docs/common_spawn_errors.md")]
|
||||
pub fn spawn_isomorphic(fut: impl Future<Output = ()> + 'static) -> Task {
|
||||
Runtime::with_current_scope(|cx| cx.spawn_isomorphic(fut)).expect("to be in a dioxus runtime")
|
||||
}
|
||||
|
||||
/// Spawns the future but does not return the [`TaskId`]
|
||||
/// Spawns the future but does not return the [`Task`]. This task will automatically be canceled when the component is dropped.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// fn App() -> Element {
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |_| {
|
||||
/// spawn(async move {
|
||||
/// tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
/// println!("Hello World");
|
||||
/// });
|
||||
/// },
|
||||
/// "Print hello in one second"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
#[doc = include_str!("../docs/common_spawn_errors.md")]
|
||||
pub fn spawn(fut: impl Future<Output = ()> + 'static) -> Task {
|
||||
Runtime::with_current_scope(|cx| cx.spawn(fut)).expect("to be in a dioxus runtime")
|
||||
}
|
||||
|
||||
/// Queue an effect to run after the next render
|
||||
/// Queue an effect to run after the next render. You generally shouldn't need to interact with this function directly. [use_effect](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_effect.html) will call this function for you.
|
||||
pub fn queue_effect(f: impl FnOnce() + 'static) {
|
||||
Runtime::with_current_scope(|cx| cx.queue_effect(f)).expect("to be in a dioxus runtime")
|
||||
}
|
||||
|
@ -100,6 +123,53 @@ pub fn queue_effect(f: impl FnOnce() + 'static) {
|
|||
/// This is good for tasks that need to be run after the component has been dropped.
|
||||
///
|
||||
/// **This will run the task in the root scope. Any calls to global methods inside the future (including `context`) will be run in the root scope.**
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// // The parent component can create and destroy children dynamically
|
||||
/// fn App() -> Element {
|
||||
/// let mut count = use_signal(|| 0);
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |_| count += 1,
|
||||
/// "Increment"
|
||||
/// }
|
||||
/// button {
|
||||
/// onclick: move |_| count -= 1,
|
||||
/// "Decrement"
|
||||
/// }
|
||||
///
|
||||
/// for id in 0..10 {
|
||||
/// Child { id }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Child(id: i32) -> Element {
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |_| {
|
||||
/// // This will spawn a task in the root scope that will run forever
|
||||
/// // It will keep running even if you drop the child component by decreasing the count
|
||||
/// spawn_forever(async move {
|
||||
/// loop {
|
||||
/// tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
/// println!("Running task spawned in child component {id}");
|
||||
/// }
|
||||
/// });
|
||||
/// },
|
||||
/// "Spawn background task"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
#[doc = include_str!("../docs/common_spawn_errors.md")]
|
||||
pub fn spawn_forever(fut: impl Future<Output = ()> + 'static) -> Option<Task> {
|
||||
Runtime::with_scope(ScopeId::ROOT, |cx| cx.spawn(fut))
|
||||
}
|
||||
|
@ -203,25 +273,23 @@ pub fn schedule_update_any() -> Arc<dyn Fn(ScopeId) + Send + Sync> {
|
|||
/// (created with [`use_effect`](crate::use_effect)).
|
||||
///
|
||||
/// Example:
|
||||
/// ```rust, ignore
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// fn app() -> Element {
|
||||
/// let state = use_signal(|| true);
|
||||
/// let mut state = use_signal(|| true);
|
||||
/// rsx! {
|
||||
/// for _ in 0..100 {
|
||||
/// h1 {
|
||||
/// "spacer"
|
||||
/// }
|
||||
/// }
|
||||
/// if **state {
|
||||
/// rsx! {
|
||||
/// if state() {
|
||||
/// child_component {}
|
||||
/// }
|
||||
/// }
|
||||
/// button {
|
||||
/// onclick: move |_| {
|
||||
/// state.set(!*state.get());
|
||||
/// state.toggle()
|
||||
/// },
|
||||
/// "Unmount element"
|
||||
/// }
|
||||
|
@ -229,9 +297,9 @@ pub fn schedule_update_any() -> Arc<dyn Fn(ScopeId) + Send + Sync> {
|
|||
/// }
|
||||
///
|
||||
/// fn child_component() -> Element {
|
||||
/// let original_scroll_position = use_signal(|| 0.0);
|
||||
/// let mut original_scroll_position = use_signal(|| 0.0);
|
||||
///
|
||||
/// use_effect(move |_| async move {
|
||||
/// use_effect(move || {
|
||||
/// let window = web_sys::window().unwrap();
|
||||
/// let document = window.document().unwrap();
|
||||
/// let element = document.get_element_by_id("my_element").unwrap();
|
||||
|
@ -242,7 +310,7 @@ pub fn schedule_update_any() -> Arc<dyn Fn(ScopeId) + Send + Sync> {
|
|||
/// use_drop(move || {
|
||||
/// /// restore scroll to the top of the page
|
||||
/// let window = web_sys::window().unwrap();
|
||||
/// window.scroll_with_x_and_y(*original_scroll_position.current(), 0.0);
|
||||
/// window.scroll_with_x_and_y(original_scroll_position(), 0.0);
|
||||
/// });
|
||||
///
|
||||
/// rsx!{
|
||||
|
@ -253,6 +321,7 @@ pub fn schedule_update_any() -> Arc<dyn Fn(ScopeId) + Send + Sync> {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[doc(alias = "use_on_unmount")]
|
||||
pub fn use_drop<D: FnOnce() + 'static>(destroy: D) {
|
||||
struct LifeCycle<D: FnOnce()> {
|
||||
/// Wrap the closure in an option so that we can take it out on drop.
|
||||
|
|
|
@ -42,7 +42,7 @@ pub(crate) mod innerlude {
|
|||
pub use crate::tasks::*;
|
||||
pub use crate::virtual_dom::*;
|
||||
|
||||
/// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
|
||||
/// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`ScopeId`] or [`ScopeState`].
|
||||
///
|
||||
/// An Errored [`Element`] will propagate the error to the nearest error boundary.
|
||||
pub type Element = Option<VNode>;
|
||||
|
|
|
@ -101,7 +101,13 @@ pub struct VNodeInner {
|
|||
/// The inner list *must* be in the format [static named attributes, remaining dynamically named attributes].
|
||||
///
|
||||
/// For example:
|
||||
/// ```rust, ignore
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// let class = "my-class";
|
||||
/// let attrs = vec![];
|
||||
/// let color = "red";
|
||||
///
|
||||
/// rsx! {
|
||||
/// div {
|
||||
/// class: "{class}",
|
||||
/// ..attrs,
|
||||
|
@ -109,10 +115,11 @@ pub struct VNodeInner {
|
|||
/// color: "{color}",
|
||||
/// }
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// Would be represented as:
|
||||
/// ```rust, ignore
|
||||
/// ```text
|
||||
/// [
|
||||
/// [class, every attribute in attrs sorted by name], // Slot 0 in the template
|
||||
/// [color], // Slot 1 in the template
|
||||
|
|
|
@ -4,25 +4,47 @@ use crate::innerlude::*;
|
|||
|
||||
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
|
||||
/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
|
||||
/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
|
||||
/// derive macro to implement the `Properties` trait automatically.
|
||||
///
|
||||
/// If your props are 'static, then Dioxus will require that they also be PartialEq for the derived memoize strategy.
|
||||
///
|
||||
/// By default, the memoization strategy is very conservative, but can be tuned to be more aggressive manually. However,
|
||||
/// this is only safe if the props are 'static - otherwise you might borrow references after-free.
|
||||
///
|
||||
/// We strongly suggest that any changes to memoization be done at the "PartialEq" level for 'static props. Additionally,
|
||||
/// we advise the use of smart pointers in cases where memoization is important.
|
||||
/// Dioxus requires your props to be 'static, `Clone`, and `PartialEq`. We use the `PartialEq` trait to determine if
|
||||
/// the props have changed when we diff the component.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// For props that are 'static:
|
||||
/// ```rust, ignore
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[derive(Props, PartialEq, Clone)]
|
||||
/// struct MyProps {
|
||||
/// struct MyComponentProps {
|
||||
/// data: String
|
||||
/// }
|
||||
///
|
||||
/// fn MyComponent(props: MyComponentProps) -> Element {
|
||||
/// rsx! {
|
||||
/// div { "Hello {props.data}" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Or even better, derive your entire props struct with the [`#[crate::component]`] macro:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[component]
|
||||
/// fn MyComponent(data: String) -> Element {
|
||||
/// rsx! {
|
||||
/// div { "Hello {data}" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[rustversion::attr(
|
||||
since(1.78.0),
|
||||
diagnostic::on_unimplemented(
|
||||
message = "`Props` is not implemented for `{Self}`",
|
||||
label = "Props",
|
||||
note = "Props is a trait that is automatically implemented for all structs that can be used as props for a component",
|
||||
note = "If you manually created a new properties struct, you may have forgotten to add `#[derive(Props, PartialEq, Clone)]` to your struct",
|
||||
)
|
||||
)]
|
||||
pub trait Properties: Clone + Sized + 'static {
|
||||
/// The type of the builder for this component.
|
||||
/// Used to create "in-progress" versions of the props.
|
||||
|
@ -96,6 +118,28 @@ where
|
|||
}
|
||||
|
||||
/// Any component that implements the `ComponentFn` trait can be used as a component.
|
||||
///
|
||||
/// This trait is automatically implemented for functions that are in one of the following forms:
|
||||
/// - `fn() -> Element`
|
||||
/// - `fn(props: Properties) -> Element`
|
||||
///
|
||||
/// You can derive it automatically for any function with arguments that implement PartialEq with the `#[component]` attribute:
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[component]
|
||||
/// fn MyComponent(a: u32, b: u32) -> Element {
|
||||
/// rsx! { "a: {a}, b: {b}" }
|
||||
/// }
|
||||
/// ```
|
||||
#[rustversion::attr(
|
||||
since(1.78.0),
|
||||
diagnostic::on_unimplemented(
|
||||
message = "`Component<{Props}>` is not implemented for `{Self}`",
|
||||
label = "Component",
|
||||
note = "Components are functions in the form `fn() -> Element`, `fn(props: Properties) -> Element`, or `#[component] fn(partial_eq1: u32, partial_eq2: u32) -> Element`.",
|
||||
note = "You may have forgotten to add `#[component]` to your function to automatically implement the `ComponentFunction` trait."
|
||||
)
|
||||
)]
|
||||
pub trait ComponentFunction<Props, Marker = ()>: Clone + 'static {
|
||||
/// Get the type id of the component.
|
||||
fn id(&self) -> TypeId {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//!
|
||||
//! ## Goals
|
||||
//! We try to prevent three different situations:
|
||||
//! 1. Running queued work after it could be dropped. Related issues (https://github.com/DioxusLabs/dioxus/pull/1993)
|
||||
//! 1. Running queued work after it could be dropped. Related issues (<https://github.com/DioxusLabs/dioxus/pull/1993>)
|
||||
//!
|
||||
//! User code often assumes that this property is true. For example, if this code reruns the child component after signal is changed to None, it will panic
|
||||
//! ```rust, ignore
|
||||
|
@ -23,7 +23,7 @@
|
|||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! 2. Running effects before the dom is updated. Related issues (https://github.com/DioxusLabs/dioxus/issues/2307)
|
||||
//! 2. Running effects before the dom is updated. Related issues (<https://github.com/DioxusLabs/dioxus/issues/2307>)
|
||||
//!
|
||||
//! Effects can be used to run code that accesses the DOM directly. They should only run when the DOM is in an updated state. If they are run with an out of date version of the DOM, unexpected behavior can occur.
|
||||
//! ```rust, ignore
|
||||
|
@ -39,8 +39,9 @@
|
|||
//! div { id: "{id}" }
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! 3. Observing out of date state. Related issues (https://github.com/DioxusLabs/dioxus/issues/1935)
|
||||
//! 3. Observing out of date state. Related issues (<https://github.com/DioxusLabs/dioxus/issues/1935>)
|
||||
//!
|
||||
//! Where ever possible, updates should happen in an order that makes it impossible to observe an out of date state.
|
||||
//! ```rust, ignore
|
||||
|
|
|
@ -173,16 +173,20 @@ impl Scope {
|
|||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[derive(Clone)]
|
||||
/// struct SharedState(&'static str);
|
||||
///
|
||||
/// static app: Component = |cx| {
|
||||
/// cx.use_hook(|| cx.provide_context(SharedState("world")));
|
||||
/// // The parent provides context that is available in all children
|
||||
/// fn app() -> Element {
|
||||
/// use_hook(|| provide_context(SharedState("world")));
|
||||
/// rsx!(Child {})
|
||||
/// }
|
||||
///
|
||||
/// static Child: Component = |cx| {
|
||||
/// let state = cx.consume_state::<SharedState>();
|
||||
/// // Any child elements can access the context with the `consume_context` function
|
||||
/// fn Child() -> Element {
|
||||
/// let state = use_context::<SharedState>();
|
||||
/// rsx!(div { "hello {state.0}" })
|
||||
/// }
|
||||
/// ```
|
||||
|
@ -258,7 +262,7 @@ impl Scope {
|
|||
id
|
||||
}
|
||||
|
||||
/// Spawns the future but does not return the [`TaskId`]
|
||||
/// Spawns the future and returns the [`Task`]
|
||||
pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) -> Task {
|
||||
let id = Runtime::with(|rt| rt.spawn(self.id, fut)).expect("Runtime to exist");
|
||||
self.spawned_tasks.borrow_mut().insert(id);
|
||||
|
@ -410,7 +414,7 @@ impl ScopeId {
|
|||
Runtime::with_scope(self, |cx| cx.spawn(fut))
|
||||
}
|
||||
|
||||
/// Spawns the future but does not return the [`TaskId`]
|
||||
/// Spawns the future but does not return the [`Task`]
|
||||
pub fn spawn(self, fut: impl Future<Output = ()> + 'static) {
|
||||
Runtime::with_scope(self, |cx| cx.spawn(fut));
|
||||
}
|
||||
|
@ -434,7 +438,7 @@ impl ScopeId {
|
|||
|
||||
/// Create a subscription that schedules a future render for the reference component. Unlike [`Self::needs_update`], this function will work outside of the dioxus runtime.
|
||||
///
|
||||
/// ## Notice: you should prefer using [`dioxus_core::schedule_update_any`] and [`Self::scope_id`]
|
||||
/// ## Notice: you should prefer using [`schedule_update_any`]
|
||||
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
|
||||
Runtime::with_scope(*self, |cx| cx.schedule_update()).expect("to be in a dioxus runtime")
|
||||
}
|
||||
|
|
|
@ -38,9 +38,9 @@ impl ScopeId {
|
|||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// use dioxus_signals::*;
|
||||
/// let my_persistent_state = Signal::new_in_scope(ScopeId::ROOT, String::new());
|
||||
/// ```rust, no_run
|
||||
/// use dioxus::prelude::*;
|
||||
/// let my_persistent_state = Signal::new_in_scope(String::new(), ScopeId::ROOT);
|
||||
/// ```
|
||||
pub const ROOT: ScopeId = ScopeId(0);
|
||||
}
|
||||
|
|
|
@ -240,7 +240,7 @@ impl Runtime {
|
|||
poll_result
|
||||
}
|
||||
|
||||
/// Drop the future with the given TaskId
|
||||
/// Drop the future with the given Task
|
||||
///
|
||||
/// This does not abort the task, so you'll want to wrap it in an abort handle if that's important to you
|
||||
pub(crate) fn remove_task(&self, id: Task) -> Option<Rc<LocalTask>> {
|
||||
|
|
|
@ -91,6 +91,7 @@ use tracing::instrument;
|
|||
///
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use dioxus_core::*;
|
||||
/// # fn app() -> Element { rsx! { div {} } }
|
||||
///
|
||||
/// let mut vdom = VirtualDom::new(app);
|
||||
|
@ -99,36 +100,67 @@ use tracing::instrument;
|
|||
///
|
||||
/// To call listeners inside the VirtualDom, call [`VirtualDom::handle_event`] with the appropriate event data.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// vdom.handle_event(event);
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use dioxus_core::*;
|
||||
/// # fn app() -> Element { rsx! { div {} } }
|
||||
/// # let mut vdom = VirtualDom::new(app);
|
||||
/// let event = std::rc::Rc::new(0);
|
||||
/// vdom.handle_event("onclick", event, ElementId(0), true);
|
||||
/// ```
|
||||
///
|
||||
/// While no events are ready, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use dioxus_core::*;
|
||||
/// # fn app() -> Element { rsx! { div {} } }
|
||||
/// # let mut vdom = VirtualDom::new(app);
|
||||
/// tokio::runtime::Runtime::new().unwrap().block_on(async {
|
||||
/// vdom.wait_for_work().await;
|
||||
/// });
|
||||
/// ```
|
||||
///
|
||||
/// Once work is ready, call [`VirtualDom::render_with_deadline`] to compute the differences between the previous and
|
||||
/// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be
|
||||
/// Once work is ready, call [`VirtualDom::render_immediate`] to compute the differences between the previous and
|
||||
/// current UI trees. This will write edits to a [`WriteMutations`] object you pass in that contains with edits that need to be
|
||||
/// handled by the renderer.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// let mutations = vdom.work_with_deadline(tokio::time::sleep(Duration::from_millis(100)));
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use dioxus_core::*;
|
||||
/// # fn app() -> Element { rsx! { div {} } }
|
||||
/// # let mut vdom = VirtualDom::new(app);
|
||||
/// let mut mutations = Mutations::default();
|
||||
///
|
||||
/// for edit in mutations.edits {
|
||||
/// real_dom.apply(edit);
|
||||
/// }
|
||||
/// vdom.render_immediate(&mut mutations);
|
||||
/// ```
|
||||
///
|
||||
/// To not wait for suspense while diffing the VirtualDom, call [`VirtualDom::render_immediate`] or pass an immediately
|
||||
/// ready future to [`VirtualDom::render_with_deadline`].
|
||||
/// To not wait for suspense while diffing the VirtualDom, call [`VirtualDom::render_immediate`].
|
||||
///
|
||||
///
|
||||
/// ## Building an event loop around Dioxus:
|
||||
///
|
||||
/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use dioxus_core::*;
|
||||
/// # struct RealDom;
|
||||
/// # struct Event {}
|
||||
/// # impl RealDom {
|
||||
/// # fn new() -> Self {
|
||||
/// # Self {}
|
||||
/// # }
|
||||
/// # fn apply(&mut self) -> Mutations {
|
||||
/// # todo!()
|
||||
/// # }
|
||||
/// # async fn wait_for_event(&mut self) -> std::rc::Rc<dyn std::any::Any> {
|
||||
/// # todo!()
|
||||
/// # }
|
||||
/// # }
|
||||
/// #
|
||||
/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
|
||||
/// let mut real_dom = RealDom::new();
|
||||
///
|
||||
/// #[component]
|
||||
/// fn app() -> Element {
|
||||
/// rsx! {
|
||||
|
@ -136,51 +168,39 @@ use tracing::instrument;
|
|||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let dom = VirtualDom::new(app);
|
||||
/// let mut dom = VirtualDom::new(app);
|
||||
///
|
||||
/// dom.rebuild(real_dom.apply());
|
||||
/// dom.rebuild(&mut real_dom.apply());
|
||||
///
|
||||
/// loop {
|
||||
/// select! {
|
||||
/// tokio::select! {
|
||||
/// _ = dom.wait_for_work() => {}
|
||||
/// evt = real_dom.wait_for_event() => dom.handle_event(evt),
|
||||
/// evt = real_dom.wait_for_event() => dom.handle_event("onclick", evt, ElementId(0), true),
|
||||
/// }
|
||||
///
|
||||
/// real_dom.apply(dom.render_immediate());
|
||||
/// dom.render_immediate(&mut real_dom.apply());
|
||||
/// }
|
||||
/// # });
|
||||
/// ```
|
||||
///
|
||||
/// ## Waiting for suspense
|
||||
///
|
||||
/// Because Dioxus supports suspense, you can use it for server-side rendering, static site generation, and other usecases
|
||||
/// Because Dioxus supports suspense, you can use it for server-side rendering, static site generation, and other use cases
|
||||
/// where waiting on portions of the UI to finish rendering is important. To wait for suspense, use the
|
||||
/// [`VirtualDom::render_with_deadline`] method:
|
||||
/// [`VirtualDom::wait_for_suspense`] method:
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// let dom = VirtualDom::new(app);
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use dioxus_core::*;
|
||||
/// # fn app() -> Element { rsx! { div {} } }
|
||||
/// tokio::runtime::Runtime::new().unwrap().block_on(async {
|
||||
/// let mut dom = VirtualDom::new(app);
|
||||
///
|
||||
/// let deadline = tokio::time::sleep(Duration::from_millis(100));
|
||||
/// let edits = dom.render_with_deadline(deadline).await;
|
||||
/// ```
|
||||
/// dom.rebuild_in_place();
|
||||
/// dom.wait_for_suspense().await;
|
||||
/// });
|
||||
///
|
||||
/// ## Use with streaming
|
||||
///
|
||||
/// If not all rendering is done by the deadline, it might be worthwhile to stream the rest later. To do this, we
|
||||
/// suggest rendering with a deadline, and then looping between [`VirtualDom::wait_for_work`] and render_immediate until
|
||||
/// no suspended work is left.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// let dom = VirtualDom::new(app);
|
||||
///
|
||||
/// let deadline = tokio::time::sleep(Duration::from_millis(20));
|
||||
/// let edits = dom.render_with_deadline(deadline).await;
|
||||
///
|
||||
/// real_dom.apply(edits);
|
||||
///
|
||||
/// while dom.has_suspended_work() {
|
||||
/// dom.wait_for_work().await;
|
||||
/// real_dom.apply(dom.render_immediate());
|
||||
/// }
|
||||
/// // Render the virtual dom
|
||||
/// ```
|
||||
pub struct VirtualDom {
|
||||
pub(crate) scopes: Slab<ScopeState>,
|
||||
|
@ -217,7 +237,9 @@ impl VirtualDom {
|
|||
///
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use dioxus_core::*;
|
||||
/// fn Example() -> Element {
|
||||
/// rsx!( div { "hello world" } )
|
||||
/// }
|
||||
|
@ -241,8 +263,10 @@ impl VirtualDom {
|
|||
///
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust, ignore
|
||||
/// #[derive(PartialEq, Props)]
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use dioxus_core::*;
|
||||
/// #[derive(PartialEq, Props, Clone)]
|
||||
/// struct SomeProps {
|
||||
/// name: &'static str
|
||||
/// }
|
||||
|
@ -251,12 +275,21 @@ impl VirtualDom {
|
|||
/// rsx!{ div { "hello {cx.name}" } }
|
||||
/// }
|
||||
///
|
||||
/// let dom = VirtualDom::new(Example);
|
||||
/// let dom = VirtualDom::new_with_props(Example, SomeProps { name: "world" });
|
||||
/// ```
|
||||
///
|
||||
/// Note: the VirtualDom is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use dioxus_core::*;
|
||||
/// # #[derive(PartialEq, Props, Clone)]
|
||||
/// # struct SomeProps {
|
||||
/// # name: &'static str
|
||||
/// # }
|
||||
/// # fn Example(cx: SomeProps) -> Element {
|
||||
/// # rsx!{ div { "hello {cx.name}" } }
|
||||
/// # }
|
||||
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
|
||||
/// dom.rebuild_in_place();
|
||||
/// ```
|
||||
|
@ -274,36 +307,7 @@ impl VirtualDom {
|
|||
dom
|
||||
}
|
||||
|
||||
/// Create a new VirtualDom with the given properties for the root component.
|
||||
///
|
||||
/// # Description
|
||||
///
|
||||
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
|
||||
///
|
||||
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
|
||||
/// to toss out the entire tree.
|
||||
///
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust, ignore
|
||||
/// #[derive(PartialEq, Props)]
|
||||
/// struct SomeProps {
|
||||
/// name: &'static str
|
||||
/// }
|
||||
///
|
||||
/// fn Example(cx: SomeProps) -> Element {
|
||||
/// rsx!{ div{ "hello {cx.name}" } }
|
||||
/// }
|
||||
///
|
||||
/// let dom = VirtualDom::new(Example);
|
||||
/// ```
|
||||
///
|
||||
/// Note: the VirtualDom is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// let mut dom = VirtualDom::new_from_root(VComponent::new(Example, SomeProps { name: "jane" }, "Example"));
|
||||
/// dom.rebuild(to_writer);
|
||||
/// ```
|
||||
/// Create a new VirtualDom from something that implements [`AnyProps`]
|
||||
#[instrument(skip(root), level = "trace", name = "VirtualDom::new")]
|
||||
pub(crate) fn new_with_component(root: impl AnyProps + 'static) -> Self {
|
||||
let (tx, rx) = futures_channel::mpsc::unbounded();
|
||||
|
@ -448,7 +452,9 @@ impl VirtualDom {
|
|||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # fn app() -> Element { rsx! { div {} } }
|
||||
/// let dom = VirtualDom::new(app);
|
||||
/// ```
|
||||
#[instrument(skip(self), level = "trace", name = "VirtualDom::wait_for_work")]
|
||||
|
@ -624,11 +630,16 @@ impl VirtualDom {
|
|||
/// Any templates previously registered will remain.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust, ignore
|
||||
/// static app: Component = |cx| rsx!{ "hello world" };
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use dioxus_core::*;
|
||||
/// fn app() -> Element {
|
||||
/// rsx! { "hello world" }
|
||||
/// }
|
||||
///
|
||||
/// let mut dom = VirtualDom::new();
|
||||
/// dom.rebuild(to_writer);
|
||||
/// let mut dom = VirtualDom::new(app);
|
||||
/// let mut mutations = Mutations::default();
|
||||
/// dom.rebuild(&mut mutations);
|
||||
/// ```
|
||||
#[instrument(skip(self, to), level = "trace", name = "VirtualDom::rebuild")]
|
||||
pub fn rebuild(&mut self, to: &mut impl WriteMutations) {
|
||||
|
|
BIN
packages/desktop/architecure/Desktop Event Architecture.png
Normal file
BIN
packages/desktop/architecure/Desktop Event Architecture.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 344 KiB |
|
@ -63,6 +63,7 @@ criterion = "0.3.5"
|
|||
thiserror = { workspace = true }
|
||||
env_logger = "0.10.0"
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
dioxus = { workspace = true }
|
||||
|
||||
[[bench]]
|
||||
name = "jsframework"
|
||||
|
|
|
@ -34,7 +34,7 @@ Remember: Dioxus is a library for declaring interactive user interfaces—it is
|
|||
|
||||
## Brief Overview
|
||||
|
||||
All Dioxus apps are built by composing functions that return an `Element`.
|
||||
All Dioxus apps are built by composing functions that start with a capital letter and return an `Element`.
|
||||
|
||||
To launch an app, we use the `launch` method and use features in `Cargo.toml` to specify which renderer we want to use. In the launch function, we pass the app's root `Component`.
|
||||
|
||||
|
@ -46,7 +46,7 @@ fn main() {
|
|||
}
|
||||
|
||||
// The #[component] attribute streamlines component creation.
|
||||
// It's not required, but highly recommended. For example, UpperCamelCase components will not generate a warning.
|
||||
// It's not required, but highly recommended. It will lint incorrect component definitions and help you create props structs.
|
||||
#[component]
|
||||
fn App() -> Element {
|
||||
rsx! { "hello world!" }
|
||||
|
@ -55,52 +55,42 @@ fn App() -> Element {
|
|||
|
||||
## Elements & your first component
|
||||
|
||||
To assemble UI trees with Dioxus, you need to use the `render` function on
|
||||
something called `LazyNodes`. To produce `LazyNodes`, you can use the `rsx!`
|
||||
macro or the NodeFactory API. For the most part, you want to use the `rsx!`
|
||||
macro.
|
||||
|
||||
You can use the `rsx!` macro to create elements with a jsx-like syntax.
|
||||
Any element in `rsx!` can have attributes, listeners, and children. For
|
||||
consistency, we force all attributes and listeners to be listed _before_
|
||||
children.
|
||||
|
||||
```rust, ignore
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
let value = "123";
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
class: "my-class {value}", // <--- attribute
|
||||
onclick: move |_| info!("clicked!"), // <--- listener
|
||||
onclick: move |_| println!("clicked!"), // <--- listener
|
||||
h1 { "hello world" }, // <--- child
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
The `rsx!` macro accepts attributes in "struct form". Any rust expression contained within curly braces that implements `IntoIterator<Item = impl IntoVNode>` will be parsed as a child. We make two exceptions: both `for` loops and `if` statements are parsed where their body is parsed as a child.
|
||||
The `rsx!` macro accepts attributes in "struct form". Any rust expression contained within curly braces that implements [`IntoDynNode`](dioxus_core::IntoDynNode) will be parsed as a child. We make two exceptions: both `for` loops and `if` statements are parsed where their body is parsed as a rsx nodes.
|
||||
|
||||
```rust, ignore
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
rsx! {
|
||||
div {
|
||||
for _ in 0..10 {
|
||||
span { "hello world" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `rsx!` macro is what generates the `Element` that our components return.
|
||||
|
||||
```rust, ignore
|
||||
#[component]
|
||||
fn Example() -> Element {
|
||||
rsx!{ "hello world" }
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Putting everything together, we can write a simple component that renders a list of
|
||||
elements:
|
||||
|
||||
```rust, ignore
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
#[component]
|
||||
fn App() -> Element {
|
||||
let name = "dave";
|
||||
|
@ -118,12 +108,13 @@ fn App() -> Element {
|
|||
## Components
|
||||
|
||||
We can compose these function components to build a complex app. Each new
|
||||
component we design must take some Properties. For components with no explicit
|
||||
properties we can omit the type altogether.
|
||||
component we design must take some Properties. For components with no explicit properties, we can omit the type altogether.
|
||||
|
||||
In Dioxus, all properties are memoized by default, and this implement both Clone and PartialEq. For props you can't clone, simply wrap the fields in a ReadOnlySignal and Dioxus will handle the wrapping for you.
|
||||
In Dioxus, all properties are memorized by default with Clone and PartialEq. For props you can't clone, simply wrap the fields in a [`ReadOnlySignal`](dioxus_signals::ReadOnlySignal) and Dioxus will handle converting types for you.
|
||||
|
||||
```rust, ignore
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
# #[component] fn Header(title: String, color: String) -> Element { todo!() }
|
||||
#[component]
|
||||
fn App() -> Element {
|
||||
rsx! {
|
||||
|
@ -135,64 +126,38 @@ fn App() -> Element {
|
|||
}
|
||||
```
|
||||
|
||||
Our `Header` component takes a `title` and a `color` property, which we
|
||||
declare on an explicit `HeaderProps` struct.
|
||||
The `#[component]` macro will help us automatically create a props struct for our component:
|
||||
|
||||
```rust, ignore
|
||||
// The `Props` derive macro lets us add additional functionality to how props are interpreted.
|
||||
#[derive(Props, PartialEq)]
|
||||
struct HeaderProps {
|
||||
title: String,
|
||||
color: String,
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Header(props: HeaderProps) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
background_color: "{props.color}"
|
||||
h1 { "{props.title}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `#[component]` macro also allows you to derive the props
|
||||
struct from function arguments:
|
||||
|
||||
```rust, ignore
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
// The component macro turns the arguments for our function into named fields we can pass in to the component in rsx
|
||||
#[component]
|
||||
fn Header(title: String, color: String) -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
background_color: "{color}"
|
||||
background_color: "{color}",
|
||||
h1 { "{title}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Components that begin with an uppercase letter may be called with
|
||||
the traditional (for React) curly-brace syntax like so:
|
||||
|
||||
```rust, ignore
|
||||
rsx! {
|
||||
Header { title: "My App" }
|
||||
}
|
||||
```
|
||||
> You can read more about props in the [reference](https://dioxuslabs.com/learn/0.5/reference/component_props).
|
||||
|
||||
## Hooks
|
||||
|
||||
While components are reusable forms of UI elements, hooks are reusable forms
|
||||
of logic. Hooks provide us a way of retrieving state from Dioxus' internal `Scope` and using
|
||||
of logic. Hooks provide a way of retrieving state from Dioxus' internal `Scope` and using
|
||||
it to render UI elements.
|
||||
|
||||
By convention, all hooks are functions that should start with `use_`. We can
|
||||
use hooks to define the state and modify it from within listeners.
|
||||
|
||||
```rust, ignore
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
#[component]
|
||||
fn App() -> Element {
|
||||
// The use signal hook runs once when the component is created and then returns the current value every run after the first
|
||||
let name = use_signal(|| "world");
|
||||
|
||||
rsx! { "hello {name}!" }
|
||||
|
@ -202,38 +167,20 @@ fn App() -> Element {
|
|||
Hooks are sensitive to how they are used. To use hooks, you must abide by the
|
||||
["rules of hooks"](https://dioxuslabs.com/learn/0.5/reference/hooks#rules-of-hooks):
|
||||
|
||||
- Functions with "use\_" should not be called in callbacks
|
||||
- Functions with "use\_" should not be called out of order
|
||||
- Functions with "use\_" should not be called in loops or conditionals
|
||||
- Hooks can only be called in the body of a component or another hook. Not inside of another expression like a loop, conditional or function call.
|
||||
- Hooks should start with "use\_"
|
||||
|
||||
In a sense, hooks let us add a field of state to our component without declaring
|
||||
an explicit state struct. However, this means we need to "load" the struct in the right
|
||||
order. If that order is wrong, then the hook will pick the wrong state and panic.
|
||||
|
||||
Most hooks you'll write are simply compositions of other hooks:
|
||||
|
||||
```rust, ignore
|
||||
fn use_username(d: Uuid) -> bool {
|
||||
let users = use_context::<Users>();
|
||||
users.get(&id).map(|user| user.logged_in).ok_or(false)
|
||||
}
|
||||
```
|
||||
|
||||
To create entirely new foundational hooks, we can use the `use_hook` method.
|
||||
|
||||
```rust, ignore
|
||||
fn use_mut_string() -> String {
|
||||
use_hook(|_| "Hello".to_string())
|
||||
}
|
||||
```
|
||||
|
||||
If you want to extend Dioxus with some new functionality, you'll probably want to implement a new hook from scratch.
|
||||
Dioxus includes many built-in hooks that you can use in your components. If those hooks don't fit your use case, you can also extend Dioxus with [custom hooks](https://dioxuslabs.com/learn/0.5/cookbook/state/custom_hooks).
|
||||
|
||||
## Putting it all together
|
||||
|
||||
Using components, templates, and hooks, we can build a simple app.
|
||||
Using components, rsx, and hooks, we can build a simple app.
|
||||
|
||||
```rust, ignore
|
||||
```rust, no_run
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
|
@ -244,28 +191,28 @@ fn main() {
|
|||
fn App() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
|
||||
rsx!(
|
||||
rsx! {
|
||||
div { "Count: {count}" }
|
||||
button { onclick: move |_| count += 1, "Increment" }
|
||||
button { onclick: move |_| count -= 1, "Decrement" }
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
## Conclusion
|
||||
|
||||
This overview doesn't cover everything. Make sure to check out the tutorial and reference guide on the official
|
||||
This overview doesn't cover everything. Make sure to check out the [tutorial](https://dioxuslabs.com/learn/0.5/guide) and [reference](https://dioxuslabs.com/learn/0.5/reference) on the official
|
||||
website for more details.
|
||||
|
||||
Beyond this overview, Dioxus supports:
|
||||
|
||||
- Server-side rendering
|
||||
- [Server-side rendering](https://dioxuslabs.com/learn/0.5/reference/fullstack)
|
||||
- Concurrent rendering (with async support)
|
||||
- Web/Desktop/Mobile support
|
||||
- Pre-rendering and rehydration
|
||||
- Fragments, Portals, and Suspense
|
||||
- Pre-rendering and hydration
|
||||
- Fragments, and Suspense
|
||||
- Inline-styles
|
||||
- Custom event handlers
|
||||
- [Custom event handlers](https://dioxuslabs.com/learn/0.5/reference/event_handlers#handler-props)
|
||||
- Custom elements
|
||||
- Basic fine-grained reactivity (IE SolidJS/Svelte)
|
||||
- and more!
|
||||
|
|
|
@ -37,6 +37,22 @@ type UnsendContext = dyn Fn() -> Box<dyn Any> + 'static;
|
|||
|
||||
impl LaunchBuilder {
|
||||
/// Create a new builder for your application. This will create a launch configuration for the current platform based on the features enabled on the `dioxus` crate.
|
||||
// If you aren't using a third party renderer and this is not a docs.rs build, generate a warning about no renderer being enabled
|
||||
#[cfg_attr(
|
||||
all(not(any(
|
||||
docsrs,
|
||||
feature = "third-party-renderer",
|
||||
feature = "liveview",
|
||||
feature = "desktop",
|
||||
feature = "mobile",
|
||||
feature = "web",
|
||||
feature = "fullstack",
|
||||
feature = "static-generation"
|
||||
))),
|
||||
deprecated(
|
||||
note = "No renderer is enabled. You must enable a renderer feature on the dioxus crate before calling the launch function.\nAdd `web`, `desktop`, `mobile`, `fullstack`, or `static-generation` to the `features` of dioxus field in your Cargo.toml.\n# Example\n```toml\n# ...\n[dependencies]\ndioxus = { version = \"0.5.0\", features = [\"web\"] }\n# ...\n```"
|
||||
)
|
||||
)]
|
||||
pub fn new() -> LaunchBuilder<current_platform::Config, ValidContext> {
|
||||
LaunchBuilder {
|
||||
launch_fn: current_platform::launch,
|
||||
|
@ -301,7 +317,24 @@ mod current_platform {
|
|||
}
|
||||
|
||||
/// Launch your application without any additional configuration. See [`LaunchBuilder`] for more options.
|
||||
// If you aren't using a third party renderer and this is not a docs.rs build, generate a warning about no renderer being enabled
|
||||
#[cfg_attr(
|
||||
all(not(any(
|
||||
docsrs,
|
||||
feature = "third-party-renderer",
|
||||
feature = "liveview",
|
||||
feature = "desktop",
|
||||
feature = "mobile",
|
||||
feature = "web",
|
||||
feature = "fullstack",
|
||||
feature = "static-generation"
|
||||
))),
|
||||
deprecated(
|
||||
note = "No renderer is enabled. You must enable a renderer feature on the dioxus crate before calling the launch function.\nAdd `web`, `desktop`, `mobile`, `fullstack`, or `static-generation` to the `features` of dioxus field in your Cargo.toml.\n# Example\n```toml\n# ...\n[dependencies]\ndioxus = { version = \"0.5.0\", features = [\"web\"] }\n# ...\n```"
|
||||
)
|
||||
)]
|
||||
pub fn launch(app: fn() -> Element) {
|
||||
#[allow(deprecated)]
|
||||
LaunchBuilder::new().launch(app)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,26 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
//!
|
||||
//! ## Dioxus Crate Features
|
||||
//!
|
||||
//! This crate has several features that can be enabled to change the active renderer and enable various integrations:
|
||||
//!
|
||||
//! - `signals`: (default) re-exports `dioxus-signals`
|
||||
//! - `macro`: (default) re-exports `dioxus-macro`
|
||||
//! - `html`: (default) exports `dioxus-html` as the default elements to use in rsx
|
||||
//! - `hooks`: (default) re-exports `dioxus-hooks`
|
||||
//! - `hot-reload`: (default) enables hot rsx reloading in all renderers that support it
|
||||
//! - `router`: exports the [router](https://dioxuslabs.com/learn/0.5/router) and enables any router features for the current platform
|
||||
//! - `third-party-renderer`: Just disables warnings about no active platform when no renderers are enabled
|
||||
//!
|
||||
//! Platform features (the current platform determines what platform the [`launch`](dioxus::prelude::launch) function runs):
|
||||
//!
|
||||
//! - `fullstack`: enables the fullstack platform. This must be used in combination with the `web` feature for wasm builds and `axum` feature for server builds
|
||||
//! - `desktop`: enables the desktop platform
|
||||
//! - `mobile`: enables the mobile platform
|
||||
//! - `web`: enables the web platform. If the fullstack platform is enabled, this will set the fullstack platform to client mode
|
||||
//! - `liveview`: enables the liveview platform
|
||||
//! - `static-generation`: enables the static generation platform. This must be used in combination with the `web` feature for wasm builds and `axum` feature for server builds
|
||||
//! - `axum`: enables the axum server with static generation or fullstack and sets the platform to server mode
|
||||
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
@ -11,6 +33,7 @@ mod launch;
|
|||
|
||||
#[cfg(feature = "launch")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "launch")))]
|
||||
#[allow(deprecated)]
|
||||
pub use launch::launch;
|
||||
|
||||
#[cfg(feature = "hooks")]
|
||||
|
|
|
@ -23,6 +23,7 @@ slab = { workspace = true }
|
|||
dioxus-debug-cell = "0.1.1"
|
||||
futures-util = { workspace = true}
|
||||
generational-box.workspace = true
|
||||
rustversion = "1.0.17"
|
||||
|
||||
[dev-dependencies]
|
||||
futures-util = { workspace = true, default-features = false }
|
||||
|
@ -30,6 +31,7 @@ dioxus-core = { workspace = true }
|
|||
dioxus = { workspace = true }
|
||||
web-sys = { version = "0.3.64", features = ["Document", "Window", "Element"] }
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
reqwest = { workspace = true }
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"]
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
## Overview
|
||||
|
||||
`dioxus-hooks` includes some basic useful hooks for dioxus such as:
|
||||
`dioxus-hooks` includes some basic useful hooks for Dioxus such as:
|
||||
|
||||
- use_signal
|
||||
- use_effect
|
||||
|
@ -29,8 +29,40 @@
|
|||
- use_memo
|
||||
- use_coroutine
|
||||
|
||||
Unlike React, none of these hooks are foundational since they all build off the primitive `use_hook`. You can extend these hooks with [custom hooks](https://dioxuslabs.com/learn/0.5/cookbook/state/custom_hooks) in your own code. If you think they would be useful for the broader community, you can open a PR to add your hook to the [Dioxus Awesome](https://github.com/DioxusLabs/awesome-dioxus) list.
|
||||
|
||||
Unlike React, none of these hooks are foundational since they all build off the primitive `use_hook`.
|
||||
## State Cheat Sheet
|
||||
|
||||
If you aren't sure what hook to use, you can use this cheat sheet to help you decide:
|
||||
|
||||
### State Location
|
||||
|
||||
Depending on where you need to access the state, you can put your state in one of three places:
|
||||
|
||||
| Location | Where can you access the state? | Recommended for Libraries? | Examples |
|
||||
| ---------------------------------------------------------------------------------------- | ------------------------------- | -------------------------- | --------------------------------------------------------------------------- |
|
||||
| [Hooks](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_signal.html) | Any components you pass it to | ✅ | `use_signal(\|\| 0)`, `use_memo(\|\| state() * 2)` |
|
||||
| [Context](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html) | Any child components | ✅ | `use_context_provider(\|\| Signal::new(0))`, `use_context::<Signal<i32>>()` |
|
||||
| [Global](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Signal.html#method.global) | Anything in your app | ❌ | `Signal::global(\|\| 0)` |
|
||||
|
||||
### Derived State
|
||||
|
||||
If you don't have an initial value for your state, you can derive your state from other states with a closure or asynchronous function:
|
||||
|
||||
| Hook | Reactive (reruns when dependencies change) | Async | Memorizes Output | Example |
|
||||
| ----------------------------------------------------------------------------------- | ------------------------------------------ | ----- | ---------------- | ----------------------------------------------------------------------------------- |
|
||||
| [`use_memo`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_memo.html) | ✅ | ❌ | ✅ | `use_memo(move \|\| count() * 2)` |
|
||||
| [`use_resource`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_resource.html) | ✅ | ✅ | ❌ | `use_resource(move \|\| reqwest::get(format!("/users/{user_id}")))` |
|
||||
| [`use_future`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_future.html) | ❌ | ✅ | ❌ | `use_future(move \|\| println!("{:?}", reqwest::get(format!("/users/{user_id}"))))` |
|
||||
|
||||
### Persistent State
|
||||
|
||||
The core hooks library doesn't provide hooks for persistent state, but you can extend the core hooks with hooks from [dioxus-sdk](https://crates.io/crates/dioxus-sdk) and the [dioxus-router](https://crates.io/crates/dioxus-router) to provide persistent state management.
|
||||
|
||||
| State | Sharable | Example |
|
||||
| ---------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------- |
|
||||
| [`use_persistent`](https://github.com/DioxusLabs/sdk/tree/master/examples/storage) | ❌ | `use_persistent("unique_key", move \|\| initial_state)` |
|
||||
| [`Router<Route> {}`](https://dioxuslabs.com/learn/0.5/router) | ✅ | `#[derive(Routable, Clone, PartialEq)] enum Route { #[route("/user/:id")] Homepage { id: u32 } }` |
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
77
packages/hooks/docs/derived_state.md
Normal file
77
packages/hooks/docs/derived_state.md
Normal file
|
@ -0,0 +1,77 @@
|
|||
Creates a new Memo. The memo will be run immediately and whenever any signal it reads is written to. Memos can be used to efficiently compute derived data from signals.
|
||||
|
||||
```rust
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_signals::*;
|
||||
|
||||
fn App() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
// the double memo will always be equal to two times the value of count, even after count changes
|
||||
let double = use_memo(move || count * 2);
|
||||
|
||||
rsx! {
|
||||
"{double}"
|
||||
button {
|
||||
// When count changes, the memo will rerun and double will be updated
|
||||
// memos rerun any time you write to a signal they read. They will only rerun values/component that depend on them if the value of the memo changes
|
||||
onclick: move |_| count += 1,
|
||||
"Increment"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The closure you pass into memos will be called whenever the state you read inside the memo is written to even if the value hasn't actually changed, but the memo you get will not rerun other parts of your app unless the output changes (`PartialEq` returns false).
|
||||
|
||||
Lets dig into some examples to see how this works:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
let mut count = use_signal(|| 1);
|
||||
// double_count will rerun when state we read inside the memo changes (count)
|
||||
let double_count = use_memo(move || count() * 2);
|
||||
|
||||
// memos act a lot like a read only version of a signal. You can read them, display them, and move them around like any other signal
|
||||
println!("{}", double_count); // Prints "2"
|
||||
|
||||
// But you can't write to them directly
|
||||
// Instead, any time you write to a value the memo reads, the memo will rerun
|
||||
count += 1;
|
||||
|
||||
println!("{}", double_count); // Prints "4"
|
||||
|
||||
// Lets create another memo that reads the value of double_count
|
||||
let double_count_plus_one = use_memo(move || double_count() + 1);
|
||||
|
||||
println!("{}", double_count_plus_one); // Prints "5"
|
||||
|
||||
// Now if we write to count the double_count memo will rerun
|
||||
// If that the output of double_count changes, then it will cause double_count_plus_one to rerun
|
||||
count += 1;
|
||||
|
||||
println!("{}", double_count); // Prints "6"
|
||||
println!("{}", double_count_plus_one); // Prints "7"
|
||||
|
||||
// However if the value of double_count doesn't change after a write, then it won't trigger double_count_plus_one to rerun
|
||||
// Since we just write the same value, the doubled value is still 6 and we don't rerun double_count_plus_one
|
||||
*count.write() = 3;
|
||||
|
||||
println!("{}", double_count); // Prints "6"
|
||||
println!("{}", double_count_plus_one); // Prints "7"
|
||||
```
|
||||
|
||||
## With non-reactive dependencies
|
||||
|
||||
To add non-reactive dependencies, you can use the [`crate::use_reactive()`] hook.
|
||||
|
||||
Signals will automatically be added as dependencies, so you don't need to call this method for them.
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
#[component]
|
||||
fn Comp(count: u32) -> Element {
|
||||
// Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.
|
||||
let new_count = use_memo(use_reactive((&count,), |(count,)| count + 1));
|
||||
todo!()
|
||||
}
|
||||
```
|
96
packages/hooks/docs/moving_state_around.md
Normal file
96
packages/hooks/docs/moving_state_around.md
Normal file
|
@ -0,0 +1,96 @@
|
|||
<details>
|
||||
<summary>How should I move around state? (Click here to learn more about moving around and sharing state in Dioxus)</summary>
|
||||
|
||||
You will often need to move state around between your components. Dioxus provides three different ways to pass around state:
|
||||
|
||||
1. Just pass your values as [props](https://dioxuslabs.com/learn/0.5/reference/component_props):
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn MyComponent() -> Element {
|
||||
let count = use_signal(|| 0);
|
||||
|
||||
rsx! {
|
||||
IncrementButton {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn IncrementButton(mut count: Signal<i32>) -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
onclick: move |_| count += 1,
|
||||
"Increment"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is the most common way to pass state around. It is the most explicit and local to your component. Use this when it isn't overly annoying to pass around a value.
|
||||
|
||||
2. Use [use_context](https://dioxuslabs.com/learn/0.5/reference/context) to pass state from a parent component to all children:
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
#[derive(Clone, Copy)]
|
||||
struct MyState {
|
||||
count: Signal<i32>
|
||||
}
|
||||
|
||||
fn ParentComponent() -> Element {
|
||||
// Use context provider provides an unique type to all children of this component
|
||||
use_context_provider(|| MyState { count: Signal::new(0) });
|
||||
|
||||
rsx! {
|
||||
// IncrementButton will have access to the count without explicitly passing it through props
|
||||
IncrementButton {}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn IncrementButton() -> Element {
|
||||
// Use context gets the value from a parent component
|
||||
let mut count = use_context::<MyState>().count;
|
||||
|
||||
rsx! {
|
||||
button {
|
||||
onclick: move |_| count += 1,
|
||||
"Increment"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This is slightly less explicit than passing it as a prop, but it is still local to the component. This is really great if you want state that is global to part of your app. It lets you create multiple global-ish states while still making state different when you reuse components. If I create a new `ParentComponent`, it will have a new `MyState`.
|
||||
|
||||
3. Globals let you share state with your whole app with rust statics:
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
// Count will be created the first time you access it with the closure you pass to Signal::global
|
||||
static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
|
||||
|
||||
fn ParentComponent() -> Element {
|
||||
rsx! {
|
||||
IncrementButton {}
|
||||
}
|
||||
}
|
||||
|
||||
fn IncrementButton() -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
// You don't need to pass anything around or get anything out of the context because COUNT is global
|
||||
onclick: move |_| *COUNT.write() += 1,
|
||||
"Increment"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Global state can be very ergonomic if your state is truly global, but you shouldn't use it if you need state to be different for different instances of your component. If I create another `IncrementButton` it will use the same `COUNT`. Libraries should generally avoid this to make components more reusable.
|
||||
|
||||
> Note: Even though it is in a static, `COUNT` will be different for each app instance (this is generally only reliant on the server).
|
||||
|
||||
</details>
|
152
packages/hooks/docs/rules_of_hooks.md
Normal file
152
packages/hooks/docs/rules_of_hooks.md
Normal file
|
@ -0,0 +1,152 @@
|
|||
## Additional Information that may be useful
|
||||
|
||||
<details>
|
||||
<summary>This function is a hook which means you need to <b>follow the rules of hooks</b> when you call it. You can click here to learn more about the rules of hooks.</summary>
|
||||
|
||||
Hooks in dioxus need to run in the same order every time you run the component. To make sure you run hooks in a consistent order, you should follow the rules of hooks:
|
||||
|
||||
1. Hooks should only be called from the root of a component or another hook
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn App() -> Element {
|
||||
// ✅ You can call hooks from the body of a component
|
||||
let number = use_signal(|| 1);
|
||||
if number() == 1 {
|
||||
// ❌ You can run into issues if you can hooks inside other expressions inside your component
|
||||
// If number changes from 0 to 1, the order of the hooks will be different and your app may panic
|
||||
let string = use_signal(|| "hello world".to_string());
|
||||
}
|
||||
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn use_my_hook() -> Signal<i32> {
|
||||
// ✅ You can call hooks from the body of other hooks
|
||||
let number = use_signal(|| 1);
|
||||
// ❌ Again, creating hooks inside expressions inside other hooks can cause issues
|
||||
if number() == 1 {
|
||||
let string = use_signal(|| "hello world".to_string());
|
||||
}
|
||||
|
||||
number
|
||||
}
|
||||
```
|
||||
|
||||
2. Hooks should always start with `use_` to make it clear that you need to call them in a consistent order
|
||||
|
||||
Because hooks should only be called from the root of a component or another hook, you shouldn't call hooks inside of:
|
||||
|
||||
- ❌ Conditionals
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn App() -> Element {
|
||||
let number = use_signal(|| 1);
|
||||
// ❌ Changing the condition will change the order of the hooks
|
||||
if number() == 1 {
|
||||
let string = use_signal(|| "hello world".to_string());
|
||||
}
|
||||
|
||||
// ❌ Changing the value you are matching will change the order of the hooks
|
||||
match number() {
|
||||
1 => {
|
||||
let string = use_signal(|| "hello world".to_string());
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
- ❌ Loops
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn App() -> Element {
|
||||
let number = use_signal(|| 1);
|
||||
// ❌ Changing the loop will change the order of the hooks
|
||||
for i in 0..number() {
|
||||
let string = use_signal(|| "hello world".to_string());
|
||||
}
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
- ❌ Event Handlers
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn App() -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
onclick: move |_| {
|
||||
// ❌ Calling the event handler will change the order of the hooks
|
||||
use_signal(|| "hello world".to_string());
|
||||
},
|
||||
"Click me"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- ❌ Initialization closures in other hooks
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn App() -> Element {
|
||||
let number = use_signal(|| {
|
||||
// ❌ This closure will only be called when the component is first created. Running the component will change the order of the hooks
|
||||
let string = use_signal(|| "hello world".to_string());
|
||||
string()
|
||||
});
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
<details>
|
||||
<summary>Why do hooks need to run in a consistent order?</summary>
|
||||
|
||||
Hooks need to be run in a consistent order because dioxus stores hooks in a list and uses the order you run hooks in to determine what part of the state belongs to which hook.
|
||||
|
||||
Lets look at an example component:
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn App() -> Element {
|
||||
let number = use_signal(|| 1); // Hook 1
|
||||
let string = use_signal(|| "hello world".to_string()); // Hook 2
|
||||
let doubled = use_memo(move || number() * 2); // Hook 3
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
When we first create the component, we run the hooks in the order they are defined and store the state in the component in a list.
|
||||
|
||||
```rust, ignore
|
||||
[
|
||||
Box::new(1),
|
||||
Box::new("hello world".to_string()),
|
||||
Box::new(2),
|
||||
]
|
||||
```
|
||||
|
||||
Next time we run the component, we return items from the state list instead of creating state again.
|
||||
|
||||
```rust, ignore
|
||||
[
|
||||
Box::new(1), // Hook 1 returns 1
|
||||
Box::new("hello world".to_string()), // Hook 2 returns "hello world"
|
||||
Box::new(2), // Hook 3 returns 2
|
||||
]
|
||||
```
|
||||
|
||||
The order the hooks are run it must be the same because the order determines which hook gets what state! If you run the hooks in a different order, dioxus may panic because it can't turn the state back into the right type or you may just get the wrong state for your hook.
|
||||
|
||||
</details>
|
||||
|
||||
</details>
|
102
packages/hooks/docs/side_effects.md
Normal file
102
packages/hooks/docs/side_effects.md
Normal file
|
@ -0,0 +1,102 @@
|
|||
Effects are reactive closures that run **after the component has finished rendering**. Effects are useful for things like manually updating the DOM after it is rendered with web-sys or javascript. Or reading a value from the rendered DOM.
|
||||
|
||||
**Effects are specifically created for side effects. If you are trying to derive state, use a [memo](#derived-state), or [resource](#derived-async-state) instead.**
|
||||
|
||||
If you are trying to update the DOM, you can use the [`use_effect`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_effect.html) hook to run an effect after the component has finished rendering.
|
||||
|
||||
`use_effect` will subscribe to any changes in the signal values it captures effects will always run after first mount and then whenever the signal values change. If the use_effect call was skipped due to an early return, the effect will no longer activate.
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn MyComponent() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
|
||||
use_effect(move || {
|
||||
// Effects are reactive like memos, and resources. If you read a value inside the effect, the effect will rerun when that value changes
|
||||
let count = count.read();
|
||||
|
||||
// You can use the count value to update the DOM manually
|
||||
eval(&format!(
|
||||
r#"var c = document.getElementById("dioxus-canvas");
|
||||
var ctx = c.getContext("2d");
|
||||
ctx.font = "30px Arial";
|
||||
ctx.fillText("{count}", 10, 50);"#
|
||||
));
|
||||
});
|
||||
|
||||
rsx! {
|
||||
button {
|
||||
// When you click the button, count will be incremented and the effect will rerun
|
||||
onclick: move |_| count += 1,
|
||||
"Increment"
|
||||
}
|
||||
canvas {
|
||||
id: "dioxus-canvas",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## With non-reactive dependencies
|
||||
|
||||
To add non-reactive dependencies, you can use the [`crate::use_reactive()`] hook.
|
||||
|
||||
Signals will automatically be added as dependencies, so you don't need to call this method for them.
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
# async fn sleep(delay: u32) {}
|
||||
|
||||
#[component]
|
||||
fn Comp(count: u32) -> Element {
|
||||
// Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.
|
||||
use_effect(use_reactive((&count,), |(count,)| println!("Manually manipulate the dom") ));
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
## Modifying mounted nodes
|
||||
|
||||
One of the most common use cases for effects is modifying or reading something from the rendered DOM. Dioxus provides access to the DOM with the [`onmounted`](https://docs.rs/dioxus/latest/dioxus/events/fn.onmounted.html) event.
|
||||
|
||||
You can combine `use_effect` with `onmounted` to run an effect with access to a DOM element after all rendering is finished:
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn MyComponent() -> Element {
|
||||
let mut current_text = use_signal(String::new);
|
||||
let mut mounted_text_div: Signal<Option<MountedEvent>> = use_signal(|| None);
|
||||
let mut rendered_size = use_signal(String::new);
|
||||
|
||||
use_effect(move || {
|
||||
// If we have mounted the text div, we can read the width of the div
|
||||
if let Some(div) = mounted_text_div() {
|
||||
// We read the current text here inside of the effect instead of the spawn so the effect subscribes to the signal
|
||||
let text = current_text();
|
||||
spawn(async move {
|
||||
let bounding_box = div.get_client_rect().await;
|
||||
rendered_size.set(format!("{text} is {bounding_box:?}"));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
rsx! {
|
||||
input {
|
||||
// When you enter text into the input, the effect will rerun because it subscribes to the current_text signal
|
||||
oninput: move |evt| current_text.set(evt.value()),
|
||||
placeholder: "Enter text here",
|
||||
value: "{current_text}"
|
||||
}
|
||||
// When text changes, it will change the size of this div
|
||||
div {
|
||||
onmounted: move |element| {
|
||||
mounted_text_div.set(Some(element.clone()));
|
||||
},
|
||||
"{current_text}"
|
||||
}
|
||||
|
||||
"{rendered_size}"
|
||||
}
|
||||
}
|
||||
```
|
149
packages/hooks/docs/use_resource.md
Normal file
149
packages/hooks/docs/use_resource.md
Normal file
|
@ -0,0 +1,149 @@
|
|||
[`use_resource()`] is a reactive hook that resolves to the result of a future. It will rerun when you write to any signals you read inside the future.
|
||||
|
||||
## Example
|
||||
|
||||
```rust
|
||||
use dioxus::prelude::*;
|
||||
|
||||
async fn get_weather(location: &WeatherLocation) -> Result<String, String> {
|
||||
Ok("Sunny".to_string())
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let country = use_signal(|| WeatherLocation {
|
||||
city: "Berlin".to_string(),
|
||||
country: "Germany".to_string(),
|
||||
coordinates: (52.5244, 13.4105),
|
||||
});
|
||||
|
||||
// Because the resource's future subscribes to `country` by reading it (`country.read()`),
|
||||
// every time `country` changes the resource's future will run again and thus provide a new value.
|
||||
let current_weather = use_resource(move || async move { get_weather(&country()).await });
|
||||
|
||||
rsx! {
|
||||
// the value of the resource can be polled to
|
||||
// conditionally render elements based off if it's future
|
||||
// finished (Some(Ok(_)), errored Some(Err(_)),
|
||||
// or is still running (None)
|
||||
match &*current_weather.read_unchecked() {
|
||||
Some(Ok(weather)) => rsx! { WeatherElement { weather } },
|
||||
Some(Err(e)) => rsx! { p { "Loading weather failed, {e}" } },
|
||||
None => rsx! { p { "Loading..." } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct WeatherLocation {
|
||||
city: String,
|
||||
country: String,
|
||||
coordinates: (f64, f64),
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn WeatherElement(weather: String) -> Element {
|
||||
rsx! { p { "The weather is {weather}" } }
|
||||
}
|
||||
```
|
||||
|
||||
## Reactivity
|
||||
|
||||
`use_resource` is reactive which just means that it will rerun when you write to any signals you read inside the future. This means that any time you change something the future depends on, the resource automatically knows to rerun. Lets take a look at some examples:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
// Create a new count signal
|
||||
let mut count = use_signal(|| 1);
|
||||
// Create a new resource that doubles the value of count
|
||||
let double_count = use_resource(move || async move {
|
||||
// Start a request to the server. We are reading the value of count in the format macro
|
||||
// Reading the value of count makes the resource "subscribe" to changes to count (when count changes, the resource will rerun)
|
||||
let response = reqwest::get(format!("https://myserver.com/doubleme?count={count}")).await.unwrap();
|
||||
response.text().await.unwrap()
|
||||
});
|
||||
|
||||
// Resource can be read in a way that is similar to signals, but they have a bit of extra information about the state of the resource future.
|
||||
|
||||
// Calling .state() on a resource will return a Signal<UseResourceState> with information about the current status of the resource
|
||||
println!("{:?}", double_count.state().read()); // Prints "UseResourceState::Pending"
|
||||
|
||||
// You can also try to get the last resolved value of the resource with the .value() method
|
||||
println!("{:?}", double_count.read()); // Prints "None"
|
||||
|
||||
// Wait for the resource to finish and get the value
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
|
||||
// Now if we read the state, we will see that it is done
|
||||
println!("{:?}", double_count.state().read()); // Prints "UseResourceState::Done"
|
||||
|
||||
// And we can get the value
|
||||
println!("{:?}", double_count.read()); // Prints "Some(2)"
|
||||
|
||||
// Now if we write to count, the resource will rerun
|
||||
count += 1; // count is now 2
|
||||
|
||||
// Wait for the resource to finish and get the value
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
|
||||
// Now if we read the state, we will see that it is done
|
||||
println!("{:?}", double_count.state().read()); // Prints "UseResourceState::Done"
|
||||
|
||||
// And we can get the value
|
||||
println!("{:?}", double_count.read()); // Prints "Some(4)"
|
||||
|
||||
// One more case, what happens if we write to the resource while it is in progress?
|
||||
// The resource will rerun and the value will be None
|
||||
count += 1; // count is now 3
|
||||
|
||||
// If we write to a value the resource subscribes to again, it will cancel the current future and start a new one
|
||||
count += 1; // count is now 4
|
||||
|
||||
println!("{:?}", double_count.state().read()); // Prints "UseResourceState::Stopped"
|
||||
println!("{:?}", double_count.read()); // Prints the last resolved value "Some(4)"
|
||||
|
||||
// After we wait for the resource to finish, we will get the value of only the latest future
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
|
||||
println!("{:?}", double_count.state().read()); // Prints "UseResourceState::Done"
|
||||
|
||||
println!("{:?}", double_count.read()); // Prints "Some(8)"
|
||||
```
|
||||
|
||||
## With non-reactive dependencies
|
||||
|
||||
`use_resource` can determine dependencies automatically with any reactive value ([`Signal`]s, [`ReadOnlySignal`]s, [`Memo`]s, [`Resource`]s, etc). If you need to rerun the future when a normal rust value changes, you can add it as a dependency with the [`crate::use_reactive()`] hook:
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
# async fn sleep(delay: u32) {}
|
||||
#[component]
|
||||
fn Comp(count: u32) -> Element {
|
||||
// We manually add the resource to the dependencies list with the `use_reactive` hook
|
||||
// Any time `count` changes, the resource will rerun
|
||||
let new_count = use_resource(use_reactive!(|(count,)| async move {
|
||||
sleep(100).await;
|
||||
count + 1
|
||||
}));
|
||||
rsx! { "{new_count:?}" }
|
||||
}
|
||||
|
||||
// If your value is already reactive, you never need to call `use_reactive` manually
|
||||
// Instead of manually adding count to the dependencies list, you can make your prop reactive by wrapping it in `ReadOnlySignal`
|
||||
#[component]
|
||||
fn ReactiveComp(count: ReadOnlySignal<u32>) -> Element {
|
||||
// Because `count` is reactive, the resource knows to rerun when `count` changes automatically
|
||||
let new_count = use_resource(move || async move {
|
||||
sleep(100).await;
|
||||
count() + 1
|
||||
});
|
||||
rsx! { "{new_count:?}" }
|
||||
}
|
||||
```
|
||||
|
||||
## Differences from `use_future` and `use_memo`
|
||||
|
||||
Just like [`crate::use_future()`], `use_resource` spawns an async task in a component. However, unlike [`crate::use_future()`], `use_resource` returns the result of the future and will rerun when any dependencies change.
|
||||
|
||||
Resources return a value based on some existing state just like memos, but unlike memos, resources do not memorize the output of the closure. They will always rerun any parts of your app that read the value of the resource when the future resolves even if the output doesn't change.
|
||||
|
||||
See also: [`Resource`]
|
|
@ -9,6 +9,7 @@ use dioxus_signals::Writable;
|
|||
/// There is *currently* no signal tracking on the Callback so anything reading from it will not be updated.
|
||||
///
|
||||
/// This API is in flux and might not remain.
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
pub fn use_callback<O>(f: impl FnMut() -> O + 'static) -> UseCallback<O> {
|
||||
// Create a copyvalue with no contents
|
||||
// This copyvalue is generic over F so that it can be sized properly
|
||||
|
@ -35,11 +36,24 @@ pub fn use_callback<O>(f: impl FnMut() -> O + 'static) -> UseCallback<O> {
|
|||
/// This callback is not generic over a return type so you can hold a bunch of callbacks at once
|
||||
///
|
||||
/// If you need a callback that returns a value, you can simply wrap the closure you pass in that sets a value in its scope
|
||||
#[derive(PartialEq)]
|
||||
pub struct UseCallback<O: 'static + ?Sized> {
|
||||
inner: CopyValue<Box<dyn FnMut() -> O>>,
|
||||
}
|
||||
|
||||
impl<O: 'static + ?Sized> PartialEq for UseCallback<O> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.inner == other.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: 'static + ?Sized> std::fmt::Debug for UseCallback<O> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("UseCallback")
|
||||
.field("inner", &self.inner.value())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<O: 'static + ?Sized> Clone for UseCallback<O> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { inner: self.inner }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
a form of use_state explicitly for map-style collections (BTreeMap, HashMap, etc).
|
||||
a form of use_signal explicitly for map-style collections (BTreeMap, HashMap, etc).
|
||||
|
||||
Why?
|
||||
---
|
||||
|
|
|
@ -6,6 +6,8 @@ use dioxus_core::{
|
|||
/// Consume some context in the tree, providing a sharable handle to the value
|
||||
///
|
||||
/// Does not regenerate the value if the value is changed at the parent.
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[must_use]
|
||||
pub fn try_use_context<T: 'static + Clone>() -> Option<T> {
|
||||
use_hook(|| try_consume_context::<T>())
|
||||
|
@ -29,6 +31,8 @@ pub fn try_use_context<T: 'static + Clone>() -> Option<T> {
|
|||
/// rsx! { "user using dark mode: {user_theme == Theme::Dark}" }
|
||||
/// }
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[must_use]
|
||||
pub fn use_context<T: 'static + Clone>() -> T {
|
||||
use_hook(|| consume_context::<T>())
|
||||
|
@ -56,6 +60,8 @@ pub fn use_context<T: 'static + Clone>() -> T {
|
|||
/// }
|
||||
///}
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
pub fn use_context_provider<T: 'static + Clone>(f: impl FnOnce() -> T) -> T {
|
||||
use_hook(|| provide_context(f()))
|
||||
}
|
||||
|
|
|
@ -6,11 +6,11 @@ use std::future::Future;
|
|||
|
||||
/// Maintain a handle over a future that can be paused, resumed, and canceled.
|
||||
///
|
||||
/// This is an upgraded form of [`use_future`] with an integrated channel system.
|
||||
/// Specifically, the coroutine generated here comes with an [`UnboundedChannel`]
|
||||
/// This is an upgraded form of [`crate::use_future()`] with an integrated channel system.
|
||||
/// Specifically, the coroutine generated here comes with an [`futures_channel::mpsc::UnboundedSender`]
|
||||
/// built into it - saving you the hassle of building your own.
|
||||
///
|
||||
/// Addititionally, coroutines are automatically injected as shared contexts, so
|
||||
/// Additionally, coroutines are automatically injected as shared contexts, so
|
||||
/// downstream components can tap into a coroutine's channel and send messages
|
||||
/// into a singular async event loop.
|
||||
///
|
||||
|
@ -33,7 +33,7 @@ use std::future::Future;
|
|||
/// ## UseCallback instead
|
||||
///
|
||||
/// However, you must plan out your own concurrency and synchronization. If you
|
||||
/// don't care about actions in your app being synchronized, you can use [`use_callback`]
|
||||
/// don't care about actions in your app being synchronized, you can use [`crate::use_callback()`]
|
||||
/// hook to spawn multiple tasks and run them concurrently.
|
||||
///
|
||||
/// ### Notice
|
||||
|
@ -42,7 +42,9 @@ use std::future::Future;
|
|||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// use futures_util::StreamExt;
|
||||
/// enum Action {
|
||||
/// Start,
|
||||
/// Stop,
|
||||
|
@ -63,8 +65,9 @@ use std::future::Future;
|
|||
/// onclick: move |_| chat_client.send(Action::Start),
|
||||
/// "Start Chat Service"
|
||||
/// }
|
||||
/// })
|
||||
/// };
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
pub fn use_coroutine<M, G, F>(init: G) -> Coroutine<M>
|
||||
where
|
||||
M: 'static,
|
||||
|
@ -96,6 +99,7 @@ where
|
|||
/// Analagous to use_context_provider and use_context,
|
||||
/// but used for coroutines specifically
|
||||
/// See the docs for [`use_coroutine`] for more details.
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[must_use]
|
||||
pub fn use_coroutine_handle<M: 'static>() -> Coroutine<M> {
|
||||
use_hook(consume_context::<Coroutine<M>>)
|
||||
|
|
|
@ -4,41 +4,8 @@ use futures_util::StreamExt;
|
|||
|
||||
use crate::use_callback;
|
||||
|
||||
/// `use_effect` will subscribe to any changes in the signal values it captures
|
||||
/// effects will always run after first mount and then whenever the signal values change
|
||||
/// If the use_effect call was skipped due to an early return, the effect will no longer activate.
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn app() -> Element {
|
||||
/// let mut count = use_signal(|| 0);
|
||||
/// //the effect runs again each time count changes
|
||||
/// use_effect(move || println!("Count changed to {count}"));
|
||||
///
|
||||
/// rsx! {
|
||||
/// h1 { "High-Five counter: {count}" }
|
||||
/// button { onclick: move |_| count += 1, "Up high!" }
|
||||
/// button { onclick: move |_| count -= 1, "Down low!" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## With non-reactive dependencies
|
||||
/// To add non-reactive dependencies, you can use the `use_reactive` hook.
|
||||
///
|
||||
/// Signals will automatically be added as dependencies, so you don't need to call this method for them.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # async fn sleep(delay: u32) {}
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Comp(count: u32) -> Element {
|
||||
/// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.
|
||||
/// use_effect(use_reactive((&count,), |(count,)| println!("Manually manipulate the dom") ));
|
||||
///
|
||||
/// todo!()
|
||||
/// }
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/side_effects.md")]
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[track_caller]
|
||||
pub fn use_effect(callback: impl FnMut() + 'static) -> Effect {
|
||||
let callback = use_callback(callback);
|
||||
|
|
|
@ -5,12 +5,16 @@ use dioxus_signals::*;
|
|||
use std::future::Future;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// A hook that allows you to spawn a future.
|
||||
/// This future will **not** run on the server
|
||||
/// The future is spawned on the next call to `wait_for_next_render` which means that it will not run on the server.
|
||||
/// To run a future on the server, you should use `spawn` directly.
|
||||
/// `use_future` **won't return a value**.
|
||||
/// If you want to return a value from a future, use `use_resource` instead.
|
||||
/// A hook that allows you to spawn a future the first time you render a component.
|
||||
///
|
||||
///
|
||||
/// This future will **not** run on the server. To run a future on the server, you should use [`spawn_isomorphic`] directly.
|
||||
///
|
||||
///
|
||||
/// `use_future` **won't return a value**. If you want to return a value from a future, use [`crate::use_resource()`] instead.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
|
@ -35,6 +39,9 @@ use std::ops::Deref;
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[doc(alias = "use_async")]
|
||||
pub fn use_future<F>(mut future: impl FnMut() -> F + 'static) -> UseFuture
|
||||
where
|
||||
F: Future + 'static,
|
||||
|
|
|
@ -2,6 +2,8 @@ use dioxus_core::prelude::*;
|
|||
use dioxus_signals::{CopyValue, Writable};
|
||||
|
||||
/// A hook that uses before/after lifecycle hooks to determine if the hook was run
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
pub fn use_hook_did_run(mut handler: impl FnMut(bool) + 'static) {
|
||||
let mut did_run_ = use_hook(|| CopyValue::new(false));
|
||||
|
||||
|
|
|
@ -2,41 +2,9 @@ use crate::use_callback;
|
|||
use dioxus_core::prelude::*;
|
||||
use dioxus_signals::{Memo, Signal};
|
||||
|
||||
/// Creates a new Memo. The memo will be run immediately and whenever any signal it reads changes.
|
||||
///
|
||||
/// Memos can be used to efficiently compute derived data from signals.
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
/// use dioxus_signals::*;
|
||||
///
|
||||
/// fn App() -> Element {
|
||||
/// let mut count = use_signal(|| 0);
|
||||
/// let double = use_memo(move || count * 2);
|
||||
/// count += 1;
|
||||
/// assert_eq!(double(), count * 2);
|
||||
///
|
||||
/// rsx! { "{double}" }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## With non-reactive dependencies
|
||||
/// To add non-reactive dependencies, you can use the `use_reactive` hook.
|
||||
///
|
||||
/// Signals will automatically be added as dependencies, so you don't need to call this method for them.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # async fn sleep(delay: u32) {}
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Comp(count: u32) -> Element {
|
||||
/// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.
|
||||
/// let new_count = use_memo(use_reactive((&count,), |(count,)| count + 1));
|
||||
///
|
||||
/// todo!()
|
||||
/// }
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/derived_state.md")]
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[track_caller]
|
||||
pub fn use_memo<R: PartialEq>(f: impl FnMut() -> R + 'static) -> Memo<R> {
|
||||
let callback = use_callback(f);
|
||||
|
|
|
@ -3,6 +3,14 @@ use dioxus_signals::{Readable, Writable};
|
|||
use crate::use_signal;
|
||||
|
||||
/// A dependency is a trait that can be used to determine if a effect or selector should be re-run.
|
||||
#[rustversion::attr(
|
||||
since(1.78.0),
|
||||
diagnostic::on_unimplemented(
|
||||
message = "`Dependency` is not implemented for `{Self}`",
|
||||
label = "Dependency",
|
||||
note = "Dependency is automatically implemented for all tuples with less than 8 references to element that implement `PartialEq` and `Clone`. For example, `(&A, &B, &C)` implements `Dependency` automatically as long as `A`, `B`, and `C` implement `PartialEq` and `Clone`.",
|
||||
)
|
||||
)]
|
||||
pub trait Dependency: Sized + Clone {
|
||||
/// The output of the dependency
|
||||
type Out: Clone + PartialEq + 'static;
|
||||
|
@ -20,6 +28,14 @@ impl Dependency for () {
|
|||
}
|
||||
|
||||
/// A dependency is a trait that can be used to determine if a effect or selector should be re-run.
|
||||
#[rustversion::attr(
|
||||
since(1.78.0),
|
||||
diagnostic::on_unimplemented(
|
||||
message = "`DependencyElement` is not implemented for `{Self}`",
|
||||
label = "dependency element",
|
||||
note = "DependencyElement is automatically implemented for types that implement `PartialEq` and `Clone`",
|
||||
)
|
||||
)]
|
||||
pub trait DependencyElement: 'static + PartialEq + Clone {}
|
||||
impl<T> DependencyElement for T where T: 'static + PartialEq + Clone {}
|
||||
|
||||
|
@ -83,6 +99,7 @@ impl_dep!(A = a1 a2, B = b1 b2, C = c1 c2, D = d1 d2, E = e1 e2, F = f1 f2, G =
|
|||
/// println!("Data changed: {}", data);
|
||||
/// }));
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
pub fn use_reactive<O, D: Dependency>(
|
||||
non_reactive_data: D,
|
||||
mut closure: impl FnMut(D::Out) -> O + 'static,
|
||||
|
@ -113,6 +130,7 @@ pub fn use_reactive<O, D: Dependency>(
|
|||
/// println!("Data changed: {}", data);
|
||||
/// }));
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[macro_export]
|
||||
macro_rules! use_reactive {
|
||||
(|| $($rest:tt)*) => { use_reactive( (), move |_| $($rest)* ) };
|
||||
|
|
|
@ -7,66 +7,11 @@ use futures_util::{future, pin_mut, FutureExt, StreamExt};
|
|||
use std::ops::Deref;
|
||||
use std::{cell::Cell, future::Future, rc::Rc};
|
||||
|
||||
/// A memo that resolves to a value asynchronously.
|
||||
/// Similar to `use_future` but `use_resource` returns a value.
|
||||
/// See [`Resource`] for more details.
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # #[derive(Clone)]
|
||||
/// # struct WeatherLocation {
|
||||
/// # city: String,
|
||||
/// # country: String,
|
||||
/// # coordinates: (f64, f64),
|
||||
/// # }
|
||||
/// # async fn get_weather(location: &WeatherLocation) -> Result<String, String> {
|
||||
/// # Ok("Sunny".to_string())
|
||||
/// # }
|
||||
/// # #[component]
|
||||
/// # fn WeatherElement(weather: String) -> Element {
|
||||
/// # rsx! { p { "The weather is {weather}" } }
|
||||
/// # }
|
||||
/// fn app() -> Element {
|
||||
/// let country = use_signal(|| WeatherLocation {
|
||||
/// city: "Berlin".to_string(),
|
||||
/// country: "Germany".to_string(),
|
||||
/// coordinates: (52.5244, 13.4105),
|
||||
/// });
|
||||
/// ///
|
||||
/// // Because the resource's future subscribes to `country` by reading it (`country.read()`),
|
||||
/// // every time `country` changes the resource's future will run again and thus provide a new value.
|
||||
/// let current_weather = use_resource(move || async move { get_weather(&country()).await });
|
||||
///
|
||||
/// rsx! {
|
||||
/// // the value of the resource can be polled to
|
||||
/// // conditionally render elements based off if it's future
|
||||
/// // finished (Some(Ok(_)), errored Some(Err(_)),
|
||||
/// // or is still running (None)
|
||||
/// match &*current_weather.read() {
|
||||
/// Some(Ok(weather)) => rsx! { WeatherElement { weather } },
|
||||
/// Some(Err(e)) => rsx! { p { "Loading weather failed, {e}" } },
|
||||
/// None => rsx! { p { "Loading..." } }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## With non-reactive dependencies
|
||||
/// To add non-reactive dependencies, you can use the `use_reactive` hook.
|
||||
///
|
||||
/// Signals will automatically be added as dependencies, so you don't need to call this method for them.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # async fn sleep(delay: u32) {}
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Comp(count: u32) -> Element {
|
||||
/// // Because the memo subscribes to `count` by adding it as a dependency, the memo will rerun every time `count` changes.
|
||||
/// let new_count = use_resource(use_reactive((&count,), |(count,)| async move {count + 1} ));
|
||||
///
|
||||
/// todo!()
|
||||
/// }
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/use_resource.md")]
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[doc(alias = "use_async_memo")]
|
||||
#[doc(alias = "use_memo_async")]
|
||||
#[must_use = "Consider using `cx.spawn` to run a future without reading its value"]
|
||||
#[track_caller]
|
||||
pub fn use_resource<T, F>(mut future: impl FnMut() -> F + 'static) -> Resource<T>
|
||||
|
@ -135,7 +80,32 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
/// A handle to a reactive future spawned with [`use_resource`] that can be used to modify or read the result of the future.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// Reading the result of a resource:
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// // Since our resource may not be ready yet, the value is an Option. Our request may also fail, so the get function returns a Result
|
||||
/// // The complete type we need to match is `Option<Result<String, reqwest::Error>>`
|
||||
/// // We can use `read_unchecked` to keep our matching code in one statement while avoiding a temporary variable error (this is still completely safe because dioxus checks the borrows at runtime)
|
||||
/// match &*resource.read_unchecked() {
|
||||
/// Some(Ok(value)) => rsx! { "{value:?}" },
|
||||
/// Some(Err(err)) => rsx! { "Error: {err}" },
|
||||
/// None => rsx! { "Loading..." },
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Resource<T: 'static> {
|
||||
value: Signal<Option<T>>,
|
||||
task: Signal<Task>,
|
||||
|
@ -143,6 +113,15 @@ pub struct Resource<T: 'static> {
|
|||
callback: UseCallback<Task>,
|
||||
}
|
||||
|
||||
impl<T> PartialEq for Resource<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.value == other.value
|
||||
&& self.state == other.state
|
||||
&& self.task == other.task
|
||||
&& self.callback == other.callback
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Resource<T> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
|
@ -172,6 +151,28 @@ impl<T> Resource<T> {
|
|||
///
|
||||
/// Will not cancel the previous future, but will ignore any values that it
|
||||
/// generates.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// // We can get a signal with the value of the resource with the `value` method
|
||||
/// onclick: move |_| resource.restart(),
|
||||
/// "Restart resource"
|
||||
/// }
|
||||
/// "{resource:?}"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn restart(&mut self) {
|
||||
self.task.write().cancel();
|
||||
let new_task = self.callback.call();
|
||||
|
@ -179,18 +180,93 @@ impl<T> Resource<T> {
|
|||
}
|
||||
|
||||
/// Forcefully cancel the resource's future.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// // We can cancel the resource before it finishes with the `cancel` method
|
||||
/// onclick: move |_| resource.cancel(),
|
||||
/// "Cancel resource"
|
||||
/// }
|
||||
/// "{resource:?}"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn cancel(&mut self) {
|
||||
self.state.set(UseResourceState::Stopped);
|
||||
self.task.write().cancel();
|
||||
}
|
||||
|
||||
/// Pause the resource's future.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// // We can pause the future with the `pause` method
|
||||
/// onclick: move |_| resource.pause(),
|
||||
/// "Pause"
|
||||
/// }
|
||||
/// button {
|
||||
/// // And resume it with the `resume` method
|
||||
/// onclick: move |_| resource.resume(),
|
||||
/// "Resume"
|
||||
/// }
|
||||
/// "{resource:?}"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn pause(&mut self) {
|
||||
self.state.set(UseResourceState::Paused);
|
||||
self.task.write().pause();
|
||||
}
|
||||
|
||||
/// Resume the resource's future.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// // We can pause the future with the `pause` method
|
||||
/// onclick: move |_| resource.pause(),
|
||||
/// "Pause"
|
||||
/// }
|
||||
/// button {
|
||||
/// // And resume it with the `resume` method
|
||||
/// onclick: move |_| resource.resume(),
|
||||
/// "Resume"
|
||||
/// }
|
||||
/// "{resource:?}"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn resume(&mut self) {
|
||||
if self.finished() {
|
||||
return;
|
||||
|
@ -200,7 +276,29 @@ impl<T> Resource<T> {
|
|||
self.task.write().resume();
|
||||
}
|
||||
|
||||
/// Clear the resource's value.
|
||||
/// Clear the resource's value. This will just reset the value. It will not modify any running tasks.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// // We clear the value without modifying any running tasks with the `clear` method
|
||||
/// onclick: move |_| resource.clear(),
|
||||
/// "Clear"
|
||||
/// }
|
||||
/// "{resource:?}"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn clear(&mut self) {
|
||||
self.value.write().take();
|
||||
}
|
||||
|
@ -214,6 +312,30 @@ impl<T> Resource<T> {
|
|||
/// Is the resource's future currently finished running?
|
||||
///
|
||||
/// Reading this does not subscribe to the future's state
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// // We can use the `finished` method to check if the future is finished
|
||||
/// if resource.finished() {
|
||||
/// rsx! {
|
||||
/// "The resource is finished"
|
||||
/// }
|
||||
/// } else {
|
||||
/// rsx! {
|
||||
/// "The resource is still running"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn finished(&self) -> bool {
|
||||
matches!(
|
||||
*self.state.peek(),
|
||||
|
@ -221,12 +343,67 @@ impl<T> Resource<T> {
|
|||
)
|
||||
}
|
||||
|
||||
/// Get the current state of the resource's future.
|
||||
/// Get the current state of the resource's future. This method returns a [`ReadOnlySignal`] which can be read to get the current state of the resource or passed to other hooks and components.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// // We can read the current state of the future with the `state` method
|
||||
/// match resource.state().cloned() {
|
||||
/// UseResourceState::Pending => rsx! {
|
||||
/// "The resource is still pending"
|
||||
/// },
|
||||
/// UseResourceState::Paused => rsx! {
|
||||
/// "The resource has been paused"
|
||||
/// },
|
||||
/// UseResourceState::Stopped => rsx! {
|
||||
/// "The resource has been stopped"
|
||||
/// },
|
||||
/// UseResourceState::Ready => rsx! {
|
||||
/// "The resource is ready!"
|
||||
/// },
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn state(&self) -> ReadOnlySignal<UseResourceState> {
|
||||
self.state.into()
|
||||
}
|
||||
|
||||
/// Get the current value of the resource's future.
|
||||
/// Get the current value of the resource's future. This method returns a [`ReadOnlySignal`] which can be read to get the current value of the resource or passed to other hooks and components.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use std::time::Duration;
|
||||
/// fn App() -> Element {
|
||||
/// let mut revision = use_signal(|| "1d03b42");
|
||||
/// let mut resource = use_resource(move || async move {
|
||||
/// // This will run every time the revision signal changes because we read the count inside the future
|
||||
/// reqwest::get(format!("https://github.com/DioxusLabs/awesome-dioxus/blob/{revision}/awesome.json")).await
|
||||
/// });
|
||||
///
|
||||
/// // We can get a signal with the value of the resource with the `value` method
|
||||
/// let value = resource.value();
|
||||
///
|
||||
/// // Since our resource may not be ready yet, the value is an Option. Our request may also fail, so the get function returns a Result
|
||||
/// // The complete type we need to match is `Option<Result<String, reqwest::Error>>`
|
||||
/// // We can use `read_unchecked` to keep our matching code in one statement while avoiding a temporary variable error (this is still completely safe because dioxus checks the borrows at runtime)
|
||||
/// match &*value.read_unchecked() {
|
||||
/// Some(Ok(value)) => rsx! { "{value:?}" },
|
||||
/// Some(Err(err)) => rsx! { "Error: {err}" },
|
||||
/// None => rsx! { "Loading..." },
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn value(&self) -> ReadOnlySignal<Option<T>> {
|
||||
self.value.into()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,22 @@
|
|||
use dioxus_core::{prelude::provide_root_context, prelude::try_consume_context, use_hook};
|
||||
|
||||
/// Try to get a value from the root of the virtual dom, if it doesn't exist, create a new one with the closure provided.
|
||||
///
|
||||
/// This is useful for global context inside of libraries. Instead of having the user provide context in the root of their app, you can use this hook to create a context at the root automatically.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # #[derive(Clone)]
|
||||
/// # struct Logger;
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// fn use_logger() -> Logger {
|
||||
/// // We want one logger per app in the root. Instead of forcing the user to always provide a logger, we can insert a default logger if one doesn't exist.
|
||||
/// use_root_context(|| Logger)
|
||||
/// }
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
pub fn use_root_context<T: 'static + Clone>(new: impl FnOnce() -> T) -> T {
|
||||
use_hook(|| {
|
||||
try_consume_context::<T>()
|
||||
|
|
|
@ -5,7 +5,7 @@ use dioxus_signals::{ReadOnlySignal, SetCompare};
|
|||
|
||||
/// Creates a new SetCompare which efficiently tracks when a value changes to check if it is equal to a set of values.
|
||||
///
|
||||
/// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_memo`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to.
|
||||
/// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_memo()`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to.
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
|
@ -38,12 +38,16 @@ use dioxus_signals::{ReadOnlySignal, SetCompare};
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[must_use]
|
||||
pub fn use_set_compare<R: Eq + Hash>(f: impl FnMut() -> R + 'static) -> SetCompare<R> {
|
||||
use_hook(move || SetCompare::new(f))
|
||||
}
|
||||
|
||||
/// A hook that returns true if the value is equal to the value in the set compare.
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[must_use]
|
||||
pub fn use_set_compare_equal<R: Eq + Hash>(
|
||||
value: R,
|
||||
|
|
|
@ -30,6 +30,10 @@ use dioxus_signals::{Signal, SignalData, Storage, SyncStorage, UnsyncStorage};
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
#[doc = include_str!("../docs/rules_of_hooks.md")]
|
||||
#[doc = include_str!("../docs/moving_state_around.md")]
|
||||
#[doc(alias = "use_state")]
|
||||
#[track_caller]
|
||||
#[must_use]
|
||||
pub fn use_signal<T: 'static>(f: impl FnOnce() -> T) -> Signal<T, UnsyncStorage> {
|
||||
|
@ -68,6 +72,7 @@ pub fn use_signal<T: 'static>(f: impl FnOnce() -> T) -> Signal<T, UnsyncStorage>
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[doc(alias = "use_rw")]
|
||||
#[must_use]
|
||||
#[track_caller]
|
||||
pub fn use_signal_sync<T: Send + Sync + 'static>(f: impl FnOnce() -> T) -> Signal<T, SyncStorage> {
|
||||
|
|
|
@ -27,6 +27,7 @@ rfd = { version = "0.14", optional = true }
|
|||
futures-channel = { workspace = true }
|
||||
serde_json = { version = "1", optional = true }
|
||||
tracing.workspace = true
|
||||
rustversion = "1.0.17"
|
||||
|
||||
[dependencies.web-sys]
|
||||
optional = true
|
||||
|
@ -50,6 +51,9 @@ features = [
|
|||
|
||||
[dev-dependencies]
|
||||
serde_json = "1"
|
||||
dioxus = { workspace = true }
|
||||
dioxus-web = { workspace = true }
|
||||
tokio = { workspace = true, features = ["time"] }
|
||||
|
||||
[features]
|
||||
default = ["serialize", "mounted", "eval"]
|
||||
|
|
133
packages/html/docs/common_event_handler_errors.md
Normal file
133
packages/html/docs/common_event_handler_errors.md
Normal file
|
@ -0,0 +1,133 @@
|
|||
## Compiler errors you may run into while using event handlers
|
||||
|
||||
<details>
|
||||
<summary>function requires argument type to outlive `'static`</summary>
|
||||
|
||||
Event handler in Dioxus need only access data that can last for the entire lifetime of the application. That generally means data that is moved into the closure. **If you get this error, you may have forgotten to add `move` to your closure.**
|
||||
|
||||
Broken component:
|
||||
|
||||
```rust, compile_fail
|
||||
# use dioxus::prelude::*;
|
||||
// We return an Element which can last as long as the component is on the screen
|
||||
fn App() -> Element {
|
||||
// Signals are `Copy` which makes them very easy to move into `'static` closures like event handlers
|
||||
let state = use_signal(|| "hello world".to_string());
|
||||
|
||||
rsx! {
|
||||
button {
|
||||
// ❌ Without `move`, rust will try to borrow the `state` signal which fails because the state signal is dropped at the end of the function
|
||||
onclick: |_| {
|
||||
println!("You clicked the button! The state is: {state}");
|
||||
},
|
||||
"Click me"
|
||||
}
|
||||
}
|
||||
// The state signal is dropped here, but the event handler still needs to access it
|
||||
}
|
||||
```
|
||||
|
||||
Fixed component:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
fn App() -> Element {
|
||||
let state = use_signal(|| "hello world".to_string());
|
||||
|
||||
rsx! {
|
||||
button {
|
||||
// ✅ The `move` keyword tells rust it can move the `state` signal into the closure. Since the closure owns the signal state, it can read it even after the function returns
|
||||
onclick: move |_| {
|
||||
println!("You clicked the button! The state is: {state}");
|
||||
},
|
||||
"Click me"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>use of moved value: `your_value` value used here after move</summary>
|
||||
|
||||
Data in rust has a single owner. If you run into this error, you have likely tried to move data that isn't `Copy` into two different closures. **You can fix this issue by making your data `Copy` or calling `clone` on it before you move it into the closure.**
|
||||
|
||||
Broken component:
|
||||
|
||||
```rust, compile_fail
|
||||
# use dioxus::prelude::*;
|
||||
// `MyComponent` accepts a string which cannot be copied implicitly
|
||||
#[component]
|
||||
fn MyComponent(string: String) -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
// ❌ We are moving the string into the onclick handler which means we can't access it elsewhere
|
||||
onclick: move |_| {
|
||||
println!("{string}");
|
||||
},
|
||||
"Print hello world"
|
||||
}
|
||||
button {
|
||||
// ❌ Since we already moved the string, we can't move it into the onclick handler again. This will cause a compiler error
|
||||
onclick: move |_| {
|
||||
println!("{string}");
|
||||
},
|
||||
"Print hello world again"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can fix this issue by either:
|
||||
|
||||
- Making your data `Copy` with `ReadOnlySignal`:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
// `MyComponent` accepts `ReadOnlySignal<String>` which implements `Copy`
|
||||
#[component]
|
||||
fn MyComponent(string: ReadOnlySignal<String>) -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
// ✅ Because the `string` signal is `Copy`, we can copy it into the closure while still having access to it elsewhere
|
||||
onclick: move |_| println!("{}", string),
|
||||
"Print hello world"
|
||||
}
|
||||
button {
|
||||
// ✅ Since `string` is `Copy`, we can move it into the onclick handler again
|
||||
onclick: move |_| println!("{}", string),
|
||||
"Print hello world again"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Calling `clone` on your data before you move it into the closure:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
// `MyComponent` accepts a string which doesn't implement `Copy`
|
||||
#[component]
|
||||
fn MyComponent(string: String) -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
// ✅ The string only has one owner. We could move it into this closure, but since we want to use the string in other closures later, we will clone it instead
|
||||
onclick: {
|
||||
// Clone the string in a new block
|
||||
let string = string.clone();
|
||||
// Then move the cloned string into the closure
|
||||
move |_| println!("{}", string)
|
||||
},
|
||||
"Print hello world"
|
||||
}
|
||||
button {
|
||||
// ✅ We don't use the string after this closure, so we can just move it into the closure directly
|
||||
onclick: move |_| println!("{}", string),
|
||||
"Print hello world again"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
125
packages/html/docs/eval.md
Normal file
125
packages/html/docs/eval.md
Normal file
|
@ -0,0 +1,125 @@
|
|||
# Communicating with JavaScript
|
||||
|
||||
You can use the `eval` function to execute JavaScript code in your application with the desktop, mobile, web or liveview renderers. Eval takes a block of JavaScript code (that may be asynchronous) and returns a `UseEval` object that you can use to send data to the JavaScript code and receive data from it.
|
||||
|
||||
<div class="warning">
|
||||
|
||||
## Safety
|
||||
|
||||
Please be careful when executing JavaScript code with `eval`. You should only execute code that you trust. **This applies especially to web targets, where the JavaScript context has access to most, if not all of your application data.** Running untrusted code can lead to a [cross-site scripting](https://developer.mozilla.org/en-US/docs/Glossary/Cross-site_scripting) (XSS) vulnerability.
|
||||
|
||||
</div>
|
||||
|
||||
```rust
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn App() -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
onclick: move |_| async move {
|
||||
// Eval is a global function you can use anywhere inside Dioxus. It will execute the given JavaScript code.
|
||||
let result = eval(r#"console.log("Hello World");
|
||||
return "Hello World";"#);
|
||||
|
||||
// You can use the `await` keyword to wait for the result of the JavaScript code.
|
||||
println!("{:?}", result.await);
|
||||
},
|
||||
"Log Hello World"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Sending data to JavaScript
|
||||
|
||||
When you execute JavaScript code with `eval`, you can pass data to it by formatting the value into the JavaScript code or sending values to the `UseEval` channel.
|
||||
|
||||
```rust
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
onclick: move |_| {
|
||||
// You can pass initial data to the eval function by formatting it into the JavaScript code.
|
||||
const LOOP_COUNT: usize = 10;
|
||||
let eval = eval(&format!(r#"for(let i = 0; i < {LOOP_COUNT}; i++) {{
|
||||
// You can receive values asynchronously with the the `await dioxus.recv()` method.
|
||||
let value = await dioxus.recv();
|
||||
console.log("Received", value);
|
||||
}}"#));
|
||||
|
||||
// You can send values from rust to the JavaScript code with the `send` method on the object returned by `eval`.
|
||||
for i in 0..LOOP_COUNT {
|
||||
eval.send(i.into()).unwrap();
|
||||
}
|
||||
},
|
||||
"Log Count"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Sending data from JavaScript
|
||||
|
||||
The `UseEval` struct also contains methods for receiving values you send from JavaScript. You can use the `dioxus.send()` method to send values to the JavaScript code and the `UseEval::recv()` method to receive values from the JavaScript code.
|
||||
|
||||
```rust
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
onclick: move |_| async move {
|
||||
// You can send values from rust to the JavaScript code by using the `send` method on the object returned by `eval`.
|
||||
let mut eval = eval(r#"for(let i = 0; i < 10; i++) {
|
||||
// You can send values asynchronously with the `dioxus.send()` method.
|
||||
dioxus.send(i);
|
||||
}"#);
|
||||
|
||||
// You can receive values from the JavaScript code with the `recv` method on the object returned by `eval`.
|
||||
for _ in 0..10 {
|
||||
let value = eval.recv().await.unwrap();
|
||||
println!("Received {}", value);
|
||||
}
|
||||
},
|
||||
"Log Count"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Interacting with the DOM with Eval
|
||||
|
||||
You can also use the `eval` function to execute JavaScript code that reads or modifies the DOM. If you want to interact with the mounted DOM, you need to use `eval` inside the [`dioxus_hooks::use_effect`] hook which runs after the component has been mounted.
|
||||
|
||||
```rust
|
||||
use dioxus::prelude::*;
|
||||
|
||||
const SCRIPT: &str = r#"
|
||||
let element = document.getElementById("my-element");
|
||||
element.innerHTML = "Hello World";
|
||||
return element.getAttribute("data-count");
|
||||
"#;
|
||||
|
||||
fn app() -> Element {
|
||||
// ❌ You shouldn't run eval in the body of a component. This will run before the component has been mounted
|
||||
// eval(SCRIPT);
|
||||
|
||||
// ✅ You should run eval inside an effect or event. This will run after the component has been mounted
|
||||
use_effect(move || {
|
||||
spawn(async {
|
||||
let count = eval(SCRIPT).await;
|
||||
println!("Count is {:?}", count);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
id: "my-element",
|
||||
"data-count": "123",
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
86
packages/html/docs/event_handlers.md
Normal file
86
packages/html/docs/event_handlers.md
Normal file
|
@ -0,0 +1,86 @@
|
|||
# Event Handlers
|
||||
|
||||
Event Handlers let you react to user input in your application. In Dioxus, event handlers accept a closure that is called when the event occurs:
|
||||
|
||||
```rust, no_run
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn App() -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
// The `onclick` event accepts a closure with the signature `fn(Event)`
|
||||
onclick: |event_data| println!("clicked! I got the event data: {event_data:?}"),
|
||||
"Click me"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Event Lifetimes
|
||||
|
||||
Events take a closure with the `'static` lifetime. This means that the closure can only access data that either exists for the entire lifetime of the application, or data that you move into the closure.
|
||||
|
||||
State in dioxus is `copy` which makes it very easy to move into `'static` closures like event handlers:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
let mut count = use_signal(|| 0);
|
||||
|
||||
rsx! {
|
||||
button {
|
||||
// Since we added the `move` keyword, the closure will move the `count` signal into the closure
|
||||
onclick: move |_| {
|
||||
// This will panic because the `count` signal is not in scope
|
||||
count.set(count() + 1);
|
||||
},
|
||||
"Click me"
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
If you need to access data that is not `Copy`, you may need to clone the data before you move it into the closure:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
// String is not `Copy`
|
||||
let string = "hello world".to_string();
|
||||
|
||||
rsx! {
|
||||
button {
|
||||
// The string only has one owner. We could move it into this closure, but since we want to use the string in other closures later, we will clone it instead
|
||||
onclick: {
|
||||
// Clone the string in a new block
|
||||
let string = string.clone();
|
||||
// Then move the cloned string into the closure
|
||||
move |_| println!("{}", string)
|
||||
},
|
||||
"Print hello world"
|
||||
}
|
||||
button {
|
||||
// We don't use the string after this closure, so we can just move it into the closure directly
|
||||
onclick: move |_| println!("{}", string),
|
||||
"Print hello world again"
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Async Event Handlers
|
||||
|
||||
In addition to closures that return nothing, you can also use async closures to handle events. If you return an async block from an event handler, dioxus will automatically spawn it:
|
||||
|
||||
```rust, no_run
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn App() -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
// The `onclick` event can also accept a closure that returns an async block
|
||||
onclick: move |_| async move {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
|
||||
println!("You clicked the button one second ago!");
|
||||
},
|
||||
"Click me"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
File diff suppressed because it is too large
Load diff
|
@ -13,37 +13,147 @@ pub type AttributeDiscription = (&'static str, Option<&'static str>, bool);
|
|||
|
||||
macro_rules! impl_attribute {
|
||||
(
|
||||
$element:ident {
|
||||
$(#[$attr_method:meta])*
|
||||
$fil:ident: $vil:ident (DEFAULT),
|
||||
}
|
||||
) => {
|
||||
$(#[$attr_method])*
|
||||
///
|
||||
/// ## Usage in rsx
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
#[doc = concat!("let ", stringify!($fil), " = \"value\";")]
|
||||
///
|
||||
/// rsx! {
|
||||
/// // Attributes need to be under the element they modify
|
||||
#[doc = concat!(" ", stringify!($element), " {")]
|
||||
/// // Attributes are followed by a colon and then the value of the attribute
|
||||
#[doc = concat!(" ", stringify!($fil), ": \"value\"")]
|
||||
/// }
|
||||
#[doc = concat!(" ", stringify!($element), " {")]
|
||||
/// // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute
|
||||
#[doc = concat!(" ", stringify!($fil), ",")]
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub const $fil: AttributeDiscription = (stringify!($fil), None, false);
|
||||
};
|
||||
|
||||
(
|
||||
$element:ident {
|
||||
$(#[$attr_method:meta])*
|
||||
$fil:ident: $vil:ident ($name:literal),
|
||||
}
|
||||
) => {
|
||||
$(#[$attr_method])*
|
||||
///
|
||||
/// ## Usage in rsx
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
#[doc = concat!("let ", stringify!($fil), " = \"value\";")]
|
||||
///
|
||||
/// rsx! {
|
||||
/// // Attributes need to be under the element they modify
|
||||
#[doc = concat!(" ", stringify!($element), " {")]
|
||||
/// // Attributes are followed by a colon and then the value of the attribute
|
||||
#[doc = concat!(" ", stringify!($fil), ": \"value\"")]
|
||||
/// }
|
||||
#[doc = concat!(" ", stringify!($element), " {")]
|
||||
/// // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute
|
||||
#[doc = concat!(" ", stringify!($fil), ",")]
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub const $fil: AttributeDiscription = ($name, None, false);
|
||||
};
|
||||
|
||||
(
|
||||
$element:ident {
|
||||
$(#[$attr_method:meta])*
|
||||
$fil:ident: $vil:ident (volatile),
|
||||
}
|
||||
) => {
|
||||
$(#[$attr_method])*
|
||||
///
|
||||
/// ## Usage in rsx
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
#[doc = concat!("let ", stringify!($fil), " = \"value\";")]
|
||||
///
|
||||
/// rsx! {
|
||||
/// // Attributes need to be under the element they modify
|
||||
#[doc = concat!(" ", stringify!($element), " {")]
|
||||
/// // Attributes are followed by a colon and then the value of the attribute
|
||||
#[doc = concat!(" ", stringify!($fil), ": \"value\"")]
|
||||
/// }
|
||||
#[doc = concat!(" ", stringify!($element), " {")]
|
||||
/// // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute
|
||||
#[doc = concat!(" ", stringify!($fil), ",")]
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub const $fil: AttributeDiscription = (stringify!($fil), None, true);
|
||||
};
|
||||
|
||||
(
|
||||
$element:ident {
|
||||
$(#[$attr_method:meta])*
|
||||
$fil:ident: $vil:ident (in $ns:literal),
|
||||
}
|
||||
) => {
|
||||
$(#[$attr_method])*
|
||||
///
|
||||
/// ## Usage in rsx
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
#[doc = concat!("let ", stringify!($fil), " = \"value\";")]
|
||||
///
|
||||
/// rsx! {
|
||||
/// // Attributes need to be under the element they modify
|
||||
#[doc = concat!(" ", stringify!($element), " {")]
|
||||
/// // Attributes are followed by a colon and then the value of the attribute
|
||||
#[doc = concat!(" ", stringify!($fil), ": \"value\"")]
|
||||
/// }
|
||||
#[doc = concat!(" ", stringify!($element), " {")]
|
||||
/// // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute
|
||||
#[doc = concat!(" ", stringify!($fil), ",")]
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub const $fil: AttributeDiscription = (stringify!($fil), Some($ns), false)
|
||||
};
|
||||
|
||||
(
|
||||
$element:ident {
|
||||
$(#[$attr_method:meta])*
|
||||
$fil:ident: $vil:ident (in $ns:literal : volatile),
|
||||
}
|
||||
) => {
|
||||
$(#[$attr_method])*
|
||||
///
|
||||
/// ## Usage in rsx
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
#[doc = concat!("let ", stringify!($fil), " = \"value\";")]
|
||||
///
|
||||
/// rsx! {
|
||||
/// // Attributes need to be under the element they modify
|
||||
#[doc = concat!(" ", stringify!($element), " {")]
|
||||
/// // Attributes are followed by a colon and then the value of the attribute
|
||||
#[doc = concat!(" ", stringify!($fil), ": \"value\"")]
|
||||
/// }
|
||||
#[doc = concat!(" ", stringify!($element), " {")]
|
||||
/// // Or you can use the shorthand syntax if you have a variable in scope that has the same name as the attribute
|
||||
#[doc = concat!(" ", stringify!($fil), ",")]
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub const $fil: AttributeDiscription = (stringify!($fil), Some($ns), true)
|
||||
};
|
||||
}
|
||||
|
@ -114,6 +224,30 @@ macro_rules! impl_element {
|
|||
) => {
|
||||
#[allow(non_camel_case_types)]
|
||||
$(#[$attr])*
|
||||
///
|
||||
/// ## Usage in rsx
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # let attributes = vec![];
|
||||
/// # fn ChildComponent() -> Element { todo!() }
|
||||
/// # let raw_expression: Element = rsx! {};
|
||||
/// rsx! {
|
||||
/// // Elements are followed by braces that surround any attributes and children for that element
|
||||
#[doc = concat!(" ", stringify!($name), " {")]
|
||||
/// // Add any attributes first
|
||||
/// class: "my-class",
|
||||
/// "custom-attribute-name": "value",
|
||||
/// // Then add any attributes you are spreading into this element
|
||||
/// ..attributes,
|
||||
/// // Then add any children elements, components, text nodes, or raw expressions
|
||||
/// div {}
|
||||
/// ChildComponent {}
|
||||
/// "child text"
|
||||
/// {raw_expression}
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub mod $name {
|
||||
#[allow(unused)]
|
||||
use super::*;
|
||||
|
@ -124,8 +258,10 @@ macro_rules! impl_element {
|
|||
|
||||
$(
|
||||
impl_attribute!(
|
||||
$name {
|
||||
$(#[$attr_method])*
|
||||
$fil: $vil ($extra),
|
||||
}
|
||||
);
|
||||
)*
|
||||
}
|
||||
|
@ -141,6 +277,30 @@ macro_rules! impl_element {
|
|||
}
|
||||
) => {
|
||||
$(#[$attr])*
|
||||
///
|
||||
/// ## Usage in rsx
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # let attributes = vec![];
|
||||
/// # fn ChildComponent() -> Element { todo!() }
|
||||
/// # let raw_expression: Element = rsx! {};
|
||||
/// rsx! {
|
||||
/// // Elements are followed by braces that surround any attributes and children for that element
|
||||
#[doc = concat!(" ", stringify!($name), " {")]
|
||||
/// // Add any attributes first
|
||||
/// color: "red",
|
||||
/// "custom-attribute-name": "value",
|
||||
/// // Then add any attributes you are spreading into this element
|
||||
/// ..attributes,
|
||||
/// // Then add any children elements, components, text nodes, or raw expressions
|
||||
/// circle { cx: "10", cy: "10", r: "2", fill: "red" }
|
||||
/// ChildComponent {}
|
||||
/// "child text"
|
||||
/// {raw_expression}
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub mod $name {
|
||||
#[allow(unused)]
|
||||
use super::*;
|
||||
|
@ -151,8 +311,10 @@ macro_rules! impl_element {
|
|||
|
||||
$(
|
||||
impl_attribute!(
|
||||
$name {
|
||||
$(#[$attr_method])*
|
||||
$fil: $vil ($extra),
|
||||
}
|
||||
);
|
||||
)*
|
||||
}
|
||||
|
@ -169,6 +331,30 @@ macro_rules! impl_element {
|
|||
) => {
|
||||
#[allow(non_camel_case_types)]
|
||||
$(#[$attr])*
|
||||
///
|
||||
/// ## Usage in rsx
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # let attributes = vec![];
|
||||
/// # fn ChildComponent() -> Element { todo!() }
|
||||
/// # let raw_expression: Element = rsx! {};
|
||||
/// rsx! {
|
||||
/// // Elements are followed by braces that surround any attributes and children for that element
|
||||
#[doc = concat!(" ", stringify!($element), " {")]
|
||||
/// // Add any attributes first
|
||||
/// color: "red",
|
||||
/// "custom-attribute-name": "value",
|
||||
/// // Then add any attributes you are spreading into this element
|
||||
/// ..attributes,
|
||||
/// // Then add any children elements, components, text nodes, or raw expressions
|
||||
/// circle { cx: "10", cy: "10", r: "2", fill: "red" }
|
||||
/// ChildComponent {}
|
||||
/// "child text"
|
||||
/// {raw_expression}
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub mod $element {
|
||||
#[allow(unused)]
|
||||
use super::*;
|
||||
|
@ -179,8 +365,10 @@ macro_rules! impl_element {
|
|||
|
||||
$(
|
||||
impl_attribute!(
|
||||
$element {
|
||||
$(#[$attr_method])*
|
||||
$fil: $vil ($extra),
|
||||
}
|
||||
);
|
||||
)*
|
||||
}
|
||||
|
@ -390,6 +578,30 @@ macro_rules! builder_constructors {
|
|||
pub enum CompleteWithBraces {
|
||||
$(
|
||||
$(#[$attr])*
|
||||
///
|
||||
/// ## Usage in rsx
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # let attributes = vec![];
|
||||
/// # fn ChildComponent() -> Element { todo!() }
|
||||
/// # let raw_expression: Element = rsx! {};
|
||||
/// rsx! {
|
||||
/// // Elements are followed by braces that surround any attributes and children for that element
|
||||
#[doc = concat!(" ", stringify!($name), " {")]
|
||||
/// // Add any attributes first
|
||||
/// class: "my-class",
|
||||
/// "custom-attribute-name": "value",
|
||||
/// // Then add any attributes you are spreading into this element
|
||||
/// ..attributes,
|
||||
/// // Then add any children elements, components, text nodes, or raw expressions
|
||||
/// div {}
|
||||
/// ChildComponent {}
|
||||
/// "child text"
|
||||
/// {raw_expression}
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
$name {}
|
||||
),*
|
||||
}
|
||||
|
@ -521,14 +733,6 @@ builder_constructors! {
|
|||
/// - The most important heading is `<h1>` and the least important heading is `<h6>`.
|
||||
/// - The `<h1>` heading is the first heading in the document.
|
||||
/// - The `<h1>` heading is usually a large bolded font.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// html!(<h1> A header element </h1>)
|
||||
/// rsx!(h1 { "A header element" })
|
||||
/// LazyNodes::new(|f| f.el(h1).children([f.text("A header element")]).finish())
|
||||
/// ```
|
||||
h1 None {};
|
||||
|
||||
/// Build a
|
||||
|
@ -541,13 +745,6 @@ builder_constructors! {
|
|||
/// - The most important heading is `<h1>` and the least important heading is `<h6>`.
|
||||
/// - The `<h2>` heading is the second heading in the document.
|
||||
/// - The `<h2>` heading is usually a large bolded font.
|
||||
///
|
||||
/// # Usage
|
||||
/// ```rust, ignore
|
||||
/// html!(<h2> A header element </h2>)
|
||||
/// rsx!(h2 { "A header element" })
|
||||
/// LazyNodes::new(|f| f.el(h2).children([f.text("A header element")]).finish())
|
||||
/// ```
|
||||
h2 None {};
|
||||
|
||||
/// Build a
|
||||
|
@ -556,10 +753,10 @@ builder_constructors! {
|
|||
///
|
||||
/// # About
|
||||
/// - The HTML <h1> element is found within the <body> tag.
|
||||
/// - Headings can range from <h1> to <h6>.
|
||||
/// - The most important heading is <h1> and the least important heading is <h6>.
|
||||
/// - The <h1> heading is the first heading in the document.
|
||||
/// - The <h1> heading is usually a large bolded font.
|
||||
/// - Headings can range from `<h1>` to `<h6>`.
|
||||
/// - The most important heading is `<h1>`` and the least important heading is `<h6>`.
|
||||
/// - The `<h1>` heading is the first heading in the document.
|
||||
/// - The `<h1>` heading is usually a large bolded font.
|
||||
h3 None {};
|
||||
|
||||
/// Build a
|
||||
|
@ -612,20 +809,13 @@ builder_constructors! {
|
|||
/// Part of the HTML namespace. Only works in HTML-compatible renderers
|
||||
///
|
||||
/// ## Definition and Usage
|
||||
/// - The <div> tag defines a division or a section in an HTML document.
|
||||
/// - The <div> tag is used as a container for HTML elements - which is then styled with CSS or manipulated with JavaScript.
|
||||
/// - The <div> tag is easily styled by using the class or id attribute.
|
||||
/// - Any sort of content can be put inside the <div> tag!
|
||||
/// - The `<div>` tag defines a division or a section in an HTML document.
|
||||
/// - The `<div>` tag is used as a container for HTML elements - which is then styled with CSS or manipulated with JavaScript.
|
||||
/// - The `<div>` tag is easily styled by using the class or id attribute.
|
||||
/// - Any sort of content can be put inside the `<div>` tag!
|
||||
///
|
||||
/// Note: By default, browsers always place a line break before and after the <div> element.
|
||||
///
|
||||
/// ## Usage
|
||||
/// ```rust, ignore
|
||||
/// html!(<div> A header element </div>)
|
||||
/// rsx!(div { "A header element" })
|
||||
/// LazyNodes::new(|f| f.element(div, &[], &[], &[], None))
|
||||
/// ```
|
||||
///
|
||||
/// ## References:
|
||||
/// - <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div>
|
||||
/// - <https://www.w3schools.com/tags/tag_div.asp>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#![allow(clippy::await_holding_refcell_ref)]
|
||||
#![doc = include_str!("../docs/eval.md")]
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};
|
||||
|
@ -8,7 +9,7 @@ use std::rc::Rc;
|
|||
use std::task::{Context, Poll};
|
||||
|
||||
/// A struct that implements EvalProvider is sent through [`ScopeState`]'s provide_context function
|
||||
/// so that [`use_eval`] can provide a platform agnostic interface for evaluating JavaScript code.
|
||||
/// so that [`eval`] can provide a platform agnostic interface for evaluating JavaScript code.
|
||||
pub trait EvalProvider {
|
||||
fn new_evaluator(&self, js: String) -> GenerationalBox<Box<dyn Evaluator>>;
|
||||
}
|
||||
|
@ -47,6 +48,8 @@ pub fn eval_provider() -> EvalCreator {
|
|||
as Rc<dyn Fn(&str) -> UseEval>
|
||||
}
|
||||
|
||||
#[doc = include_str!("../docs/eval.md")]
|
||||
#[doc(alias = "javascript")]
|
||||
pub fn eval(script: &str) -> UseEval {
|
||||
let eval_provider = dioxus_core::prelude::try_consume_context::<Rc<dyn EvalProvider>>()
|
||||
// Create a dummy provider that always hiccups when trying to evaluate
|
||||
|
@ -84,7 +87,9 @@ pub fn eval(script: &str) -> UseEval {
|
|||
UseEval::new(eval_provider.new_evaluator(script.to_string()))
|
||||
}
|
||||
|
||||
/// A wrapper around the target platform's evaluator.
|
||||
/// A wrapper around the target platform's evaluator that lets you send and receive data from JavaScript spawned by [`eval`].
|
||||
///
|
||||
#[doc = include_str!("../docs/eval.md")]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct UseEval {
|
||||
evaluator: GenerationalBox<Box<dyn Evaluator>>,
|
||||
|
|
|
@ -30,7 +30,7 @@ impl FormValue {
|
|||
self.0.first().unwrap().clone()
|
||||
}
|
||||
|
||||
/// Convert into Vec<String>
|
||||
/// Convert into [`Vec<String>`]
|
||||
pub fn to_vec(self) -> Vec<String> {
|
||||
self.0.clone()
|
||||
}
|
||||
|
@ -287,7 +287,50 @@ impl_event! {
|
|||
/// onchange
|
||||
onchange
|
||||
|
||||
/// oninput handler
|
||||
/// The `oninput` event is fired when the value of a `<input>`, `<select>`, or `<textarea>` element is changed.
|
||||
///
|
||||
/// There are two main approaches to updating your input element:
|
||||
/// 1) Controlled inputs directly update the value of the input element as the user interacts with the element
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// fn App() -> Element {
|
||||
/// let mut value = use_signal(|| "hello world".to_string());
|
||||
///
|
||||
/// rsx! {
|
||||
/// input {
|
||||
/// // We directly set the value of the input element to our value signal
|
||||
/// value: "{value}",
|
||||
/// // The `oninput` event handler will run every time the user changes the value of the input element
|
||||
/// // We can set the `value` signal to the new value of the input element
|
||||
/// oninput: move |event| value.set(event.value())
|
||||
/// }
|
||||
/// // Since this is a controlled input, we can also update the value of the input element directly
|
||||
/// button {
|
||||
/// onclick: move |_| value.write().clear(),
|
||||
/// "Clear"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// 2) Uncontrolled inputs just read the value of the input element as it changes
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// fn App() -> Element {
|
||||
/// rsx! {
|
||||
/// input {
|
||||
/// // In uncontrolled inputs, we don't set the value of the input element directly
|
||||
/// // But you can still read the value of the input element
|
||||
/// oninput: move |event| println!("{}", event.value()),
|
||||
/// }
|
||||
/// // Since we don't directly control the value of the input element, we can't easily modify it
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
oninput
|
||||
|
||||
/// oninvalid
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
#![doc = include_str!("../../docs/event_handlers.md")]
|
||||
|
||||
use std::any::Any;
|
||||
use std::sync::RwLock;
|
||||
|
||||
|
@ -11,6 +13,17 @@ macro_rules! impl_event {
|
|||
) => {
|
||||
$(
|
||||
$( #[$attr] )*
|
||||
/// <details open>
|
||||
/// <summary>General Event Handler Information</summary>
|
||||
///
|
||||
#[doc = include_str!("../../docs/event_handlers.md")]
|
||||
///
|
||||
/// </details>
|
||||
///
|
||||
#[doc = include_str!("../../docs/common_event_handler_errors.md")]
|
||||
$(
|
||||
#[doc(alias = $js_name)]
|
||||
)?
|
||||
#[inline]
|
||||
pub fn $name<E: crate::EventReturn<T>, T>(mut _f: impl FnMut(::dioxus_core::Event<$data>) -> E + 'static) -> ::dioxus_core::Attribute {
|
||||
::dioxus_core::Attribute::new(
|
||||
|
@ -356,6 +369,15 @@ pub fn event_bubbles(evt: &str) -> bool {
|
|||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[rustversion::attr(
|
||||
since(1.78.0),
|
||||
diagnostic::on_unimplemented(
|
||||
message = "`EventHandlerReturn` is not implemented for `{Self}`",
|
||||
label = "Return Value",
|
||||
note = "Event handlers in dioxus need to return either: nothing (the unit type `()`), or an async block that dioxus will automatically spawn",
|
||||
note = "You likely need to add a semicolon to the end of the event handler to make it return nothing",
|
||||
)
|
||||
)]
|
||||
pub trait EventReturn<P>: Sized {
|
||||
fn spawn(self) {}
|
||||
}
|
|
@ -53,6 +53,7 @@ impl RenderedElementBacking for () {
|
|||
|
||||
/// The way that scrolling should be performed
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[doc(alias = "ScrollIntoViewOptions")]
|
||||
pub enum ScrollBehavior {
|
||||
/// Scroll to the element immediately
|
||||
#[cfg_attr(feature = "serialize", serde(rename = "instant"))]
|
||||
|
@ -84,21 +85,27 @@ impl MountedData {
|
|||
}
|
||||
|
||||
/// Get the number of pixels that an element's content is scrolled
|
||||
#[doc(alias = "scrollTop")]
|
||||
#[doc(alias = "scrollLeft")]
|
||||
pub async fn get_scroll_offset(&self) -> MountedResult<PixelsVector2D> {
|
||||
self.inner.get_scroll_offset().await
|
||||
}
|
||||
|
||||
/// Get the size of an element's content, including content not visible on the screen due to overflow
|
||||
#[doc(alias = "scrollWidth")]
|
||||
#[doc(alias = "scrollHeight")]
|
||||
pub async fn get_scroll_size(&self) -> MountedResult<PixelsSize> {
|
||||
self.inner.get_scroll_size().await
|
||||
}
|
||||
|
||||
/// Get the bounding rectangle of the element relative to the viewport (this does not include the scroll position)
|
||||
#[doc(alias = "getBoundingClientRect")]
|
||||
pub async fn get_client_rect(&self) -> MountedResult<PixelsRect> {
|
||||
self.inner.get_client_rect().await
|
||||
}
|
||||
|
||||
/// Scroll to make the element visible
|
||||
#[doc(alias = "scrollIntoView")]
|
||||
pub fn scroll_to(
|
||||
&self,
|
||||
behavior: ScrollBehavior,
|
||||
|
@ -107,6 +114,8 @@ impl MountedData {
|
|||
}
|
||||
|
||||
/// Set the focus on the element
|
||||
#[doc(alias = "focus")]
|
||||
#[doc(alias = "blur")]
|
||||
pub fn set_focus(&self, focus: bool) -> Pin<Box<dyn Future<Output = MountedResult<()>>>> {
|
||||
self.inner.set_focus(focus)
|
||||
}
|
||||
|
@ -126,7 +135,64 @@ pub type MountedEvent = Event<MountedData>;
|
|||
impl_event! [
|
||||
MountedData;
|
||||
|
||||
/// mounted
|
||||
#[doc(alias = "ref")]
|
||||
#[doc(alias = "createRef")]
|
||||
#[doc(alias = "useRef")]
|
||||
/// The onmounted event is fired when the element is first added to the DOM. This event gives you a [`MountedData`] object and lets you interact with the raw DOM element.
|
||||
///
|
||||
/// This event is fired once per element. If you need to access the element multiple times, you can store the [`MountedData`] object in a [`use_signal`] hook and use it as needed.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn App() -> Element {
|
||||
/// let mut header_element = use_signal(|| None);
|
||||
///
|
||||
/// rsx! {
|
||||
/// div {
|
||||
/// h1 {
|
||||
/// // The onmounted event will run the first time the h1 element is mounted
|
||||
/// onmounted: move |element| header_element.set(Some(element.data())),
|
||||
/// "Scroll to top example"
|
||||
/// }
|
||||
///
|
||||
/// for i in 0..100 {
|
||||
/// div { "Item {i}" }
|
||||
/// }
|
||||
///
|
||||
/// button {
|
||||
/// // When you click the button, if the header element has been mounted, we scroll to that element
|
||||
/// onclick: move |_| async move {
|
||||
/// if let Some(header) = header_element.cloned() {
|
||||
/// let _ = header.scroll_to(ScrollBehavior::Smooth).await;
|
||||
/// }
|
||||
/// },
|
||||
/// "Scroll to top"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// The `MountedData` struct contains cross platform APIs that work on the desktop, mobile, liveview and web platforms. For the web platform, you can also downcast the `MountedData` event to the `web-sys::Element` type for more web specific APIs:
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # use dioxus_web::WebEventExt;
|
||||
/// fn App() -> Element {
|
||||
/// rsx! {
|
||||
/// div {
|
||||
/// id: "some-id",
|
||||
/// onmounted: move |element| {
|
||||
/// // You can use the web_event trait to downcast the element to a web specific event. For the mounted event, this will be a web_sys::Element
|
||||
/// let web_sys_element = element.web_event();
|
||||
/// assert_eq!(web_sys_element.id(), "some-id");
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
onmounted
|
||||
];
|
||||
|
||||
|
|
|
@ -238,6 +238,7 @@ mod segment;
|
|||
/// # #[component]
|
||||
/// # fn Home() -> Element { None }
|
||||
/// ```
|
||||
#[doc(alias = "route")]
|
||||
#[proc_macro_derive(
|
||||
Routable,
|
||||
attributes(route, nest, end_nest, layout, end_layout, redirect, child)
|
||||
|
|
|
@ -30,6 +30,7 @@ http = { workspace = true, optional = true }
|
|||
dioxus-fullstack = { workspace = true, optional = true }
|
||||
tokio = { workspace = true, features = ["full"], optional = true }
|
||||
dioxus-cli-config = { workspace = true, features = ["read-config"] }
|
||||
rustversion = "1.0.17"
|
||||
|
||||
# you need to comment this out when publishing since cargo workspaces is not smart enough to wipe this when dropping
|
||||
# dev-dependncey crates
|
||||
|
|
|
@ -40,6 +40,7 @@ use crate::utils::use_router_internal::use_router_internal;
|
|||
/// # vdom.rebuild_in_place();
|
||||
/// # assert_eq!(dioxus_ssr::render(&vdom), "<h1>App</h1><h2>Current Path</h2><p>/</p>")
|
||||
/// ```
|
||||
#[doc(alias = "use_url")]
|
||||
#[must_use]
|
||||
pub fn use_route<R: Routable + Clone>() -> R {
|
||||
match use_router_internal() {
|
||||
|
|
|
@ -7,7 +7,8 @@ pub fn use_router() -> RouterContext {
|
|||
use_router_internal().expect("use_route must have access to a router")
|
||||
}
|
||||
|
||||
/// Aquire the router without subscribing to updates.
|
||||
/// Acquire the router without subscribing to updates.
|
||||
#[doc(alias = "url")]
|
||||
pub fn router() -> RouterContext {
|
||||
dioxus_lib::prelude::consume_context()
|
||||
}
|
||||
|
|
|
@ -24,11 +24,57 @@ impl<E: Display> Display for RouteParseError<E> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Something that can be created from an entire query string.
|
||||
/// Something that can be created from an entire query string. This trait must be implemented for any type that is spread into the query segment like `#[route("/?:..query")]`.
|
||||
///
|
||||
/// This trait needs to be implemented if you want to turn a query string into a struct.
|
||||
///
|
||||
/// A working example can be found in the `examples` folder in the root package under `query_segments_demo`.
|
||||
/// **This trait is automatically implemented for any types that implement `From<&str>`.**
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// #[derive(Routable, Clone, PartialEq, Debug)]
|
||||
/// enum Route {
|
||||
/// // FromQuery must be implemented for any types you spread into the query segment
|
||||
/// #[route("/?:..query")]
|
||||
/// Home {
|
||||
/// query: CustomQuery
|
||||
/// },
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Default, Clone, PartialEq, Debug)]
|
||||
/// struct CustomQuery {
|
||||
/// count: i32,
|
||||
/// }
|
||||
///
|
||||
/// // We implement From<&str> for CustomQuery so that FromQuery is implemented automatically
|
||||
/// impl From<&str> for CustomQuery {
|
||||
/// fn from(query: &str) -> Self {
|
||||
/// CustomQuery {
|
||||
/// count: query.parse().unwrap_or(0),
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // We also need to implement Display for CustomQuery which will be used to format the query string into the URL
|
||||
/// impl std::fmt::Display for CustomQuery {
|
||||
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
/// write!(f, "{}", self.count)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # #[component]
|
||||
/// # fn Home(query: CustomQuery) -> Element {
|
||||
/// # todo!()
|
||||
/// # }
|
||||
/// ```
|
||||
#[rustversion::attr(
|
||||
since(1.78.0),
|
||||
diagnostic::on_unimplemented(
|
||||
message = "`FromQuery` is not implemented for `{Self}`",
|
||||
label = "spread query",
|
||||
note = "FromQuery is automatically implemented for types that implement `From<&str>`. You need to either implement From<&str> or implement FromQuery manually."
|
||||
)
|
||||
)]
|
||||
pub trait FromQuery {
|
||||
/// Create an instance of `Self` from a query string.
|
||||
fn from_query(query: &str) -> Self;
|
||||
|
@ -40,9 +86,63 @@ impl<T: for<'a> From<&'a str>> FromQuery for T {
|
|||
}
|
||||
}
|
||||
|
||||
/// Something that can be created from a query argument.
|
||||
/// Something that can be created from a query argument. This trait must be implemented for any type that is used as a query argument like `#[route("/?:query")]`.
|
||||
///
|
||||
/// This trait must be implemented for every type used within a query string in the router macro.
|
||||
/// **This trait is automatically implemented for any types that implement `FromStr` and `Default`.**
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// #[derive(Routable, Clone, PartialEq, Debug)]
|
||||
/// enum Route {
|
||||
/// // FromQuerySegment must be implemented for any types you use in the query segment
|
||||
/// // When you don't spread the query, you can parse multiple values form the query
|
||||
/// // This url will be in the format `/?query=123&other=456`
|
||||
/// #[route("/?:query&:other")]
|
||||
/// Home {
|
||||
/// query: CustomQuery,
|
||||
/// other: i32,
|
||||
/// },
|
||||
/// }
|
||||
///
|
||||
/// // We can derive Default for CustomQuery
|
||||
/// // If the router fails to parse the query value, it will use the default value instead
|
||||
/// #[derive(Default, Clone, PartialEq, Debug)]
|
||||
/// struct CustomQuery {
|
||||
/// count: i32,
|
||||
/// }
|
||||
///
|
||||
/// // We implement FromStr for CustomQuery so that FromQuerySegment is implemented automatically
|
||||
/// impl std::str::FromStr for CustomQuery {
|
||||
/// type Err = <i32 as std::str::FromStr>::Err;
|
||||
///
|
||||
/// fn from_str(query: &str) -> Result<Self, Self::Err> {
|
||||
/// Ok(CustomQuery {
|
||||
/// count: query.parse()?,
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // We also need to implement Display for CustomQuery which will be used to format the query string into the URL
|
||||
/// impl std::fmt::Display for CustomQuery {
|
||||
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
/// write!(f, "{}", self.count)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # #[component]
|
||||
/// # fn Home(query: CustomQuery, other: i32) -> Element {
|
||||
/// # todo!()
|
||||
/// # }
|
||||
/// ```
|
||||
#[rustversion::attr(
|
||||
since(1.78.0),
|
||||
diagnostic::on_unimplemented(
|
||||
message = "`FromQueryArgument` is not implemented for `{Self}`",
|
||||
label = "query argument",
|
||||
note = "FromQueryArgument is automatically implemented for types that implement `FromStr` and `Default`. You need to either implement FromStr and Default or implement FromQueryArgument manually."
|
||||
)
|
||||
)]
|
||||
pub trait FromQueryArgument: Default {
|
||||
/// The error that can occur when parsing a query argument.
|
||||
type Err;
|
||||
|
@ -68,9 +168,10 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Something that can be created from an entire hash fragment.
|
||||
/// Something that can be created from an entire hash fragment. This must be implemented for any type that is used as a hash fragment like `#[route("/#:hash_fragment")]`.
|
||||
///
|
||||
/// This trait needs to be implemented if you want to turn a hash fragment into a struct.
|
||||
///
|
||||
/// **This trait is automatically implemented for any types that implement `FromStr` and `Default`.**
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
@ -123,6 +224,15 @@ where
|
|||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[rustversion::attr(
|
||||
since(1.78.0),
|
||||
diagnostic::on_unimplemented(
|
||||
message = "`FromHashFragment` is not implemented for `{Self}`",
|
||||
label = "hash fragment",
|
||||
note = "FromHashFragment is automatically implemented for types that implement `FromStr` and `Default`. You need to either implement FromStr and Default or implement FromHashFragment manually."
|
||||
)
|
||||
)]
|
||||
pub trait FromHashFragment {
|
||||
/// Create an instance of `Self` from a hash fragment.
|
||||
fn from_hash_fragment(hash: &str) -> Self;
|
||||
|
@ -144,7 +254,64 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Something that can be created from a route segment.
|
||||
/// Something that can be created from a single route segment. This must be implemented for any type that is used as a route segment like `#[route("/:route_segment")]`.
|
||||
///
|
||||
///
|
||||
/// **This trait is automatically implemented for any types that implement `FromStr` and `Default`.**
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// #[derive(Routable, Clone, PartialEq, Debug)]
|
||||
/// enum Route {
|
||||
/// // FromRouteSegment must be implemented for any types you use in the route segment
|
||||
/// // When you don't spread the route, you can parse multiple values from the route
|
||||
/// // This url will be in the format `/123/456`
|
||||
/// #[route("/:route_segment_one/:route_segment_two")]
|
||||
/// Home {
|
||||
/// route_segment_one: CustomRouteSegment,
|
||||
/// route_segment_two: i32,
|
||||
/// },
|
||||
/// }
|
||||
///
|
||||
/// // We can derive Default for CustomRouteSegment
|
||||
/// // If the router fails to parse the route segment, it will use the default value instead
|
||||
/// #[derive(Default, PartialEq, Clone, Debug)]
|
||||
/// struct CustomRouteSegment {
|
||||
/// count: i32,
|
||||
/// }
|
||||
///
|
||||
/// // We implement FromStr for CustomRouteSegment so that FromRouteSegment is implemented automatically
|
||||
/// impl std::str::FromStr for CustomRouteSegment {
|
||||
/// type Err = <i32 as std::str::FromStr>::Err;
|
||||
///
|
||||
/// fn from_str(route_segment: &str) -> Result<Self, Self::Err> {
|
||||
/// Ok(CustomRouteSegment {
|
||||
/// count: route_segment.parse()?,
|
||||
/// })
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // We also need to implement Display for CustomRouteSegment which will be used to format the route segment into the URL
|
||||
/// impl std::fmt::Display for CustomRouteSegment {
|
||||
/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
/// write!(f, "{}", self.count)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # #[component]
|
||||
/// # fn Home(route_segment_one: CustomRouteSegment, route_segment_two: i32) -> Element {
|
||||
/// # todo!()
|
||||
/// # }
|
||||
/// ```
|
||||
#[rustversion::attr(
|
||||
since(1.78.0),
|
||||
diagnostic::on_unimplemented(
|
||||
message = "`FromRouteSegment` is not implemented for `{Self}`",
|
||||
label = "route segment",
|
||||
note = "FromRouteSegment is automatically implemented for types that implement `FromStr` and `Default`. You need to either implement FromStr and Default or implement FromRouteSegment manually."
|
||||
)
|
||||
)]
|
||||
pub trait FromRouteSegment: Sized {
|
||||
/// The error that can occur when parsing a route segment.
|
||||
type Err;
|
||||
|
@ -170,7 +337,68 @@ fn full_circle() {
|
|||
assert_eq!(String::from_route_segment(route).unwrap(), route);
|
||||
}
|
||||
|
||||
/// Something that can be converted to route segments.
|
||||
/// Something that can be converted into multiple route segments. This must be implemented for any type that is spread into the route segment like `#[route("/:..route_segments")]`.
|
||||
///
|
||||
///
|
||||
/// **This trait is automatically implemented for any types that implement `IntoIterator<Item=impl Display>`.**
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// #[derive(Routable, Clone, PartialEq, Debug)]
|
||||
/// enum Route {
|
||||
/// // FromRouteSegments must be implemented for any types you use in the route segment
|
||||
/// // When you spread the route, you can parse multiple values from the route
|
||||
/// // This url will be in the format `/123/456/789`
|
||||
/// #[route("/:..numeric_route_segments")]
|
||||
/// Home {
|
||||
/// numeric_route_segments: NumericRouteSegments,
|
||||
/// },
|
||||
/// }
|
||||
///
|
||||
/// // We can derive Default for NumericRouteSegments
|
||||
/// // If the router fails to parse the route segment, it will use the default value instead
|
||||
/// #[derive(Default, PartialEq, Clone, Debug)]
|
||||
/// struct NumericRouteSegments {
|
||||
/// numbers: Vec<i32>,
|
||||
/// }
|
||||
///
|
||||
/// // Implement ToRouteSegments for NumericRouteSegments so that we can display the route segments
|
||||
/// impl ToRouteSegments for &NumericRouteSegments {
|
||||
/// fn display_route_segments(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
/// for number in &self.numbers {
|
||||
/// write!(f, "/{}", number)?;
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // We also need to parse the route segments with `FromRouteSegments`
|
||||
/// impl FromRouteSegments for NumericRouteSegments {
|
||||
/// type Err = <i32 as std::str::FromStr>::Err;
|
||||
///
|
||||
/// fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err> {
|
||||
/// let mut numbers = Vec::new();
|
||||
/// for segment in segments {
|
||||
/// numbers.push(segment.parse()?);
|
||||
/// }
|
||||
/// Ok(NumericRouteSegments { numbers })
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # #[component]
|
||||
/// # fn Home(numeric_route_segments: NumericRouteSegments) -> Element {
|
||||
/// # todo!()
|
||||
/// # }
|
||||
/// ```
|
||||
#[rustversion::attr(
|
||||
since(1.78.0),
|
||||
diagnostic::on_unimplemented(
|
||||
message = "`ToRouteSegments` is not implemented for `{Self}`",
|
||||
label = "route segment",
|
||||
note = "ToRouteSegments is automatically implemented for types that implement `IntoIterator` with an `Item` type that implements `Display`. You need to either implement IntoIterator or implement ToRouteSegments manually."
|
||||
)
|
||||
)]
|
||||
pub trait ToRouteSegments {
|
||||
/// Display the route segments. You must url encode the segments.
|
||||
fn display_route_segments(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
|
||||
|
@ -205,7 +433,68 @@ fn to_route_segments() {
|
|||
assert_eq!(DisplaysRoute.to_string(), "/hello/world");
|
||||
}
|
||||
|
||||
/// Something that can be created from route segments.
|
||||
/// Something that can be created from multiple route segments. This must be implemented for any type that is spread into the route segment like `#[route("/:..route_segments")]`.
|
||||
///
|
||||
///
|
||||
/// **This trait is automatically implemented for any types that implement `FromIterator<impl Display>`.**
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// #[derive(Routable, Clone, PartialEq, Debug)]
|
||||
/// enum Route {
|
||||
/// // FromRouteSegments must be implemented for any types you use in the route segment
|
||||
/// // When you spread the route, you can parse multiple values from the route
|
||||
/// // This url will be in the format `/123/456/789`
|
||||
/// #[route("/:..numeric_route_segments")]
|
||||
/// Home {
|
||||
/// numeric_route_segments: NumericRouteSegments,
|
||||
/// },
|
||||
/// }
|
||||
///
|
||||
/// // We can derive Default for NumericRouteSegments
|
||||
/// // If the router fails to parse the route segment, it will use the default value instead
|
||||
/// #[derive(Default, Clone, PartialEq, Debug)]
|
||||
/// struct NumericRouteSegments {
|
||||
/// numbers: Vec<i32>,
|
||||
/// }
|
||||
///
|
||||
/// // Implement ToRouteSegments for NumericRouteSegments so that we can display the route segments
|
||||
/// impl ToRouteSegments for &NumericRouteSegments {
|
||||
/// fn display_route_segments(self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
/// for number in &self.numbers {
|
||||
/// write!(f, "/{}", number)?;
|
||||
/// }
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // We also need to parse the route segments with `FromRouteSegments`
|
||||
/// impl FromRouteSegments for NumericRouteSegments {
|
||||
/// type Err = <i32 as std::str::FromStr>::Err;
|
||||
///
|
||||
/// fn from_route_segments(segments: &[&str]) -> Result<Self, Self::Err> {
|
||||
/// let mut numbers = Vec::new();
|
||||
/// for segment in segments {
|
||||
/// numbers.push(segment.parse()?);
|
||||
/// }
|
||||
/// Ok(NumericRouteSegments { numbers })
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// # #[component]
|
||||
/// # fn Home(numeric_route_segments: NumericRouteSegments) -> Element {
|
||||
/// # todo!()
|
||||
/// # }
|
||||
/// ```
|
||||
#[rustversion::attr(
|
||||
since(1.78.0),
|
||||
diagnostic::on_unimplemented(
|
||||
message = "`FromRouteSegments` is not implemented for `{Self}`",
|
||||
label = "spread route segments",
|
||||
note = "FromRouteSegments is automatically implemented for types that implement `FromIterator` with an `Item` type that implements `Display`. You need to either implement FromIterator or implement FromRouteSegments manually."
|
||||
)
|
||||
)]
|
||||
pub trait FromRouteSegments: Sized {
|
||||
/// The error that can occur when parsing route segments.
|
||||
type Err;
|
||||
|
@ -233,12 +522,80 @@ type SiteMapFlattened<'a> = FlatMap<
|
|||
fn(&SiteMapSegment) -> Vec<Vec<SegmentType>>,
|
||||
>;
|
||||
|
||||
/// Something that can be:
|
||||
/// The Routable trait is implemented for types that can be converted to and from a route and be rendered as a page.
|
||||
///
|
||||
/// A Routable object is something that can be:
|
||||
/// 1. Converted from a route.
|
||||
/// 2. Converted to a route.
|
||||
/// 3. Rendered as a component.
|
||||
///
|
||||
/// This trait can be derived using the `#[derive(Routable)]` macro.
|
||||
/// This trait should generally be derived using the [`dioxus_router_macro::Routable`] macro which has more information about the syntax.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
///
|
||||
/// fn App() -> Element {
|
||||
/// rsx! {
|
||||
/// Router::<Route> { }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Routes are generally enums that derive `Routable`
|
||||
/// #[derive(Routable, Clone, PartialEq, Debug)]
|
||||
/// enum Route {
|
||||
/// // Each enum has an associated url
|
||||
/// #[route("/")]
|
||||
/// Home {},
|
||||
/// // Routes can include dynamic segments that are parsed from the url
|
||||
/// #[route("/blog/:blog_id")]
|
||||
/// Blog { blog_id: usize },
|
||||
/// // Or query segments that are parsed from the url
|
||||
/// #[route("/edit?:blog_id")]
|
||||
/// Edit { blog_id: usize },
|
||||
/// // Or hash segments that are parsed from the url
|
||||
/// #[route("/hashtag/#:hash")]
|
||||
/// Hash { hash: String },
|
||||
/// }
|
||||
///
|
||||
/// // Each route variant defaults to rendering a component of the same name
|
||||
/// #[component]
|
||||
/// fn Home() -> Element {
|
||||
/// rsx! {
|
||||
/// h1 { "Home" }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // If the variant has dynamic parameters, those are passed to the component
|
||||
/// #[component]
|
||||
/// fn Blog(blog_id: usize) -> Element {
|
||||
/// rsx! {
|
||||
/// h1 { "Blog" }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Edit(blog_id: usize) -> Element {
|
||||
/// rsx! {
|
||||
/// h1 { "Edit" }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Hash(hash: String) -> Element {
|
||||
/// rsx! {
|
||||
/// h1 { "Hashtag #{hash}" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[rustversion::attr(
|
||||
since(1.78.0),
|
||||
diagnostic::on_unimplemented(
|
||||
message = "`Routable` is not implemented for `{Self}`",
|
||||
label = "Route",
|
||||
note = "Routable should generally be derived using the `#[derive(Routable)]` macro."
|
||||
)
|
||||
)]
|
||||
pub trait Routable: FromStr + Display + Clone + 'static {
|
||||
/// The error that can occur when parsing a route.
|
||||
const SITE_MAP: &'static [SiteMapSegment];
|
||||
|
|
|
@ -28,6 +28,8 @@ dioxus = { workspace = true }
|
|||
tokio = { version = "1", features = ["full"] }
|
||||
tracing-subscriber = "0.3.17"
|
||||
simple_logger = "4.2.0"
|
||||
reqwest = { workspace = true }
|
||||
rand = "0.8"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
147
packages/signals/docs/memo.md
Normal file
147
packages/signals/docs/memo.md
Normal file
|
@ -0,0 +1,147 @@
|
|||
Memos are the result of computing a value from `use_memo`.
|
||||
|
||||
You may have noticed that this struct doesn't have many methods. Most methods for `Memo` are defined on the [`Readable`] and [`Writable`] traits.
|
||||
|
||||
# Reading a Memo
|
||||
|
||||
You can use the methods on the `Readable` trait to read a memo:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
// The memo will rerun any time we write to the count signal
|
||||
let halved = use_memo(move || count() / 2);
|
||||
|
||||
rsx! {
|
||||
// When we read the value of memo, the current component will subscribe to the result of the memo. It will only rerun when the result of the memo changes.
|
||||
"{halved}"
|
||||
button {
|
||||
onclick: move |_| {
|
||||
count += 1;
|
||||
},
|
||||
"Increment"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Memo also includes helper methods just like [`Signal`]s to make it easier to use. Calling a memo like a function will clone the inner value:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
|
||||
fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
// The memo will rerun any time we write to the count signal
|
||||
let halved = use_memo(move || count() / 2);
|
||||
// This will rerun any time the halved value changes
|
||||
let doubled = use_memo(move || 2 * halved());
|
||||
|
||||
rsx! {
|
||||
"{doubled}"
|
||||
button {
|
||||
onclick: move |_| {
|
||||
count += 1;
|
||||
},
|
||||
"Increment"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For a full list of all the helpers available, check out the [`Readable`], [`ReadableVecExt`], and [`ReadableOptionExt`] traits.
|
||||
|
||||
# Memos with Async
|
||||
|
||||
Because Memos check borrows at runtime, you need to be careful when reading memos inside of async code. If you hold a read of a memo over an await point, that read may still be open when the memo reruns which will cause a panic:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
# async fn sleep(delay: u32) {}
|
||||
async fn double_me_async(value: &u32) -> u32 {
|
||||
sleep(100).await;
|
||||
*value * 2
|
||||
}
|
||||
let mut signal = use_signal(|| 0);
|
||||
let halved = use_memo(move || signal() / 2);
|
||||
|
||||
let doubled = use_resource(move || async move {
|
||||
// Don't hold reads over await points
|
||||
let halved = halved.read();
|
||||
// While the future is waiting for the async work to finish, the read will be open
|
||||
double_me_async(&halved).await
|
||||
});
|
||||
|
||||
rsx!{
|
||||
"{doubled:?}"
|
||||
button {
|
||||
onclick: move |_| {
|
||||
// When you write to signal, it will cause the memo to rerun which may panic because you are holding a read of the memo over an await point
|
||||
signal += 1;
|
||||
},
|
||||
"Increment"
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
Instead of holding a read over an await point, you can clone whatever values you need out of your memo:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
# async fn sleep(delay: u32) {}
|
||||
async fn double_me_async(value: u32) -> u32 {
|
||||
sleep(100).await;
|
||||
value * 2
|
||||
}
|
||||
let mut signal = use_signal(|| 0);
|
||||
let halved = use_memo(move || signal() / 2);
|
||||
|
||||
let doubled = use_resource(move || async move {
|
||||
// Calling the memo will clone the inner value
|
||||
let halved = halved();
|
||||
double_me_async(halved).await;
|
||||
});
|
||||
|
||||
rsx!{
|
||||
"{doubled:?}"
|
||||
button {
|
||||
onclick: move |_| {
|
||||
signal += 1;
|
||||
},
|
||||
"Increment"
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
# Memo lifecycle
|
||||
|
||||
Memos are implemented with [generational-box](https://crates.io/crates/generational-box) which makes all values Copy even if the inner value is not Copy.
|
||||
|
||||
This is incredibly convenient for UI development, but it does come with some tradeoffs. The lifetime of the memo is tied to the lifetime of the component it was created in. If you drop the component that created the memo, the memo will be dropped as well. You might run into this if you try to pass a memo from a child component to a parent component and drop the child component. To avoid this you can create your memo higher up in your component tree, or use global memos.
|
||||
|
||||
TLDR **Don't pass memos up in the component tree**. It will cause issues:
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn MyComponent() -> Element {
|
||||
let child_signal = use_signal(|| None);
|
||||
|
||||
rsx! {
|
||||
IncrementButton {
|
||||
child_signal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn IncrementButton(mut child_signal: Signal<Option<Memo<i32>>>) -> Element {
|
||||
let signal_owned_by_child = use_signal(|| 0);
|
||||
let memo_owned_by_child = use_memo(move || signal_owned_by_child() * 2);
|
||||
// Don't do this: it may cause issues if you drop the child component
|
||||
child_signal.set(Some(memo_owned_by_child));
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
112
packages/signals/docs/reactivity.md
Normal file
112
packages/signals/docs/reactivity.md
Normal file
|
@ -0,0 +1,112 @@
|
|||
# Reactivity
|
||||
|
||||
The core of dioxus relies on the concept of reactivity. Reactivity is the system that updates your app when state changes.
|
||||
|
||||
There are two parts to reactivity: **Reactive Contexts** and **Tracked Values**.
|
||||
|
||||
## Reactive Contexts
|
||||
|
||||
Reactive Contexts keep track of what state different parts of your app rely on. Reactive Context show up throughout dioxus: Component, use_effect, use_memo and use_resource all have their own reactive contexts:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
let count = use_signal(|| 0);
|
||||
// The reactive context in the memo knows that the memo depends on the count signal
|
||||
use_memo(move || count() * 2);
|
||||
```
|
||||
|
||||
## Tracked Values
|
||||
|
||||
Tracked values are values that reactive contexts know about. When you read a tracked value, the reactive context will rerun when the value changes. Signals, Memos, and Resources are all tracked values:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
// The count signal is tracked
|
||||
let count = use_signal(|| 0);
|
||||
// When you read the count signal, the reactive context subscribes to the count signal
|
||||
let double_count = use_memo(move || count() * 2);
|
||||
```
|
||||
|
||||
## Reactivity
|
||||
|
||||
Reactivity is the system that combines reactive context and tracked values to update your app when state changes.
|
||||
|
||||
You can use reactivity to create derived state and update your app when state changes.
|
||||
|
||||
You can derive state from other state with [`use_memo`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_memo.html).
|
||||
|
||||
```rust, no_run
|
||||
use dioxus::prelude::*;
|
||||
|
||||
let mut count = use_signal(|| 0);
|
||||
let double_count = use_memo(move || count() * 2);
|
||||
|
||||
// Now whenever we read double_count, we know it is always twice the value of count
|
||||
println!("{}", double_count); // Prints "2"
|
||||
|
||||
// After we write to count, the reactive context will rerun and double_count will be updated automatically
|
||||
count += 1;
|
||||
|
||||
println!("{}", double_count); // Prints "4"
|
||||
```
|
||||
|
||||
You can also use reactivity to create derive state asynchronously. For example, you can use [`use_resource`](https://docs.rs/dioxus/latest/dioxus/prelude/fn.use_resource.html) to load data from a server:
|
||||
|
||||
```rust, no_run
|
||||
use dioxus::prelude::*;
|
||||
|
||||
let count = use_signal(|| 0);
|
||||
let double_count = use_resource(move || async move {
|
||||
// Start a request to the server. We are reading the value of count to format it into the url
|
||||
// Since we are reading count, this resource will "subscribe" to changes to count (when count changes, the resource will rerun)
|
||||
let response = reqwest::get(format!("https://myserver.com/doubleme?count={count}")).await.unwrap();
|
||||
response.text().await.unwrap()
|
||||
});
|
||||
```
|
||||
|
||||
## Non Reactive State
|
||||
|
||||
You can use plain Rust types in Dioxus, but you should be aware that they are not reactive. If you read the non-reactive state, reactive scopes will not subscribe to the state.
|
||||
|
||||
You can make non-reactive state reactive by using the `Signal` type instead of a plain Rust type or by using the `use_reactive` hook.
|
||||
|
||||
```rust, no_run
|
||||
use dioxus::prelude::*;
|
||||
|
||||
// ❌ Don't create non-reactive state
|
||||
let state = use_hook(|| std::cell::RefCell::new(0));
|
||||
|
||||
// Computed values will get out of date if the state they depend on is not reactive
|
||||
let doubled = use_memo(move || *state.borrow() * 2);
|
||||
|
||||
// ✅ Create reactive state
|
||||
let state = use_signal(|| 0);
|
||||
|
||||
// Computed values will automatically keep up to date with the latest reactive state
|
||||
let doubled = use_memo(move || state() * 2);
|
||||
|
||||
// ❌ Don't depend on non-reactive prop state in memos/resources
|
||||
#[component]
|
||||
fn MyComponent(state: i32) -> Element {
|
||||
let doubled = use_memo(move || state * 2);
|
||||
todo!()
|
||||
}
|
||||
|
||||
// ✅ Wrap your props in ReadOnlySignal to make them reactive
|
||||
#[component]
|
||||
fn MyReactiveComponent(state: ReadOnlySignal<i32>) -> Element {
|
||||
let doubled = use_memo(move || state() * 2);
|
||||
todo!()
|
||||
}
|
||||
```
|
||||
|
||||
If your state can't be reactive, you can use the `use_reactive` hook to make it reactive.
|
||||
|
||||
```rust, no_run
|
||||
use dioxus::prelude::*;
|
||||
|
||||
let state = rand::random::<i32>();
|
||||
|
||||
// You can make the state reactive by wrapping it in use_reactive
|
||||
let doubled = use_memo(use_reactive!(|state| state * 2));
|
||||
```
|
165
packages/signals/docs/signals.md
Normal file
165
packages/signals/docs/signals.md
Normal file
|
@ -0,0 +1,165 @@
|
|||
Signals are a Copy state management solution with automatic dependency tracking.
|
||||
|
||||
You may have noticed that this struct doesn't have many methods. Most methods for Signal are defined on the [`Readable`] and [`Writable`] traits.
|
||||
|
||||
# Reading and Writing to a Signal
|
||||
|
||||
Signals are similar to a copy version of `Rc<RefCell<T>>` built for UIs. You can read and write to a signal like a RefCell:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
let mut signal = use_signal(|| 0);
|
||||
|
||||
{
|
||||
// This will read the value (we use a block to make sure the read is dropped before the write. You can read more about this in the next section)
|
||||
let read = signal.read();
|
||||
// Just like refcell, read you can deref the read to get the inner &T reference
|
||||
match &*read {
|
||||
&0 => println!("read is 0"),
|
||||
&1 => println!("read is 1"),
|
||||
_ => println!("read is something else ({read})"),
|
||||
}
|
||||
}
|
||||
|
||||
// This will write to the value
|
||||
let mut write = signal.write();
|
||||
// Again, we can deref the write to get the inner &mut T reference
|
||||
*write += 1;
|
||||
```
|
||||
|
||||
Signals also have a bunch of helper methods to make it easier to use. Calling it like a function will clone the inner value. You can also call a few traits like AddAssign on it directly without writing to it manually:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
let mut signal = use_signal(|| 0);
|
||||
// This will clone the value
|
||||
let clone: i32 = signal();
|
||||
|
||||
// You can directly display the signal
|
||||
println!("{}", signal);
|
||||
|
||||
let signal_vec = use_signal(|| vec![1, 2, 3]);
|
||||
// And use vec methods like .get and .len without reading the signal explicitly
|
||||
let first = signal_vec.get(0);
|
||||
let last = signal_vec.last();
|
||||
let len = signal_vec.len();
|
||||
|
||||
// You can also iterate over signals directly
|
||||
for i in signal_vec.iter() {
|
||||
println!("{}", i);
|
||||
}
|
||||
```
|
||||
|
||||
For a full list of all the helpers available, check out the [`Readable`], [`ReadableVecExt`], [`ReadableOptionExt`], [`Writable`], [`WritableVecExt`], and [`WritableOptionExt`] traits.
|
||||
|
||||
Just like `RefCell<T>`, Signal checks borrows at runtime. If you read and write to the signal at the same time, it will panic:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
let mut signal = use_signal(|| 0);
|
||||
// If you create a read and hold it while you write to the signal, it will panic
|
||||
let read = signal.read_unchecked();
|
||||
// This will panic
|
||||
signal += 1;
|
||||
println!("{}", read);
|
||||
```
|
||||
|
||||
To avoid issues with overlapping reads and writes, you can use the `with_*` variants of methods to read and write to the signal in a single scope or wrap your reads and writes inside a block:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
let mut signal = use_signal(|| 0);
|
||||
{
|
||||
// Since this read is inside a block that ends before we write to the signal, the signal will be dropped before the write and it will not panic
|
||||
let read = signal.read();
|
||||
println!("{}", read);
|
||||
}
|
||||
signal += 1;
|
||||
|
||||
// Or you can use the with and with_write methods which only read or write to the signal inside the closure
|
||||
signal.with(|read| println!("{}", read));
|
||||
// Since the read only lasts as long as the closure, this will not panic
|
||||
signal.with_mut(|write| *write += 1);
|
||||
```
|
||||
|
||||
# Signals with Async
|
||||
|
||||
Because signals check borrows at runtime, you need to be careful when reading and writing to signals inside of async code. If you hold a read or write to a signal over an await point, that read or write may still be open while you run other parts of your app:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
# async fn sleep(delay: u32) {}
|
||||
async fn double_me_async(value: &mut u32) {
|
||||
sleep(100).await;
|
||||
*value *= 2;
|
||||
}
|
||||
let mut signal = use_signal(|| 0);
|
||||
|
||||
use_future(move || async move {
|
||||
// Don't hold reads or writes over await points
|
||||
let mut write = signal.write();
|
||||
// While the future is waiting for the async work to finish, the write will be open
|
||||
double_me_async(&mut write).await;
|
||||
});
|
||||
|
||||
rsx!{
|
||||
// This read may panic because the write is still active while the future is waiting for the async work to finish
|
||||
"{signal}"
|
||||
};
|
||||
```
|
||||
|
||||
Instead of holding a read or write over an await point, you can clone whatever values you need out of your signal and then set the signal to the result once the async work is done:
|
||||
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
# async fn sleep(delay: u32) {}
|
||||
async fn double_me_async(value: u32) -> u32 {
|
||||
sleep(100).await;
|
||||
value * 2
|
||||
}
|
||||
let mut signal = use_signal(|| 0);
|
||||
|
||||
use_future(move || async move {
|
||||
// Clone the value out of the signal
|
||||
let current_value = signal();
|
||||
// Run the async work
|
||||
let new_value = double_me_async(current_value).await;
|
||||
// Set the signal to the new value
|
||||
signal.set(new_value);
|
||||
});
|
||||
|
||||
rsx! {
|
||||
// This read will not panic because the write is never held over an await point
|
||||
"{signal}"
|
||||
};
|
||||
```
|
||||
|
||||
# Signals lifecycle
|
||||
|
||||
Signals are implemented with [generational-box](https://crates.io/crates/generational-box) which makes all values Copy even if the inner value is not Copy.
|
||||
|
||||
This is incredibly convenient for UI development, but it does come with some tradeoffs. The lifetime of the signal is tied to the lifetime of the component it was created in. If you drop the component that created the signal, the signal will be dropped as well. You might run into this if you try to pass a signal from a child component to a parent component and drop the child component. To avoid this you can create your signal higher up in your component tree, use global signals, or create a signal in a specific scope (like the `ScopeId::ROOT`) with [`Signal::new_in_scope`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Signal.html#method.new_in_scope)
|
||||
|
||||
TLDR **Don't pass signals up in the component tree**. It will cause issues:
|
||||
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn MyComponent() -> Element {
|
||||
let child_signal = use_signal(|| None);
|
||||
|
||||
rsx! {
|
||||
IncrementButton {
|
||||
child_signal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn IncrementButton(mut child_signal: Signal<Option<Signal<i32>>>) -> Element {
|
||||
let signal_owned_by_child = use_signal(|| 0);
|
||||
// Don't do this: it may cause issues if you drop the child component
|
||||
child_signal.set(Some(signal_owned_by_child));
|
||||
|
||||
todo!()
|
||||
}
|
||||
```
|
|
@ -114,6 +114,11 @@ impl<T: 'static, S: Storage<T>> CopyValue<T, S> {
|
|||
pub fn id(&self) -> GenerationalBoxId {
|
||||
self.value.id()
|
||||
}
|
||||
|
||||
/// Get the underlying [`GenerationalBox`] value.
|
||||
pub fn value(&self) -> GenerationalBox<T, S> {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, S: Storage<T>> Readable for CopyValue<T, S> {
|
||||
|
|
|
@ -17,7 +17,10 @@ struct UpdateInformation<T> {
|
|||
callback: RefCell<Box<dyn FnMut() -> T>>,
|
||||
}
|
||||
|
||||
/// A value that is memoized. This is useful for caching the result of a computation.
|
||||
#[doc = include_str!("../docs/memo.md")]
|
||||
#[doc(alias = "Selector")]
|
||||
#[doc(alias = "UseMemo")]
|
||||
#[doc(alias = "Memorize")]
|
||||
pub struct Memo<T: 'static> {
|
||||
inner: Signal<T>,
|
||||
update: CopyValue<UpdateInformation<T>>,
|
||||
|
|
|
@ -7,12 +7,7 @@ use std::{cell::RefCell, hash::Hash};
|
|||
|
||||
use crate::{CopyValue, Writable};
|
||||
|
||||
/// A context for signal reads and writes to be directed to
|
||||
///
|
||||
/// When a signal calls .read(), it will look for the current ReactiveContext to read from.
|
||||
/// If it doesn't find it, then it will try and insert a context into the nearest component scope via context api.
|
||||
///
|
||||
/// When the ReactiveContext drops, it will remove itself from the associated contexts attached to signal
|
||||
#[doc = include_str!("../docs/reactivity.md")]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ReactiveContext {
|
||||
inner: CopyValue<Inner, SyncStorage>,
|
||||
|
|
|
@ -10,6 +10,30 @@ pub type ReadableRef<'a, T: Readable, O = <T as Readable>::Target> =
|
|||
<T::Storage as AnyStorage>::Ref<'a, O>;
|
||||
|
||||
/// A trait for states that can be read from like [`crate::Signal`], [`crate::GlobalSignal`], or [`crate::ReadOnlySignal`]. You may choose to accept this trait as a parameter instead of the concrete type to allow for more flexibility in your API. For example, instead of creating two functions, one that accepts a [`crate::Signal`] and one that accepts a [`crate::GlobalSignal`], you can create one function that accepts a [`Readable`] type.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn double(something_readable: &impl Readable<Target = i32>) -> i32 {
|
||||
/// something_readable.cloned() * 2
|
||||
/// }
|
||||
///
|
||||
/// static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
|
||||
///
|
||||
/// fn MyComponent(count: Signal<i32>) -> Element {
|
||||
/// // Since we defined the function in terms of the readable trait, we can use it with any readable type (Signal, GlobalSignal, ReadOnlySignal, etc)
|
||||
/// let doubled = use_memo(move || double(&count));
|
||||
/// let global_count_doubled = use_memo(|| double(&COUNT));
|
||||
/// rsx! {
|
||||
/// div {
|
||||
/// "Count local: {count}"
|
||||
/// "Doubled local: {doubled}"
|
||||
/// "Count global: {COUNT}"
|
||||
/// "Doubled global: {global_count_doubled}"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Readable {
|
||||
/// The target type of the reference.
|
||||
type Target: ?Sized + 'static;
|
||||
|
@ -17,7 +41,30 @@ pub trait Readable {
|
|||
/// The type of the storage this readable uses.
|
||||
type Storage: AnyStorage;
|
||||
|
||||
/// Map the readable type to a new type.
|
||||
/// Map the readable type to a new type. This lets you provide a view into a readable type without needing to clone the inner value.
|
||||
///
|
||||
/// Anything that subscribes to the readable value will be rerun whenever the original value changes, even if the view does not change. If you want to memorize the view, you can use a [`crate::Memo`] instead.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn List(list: Signal<Vec<i32>>) -> Element {
|
||||
/// rsx! {
|
||||
/// for index in 0..list.len() {
|
||||
/// // We can use the `map` method to provide a view into the single item in the list that the child component will render
|
||||
/// Item { item: list.map(move |v| &v[index]) }
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // The child component doesn't need to know that the mapped value is coming from a list
|
||||
/// #[component]
|
||||
/// fn Item(item: MappedSignal<i32>) -> Element {
|
||||
/// rsx! {
|
||||
/// div { "Item: {item}" }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn map<O>(self, f: impl Fn(&Self::Target) -> &O + 'static) -> MappedSignal<O, Self::Storage>
|
||||
where
|
||||
Self: Clone + Sized + 'static,
|
||||
|
@ -80,6 +127,38 @@ pub trait Readable {
|
|||
fn peek_unchecked(&self) -> ReadableRef<'static, Self>;
|
||||
|
||||
/// Get the current value of the state without subscribing to updates. If the value has been dropped, this will panic.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn MyComponent(mut count: Signal<i32>) -> Element {
|
||||
/// let mut event_source = use_signal(|| None);
|
||||
/// let doubled = use_memo(move || {
|
||||
/// // We want to log the value of the event_source, but we don't need to rerun the doubled value if the event_source changes (because the value of doubled doesn't depend on the event_source)
|
||||
/// // We can read the value with peek without subscribing to updates
|
||||
/// let click_count = count.peek();
|
||||
/// tracing::info!("Click count: {click_count:?}");
|
||||
/// count() * 2
|
||||
/// });
|
||||
/// rsx! {
|
||||
/// div { "Count: {count}" }
|
||||
/// div { "Doubled: {doubled}" }
|
||||
/// button {
|
||||
/// onclick: move |_| {
|
||||
/// event_source.set(Some("Click me button"));
|
||||
/// },
|
||||
/// "Click me"
|
||||
/// }
|
||||
/// button {
|
||||
/// onclick: move |_| {
|
||||
/// event_source.set(Some("Double me button"));
|
||||
/// count += 1;
|
||||
/// },
|
||||
/// "Double me"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[track_caller]
|
||||
fn peek(&self) -> ReadableRef<Self> {
|
||||
Self::Storage::downcast_lifetime_ref(self.peek_unchecked())
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
use crate::{default_impl, fmt_impls, write_impls};
|
||||
use crate::{
|
||||
read::Readable, write::Writable, CopyValue, GlobalMemo, GlobalSignal, ReactiveContext,
|
||||
ReadableRef,
|
||||
};
|
||||
use crate::{read::*, write::*, CopyValue, GlobalMemo, GlobalSignal, ReactiveContext, ReadableRef};
|
||||
use crate::{Memo, WritableRef};
|
||||
use dioxus_core::IntoDynNode;
|
||||
use dioxus_core::{prelude::IntoAttributeValue, ScopeId};
|
||||
|
@ -14,41 +11,19 @@ use std::{
|
|||
sync::Mutex,
|
||||
};
|
||||
|
||||
/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
|
||||
///
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
/// use dioxus_signals::*;
|
||||
///
|
||||
/// #[component]
|
||||
/// fn App() -> Element {
|
||||
/// let mut count = use_signal(|| 0);
|
||||
///
|
||||
/// // Because signals have automatic dependency tracking, if you never read them in a component, that component will not be re-rended when the signal is updated.
|
||||
/// // The app component will never be rerendered in this example.
|
||||
/// rsx! { Child { state: count } }
|
||||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Child(mut state: Signal<u32>) -> Element {
|
||||
/// use_future(move || async move {
|
||||
/// // Because the signal is a Copy type, we can use it in an async block without cloning it.
|
||||
/// state += 1;
|
||||
/// });
|
||||
///
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |_| state += 1,
|
||||
/// "{state}"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[doc = include_str!("../docs/signals.md")]
|
||||
#[doc(alias = "State")]
|
||||
#[doc(alias = "UseState")]
|
||||
#[doc(alias = "UseRef")]
|
||||
pub struct Signal<T: 'static, S: Storage<SignalData<T>> = UnsyncStorage> {
|
||||
pub(crate) inner: CopyValue<SignalData<T>, S>,
|
||||
}
|
||||
|
||||
/// A signal that can safely shared between threads.
|
||||
#[doc(alias = "SendSignal")]
|
||||
#[doc(alias = "UseRwLock")]
|
||||
#[doc(alias = "UseRw")]
|
||||
#[doc(alias = "UseMutex")]
|
||||
pub type SyncSignal<T> = Signal<T, SyncStorage>;
|
||||
|
||||
/// The data stored for tracking in a signal.
|
||||
|
@ -58,7 +33,24 @@ pub struct SignalData<T> {
|
|||
}
|
||||
|
||||
impl<T: 'static> Signal<T> {
|
||||
/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
|
||||
/// Creates a new [`Signal`]. Signals are a Copy state management solution with automatic dependency tracking.
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// This function should generally only be called inside hooks. The signal that this function creates is owned by the current component and will only be dropped when the component is dropped. If you call this function outside of a hook many times, you will leak memory until the component is dropped.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// fn MyComponent() {
|
||||
/// // ❌ Every time MyComponent runs, it will create a new signal that is only dropped when MyComponent is dropped
|
||||
/// let signal = Signal::new(0);
|
||||
/// use_context_provider(|| signal);
|
||||
/// // ✅ Since the use_context_provider hook only runs when the component is created, the signal will only be created once and it will be dropped when MyComponent is dropped
|
||||
/// let signal = use_context_provider(|| Signal::new(0));
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// </div>
|
||||
#[track_caller]
|
||||
pub fn new(value: T) -> Self {
|
||||
Self::new_maybe_sync(value)
|
||||
|
@ -70,16 +62,60 @@ impl<T: 'static> Signal<T> {
|
|||
Self::new_maybe_sync_in_scope(value, owner)
|
||||
}
|
||||
|
||||
/// Creates a new global Signal that can be used in a global static.
|
||||
#[track_caller]
|
||||
/// Creates a new [`GlobalSignal`] that can be used anywhere inside your dioxus app. This signal will automatically be created once per app the first time you use it.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// // Create a new global signal that can be used anywhere in your app
|
||||
/// static SIGNAL: GlobalSignal<i32> = Signal::global(|| 0);
|
||||
///
|
||||
/// fn App() -> Element {
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: move |_| *SIGNAL.write() += 1,
|
||||
/// "{SIGNAL}"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// Global signals are generally not recommended for use in libraries because it makes it more difficult to allow multiple instances of components you define in your library.
|
||||
///
|
||||
/// </div>
|
||||
pub const fn global(constructor: fn() -> T) -> GlobalSignal<T> {
|
||||
GlobalSignal::new(constructor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq + 'static> Signal<T> {
|
||||
/// Creates a new global Signal that can be used in a global static.
|
||||
#[track_caller]
|
||||
/// Creates a new [`GlobalMemo`] that can be used anywhere inside your dioxus app. This memo will automatically be created once per app the first time you use it.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// static SIGNAL: GlobalSignal<i32> = Signal::global(|| 0);
|
||||
/// // Create a new global memo that can be used anywhere in your app
|
||||
/// static DOUBLED: GlobalMemo<i32> = Signal::global_memo(|| SIGNAL() * 2);
|
||||
///
|
||||
/// fn App() -> Element {
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// // When SIGNAL changes, the memo will update because the SIGNAL is read inside DOUBLED
|
||||
/// onclick: move |_| *SIGNAL.write() += 1,
|
||||
/// "{DOUBLED}"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// <div class="warning">
|
||||
///
|
||||
/// Global memos are generally not recommended for use in libraries because it makes it more difficult to allow multiple instances of components you define in your library.
|
||||
///
|
||||
/// </div>
|
||||
pub const fn global_memo(constructor: fn() -> T) -> GlobalMemo<T> {
|
||||
GlobalMemo::new(constructor)
|
||||
}
|
||||
|
@ -116,11 +152,20 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
|
||||
pub fn new_with_caller(
|
||||
value: T,
|
||||
#[cfg(debug_assertions)] caller: &'static std::panic::Location<'static>,
|
||||
) -> Self {
|
||||
/// Creates a new Signal with an explicit caller. Signals are a Copy state management solution with automatic dependency tracking.
|
||||
///
|
||||
/// This method can be used to provide the correct caller information for signals that are created in closures:
|
||||
///
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// #[track_caller]
|
||||
/// fn use_my_signal(function: impl FnOnce() -> i32) -> Signal<i32> {
|
||||
/// // We capture the caller information outside of the closure so that it points to the caller of use_my_custom_hook instead of the closure
|
||||
/// let caller = std::panic::Location::caller();
|
||||
/// use_hook(move || Signal::new_with_caller(function(), caller))
|
||||
/// }
|
||||
/// ```
|
||||
pub fn new_with_caller(value: T, caller: &'static std::panic::Location<'static>) -> Self {
|
||||
Self {
|
||||
inner: CopyValue::new_with_caller(
|
||||
SignalData {
|
||||
|
|
|
@ -6,7 +6,31 @@ use crate::read::Readable;
|
|||
#[allow(type_alias_bounds)]
|
||||
pub type WritableRef<'a, T: Writable, O = <T as Readable>::Target> = T::Mut<'a, O>;
|
||||
|
||||
/// A trait for states that can be read from like [`crate::Signal`], or [`crate::GlobalSignal`]. You may choose to accept this trait as a parameter instead of the concrete type to allow for more flexibility in your API. For example, instead of creating two functions, one that accepts a [`crate::Signal`] and one that accepts a [`crate::GlobalSignal`], you can create one function that accepts a [`Writable`] type.
|
||||
/// A trait for states that can be written to like [`crate::Signal`]. You may choose to accept this trait as a parameter instead of the concrete type to allow for more flexibility in your API.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// # use dioxus::prelude::*;
|
||||
/// enum MyEnum {
|
||||
/// String(String),
|
||||
/// Number(i32),
|
||||
/// }
|
||||
///
|
||||
/// fn MyComponent(mut count: Signal<MyEnum>) -> Element {
|
||||
/// rsx!{
|
||||
/// button {
|
||||
/// onclick: move |_| {
|
||||
/// // You can use any methods from the Writable trait on Signals
|
||||
/// match &mut *count.write() {
|
||||
/// MyEnum::String(s) => s.push('a'),
|
||||
/// MyEnum::Number(n) => *n += 1,
|
||||
/// }
|
||||
/// },
|
||||
/// "Add value"
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Writable: Readable {
|
||||
/// The type of the reference.
|
||||
type Mut<'a, R: ?Sized + 'static>: DerefMut<Target = R>;
|
||||
|
@ -112,7 +136,7 @@ pub trait Writable: Readable {
|
|||
}
|
||||
}
|
||||
|
||||
/// An extension trait for Writable<Option<T>> that provides some convenience methods.
|
||||
/// An extension trait for [`Writable<Option<T>>`]` that provides some convenience methods.
|
||||
pub trait WritableOptionExt<T: 'static>: Writable<Target = Option<T>> {
|
||||
/// Gets the value out of the Option, or inserts the given value if the Option is empty.
|
||||
#[track_caller]
|
||||
|
@ -146,7 +170,7 @@ where
|
|||
{
|
||||
}
|
||||
|
||||
/// An extension trait for Writable<Vec<T>> that provides some convenience methods.
|
||||
/// An extension trait for [`Writable<Vec<T>>`] that provides some convenience methods.
|
||||
pub trait WritableVecExt<T: 'static>: Writable<Target = Vec<T>> {
|
||||
/// Pushes a new value to the end of the vector.
|
||||
#[track_caller]
|
||||
|
@ -227,7 +251,7 @@ pub trait WritableVecExt<T: 'static>: Writable<Target = Vec<T>> {
|
|||
}
|
||||
}
|
||||
|
||||
/// An iterator over the values of a `Writable<Vec<T>>`.
|
||||
/// An iterator over the values of a [`Writable<Vec<T>>`].
|
||||
pub struct WritableValueIterator<'a, R> {
|
||||
index: usize,
|
||||
value: &'a mut R,
|
||||
|
|
|
@ -17,8 +17,13 @@ This crate is a part of the broader Dioxus ecosystem. For more resources about D
|
|||
|
||||
Dioxus SSR provides utilities to render Dioxus components to valid HTML. Once rendered, the HTML can be rehydrated client-side or served from your web server of choice.
|
||||
|
||||
```rust, ignore
|
||||
let app: Component = |cx| rsx!(div {"hello world!"});
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn app() -> Element {
|
||||
rsx!{
|
||||
div {"hello world!"}
|
||||
}
|
||||
}
|
||||
|
||||
let mut vdom = VirtualDom::new(app);
|
||||
vdom.rebuild_in_place();
|
||||
|
@ -31,19 +36,22 @@ assert_eq!(text, "<div>hello world!</div>")
|
|||
|
||||
The simplest example is to simply render some `rsx!` nodes to HTML. This can be done with the [`render_element`] API.
|
||||
|
||||
```rust, ignore
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
let content = dioxus_ssr::render_element(rsx!{
|
||||
div {
|
||||
(0..5).map(|i| rsx!(
|
||||
for i in 0..5 {
|
||||
"Number: {i}"
|
||||
))
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
## Rendering a VirtualDom
|
||||
|
||||
```rust, ignore
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
# fn app() -> Element { todo!() }
|
||||
let mut vdom = VirtualDom::new(app);
|
||||
vdom.rebuild_in_place();
|
||||
|
||||
|
@ -60,7 +68,9 @@ With pre-rendering enabled, this crate will generate element nodes with Element
|
|||
|
||||
To enable pre-rendering, simply set the pre-rendering flag to true.
|
||||
|
||||
```rust, ignore
|
||||
```rust, no_run
|
||||
# use dioxus::prelude::*;
|
||||
# fn App() -> Element { todo!() }
|
||||
let mut vdom = VirtualDom::new(App);
|
||||
|
||||
vdom.rebuild_in_place();
|
||||
|
@ -75,30 +85,19 @@ let text = renderer.render(&vdom);
|
|||
|
||||
Dioxus SSR can also be used to render on the server. You can just render the VirtualDOM to a string and send that to the client.
|
||||
|
||||
```rust, ignore
|
||||
```rust
|
||||
# use dioxus::prelude::*;
|
||||
fn App() -> Element {
|
||||
rsx! { div { "hello world!" } }
|
||||
}
|
||||
let mut vdom = VirtualDom::new(App);
|
||||
vdom.rebuild_in_place();
|
||||
let text = dioxus_ssr::render(&vdom);
|
||||
assert_eq!(text, "<div>hello world!</div>")
|
||||
```
|
||||
|
||||
The rest of the space - IE doing this more efficiently, caching the VirtualDom, etc, will all need to be a custom implementation for now.
|
||||
|
||||
## Usage without a VirtualDom
|
||||
|
||||
Dioxus SSR needs an arena to allocate from - whether it be the VirtualDom or a dedicated Bump allocator. To render `rsx!` directly to a string, you'll want to create a `Renderer` and call `render_element`.
|
||||
|
||||
```rust, ignore
|
||||
let text = dioxus_ssr::Renderer::new().render_element(rsx!{
|
||||
div { "hello world" }
|
||||
});
|
||||
assert_eq!(text, "<div>hello world!</div>")
|
||||
```
|
||||
|
||||
This can be automated with the `render_element!` macro:
|
||||
|
||||
```rust, ignore
|
||||
let text = render_element!(rsx!( div { "hello world" } ));
|
||||
```
|
||||
|
||||
## Usage in static site generation
|
||||
|
||||
Dioxus SSR is a powerful tool to generate static sites. Using Dioxus for static site generation _is_ a bit overkill, however. The new documentation generation library, Doxie, is essentially Dioxus SSR on steroids designed for static site generation with client-side hydration.
|
||||
|
|
Loading…
Reference in a new issue