docs: create a new "advanced" book

This commit is contained in:
Jonathan Kelley committed 2021-10-29 00:40:36 -04:00
1 parent 54d050cf89
commit 97a2c62804
69 files changed
+24 -9

No files matched your search

+103
View File
@@ -0,0 +1,103 @@
# Introduction
![dioxuslogo](./images/dioxuslogo_full.png)
**Dioxus** is a framework and ecosystem for building fast, scalable, and robust user interfaces with the Rust programming language. This guide will help you get up-and-running with Dioxus running on the Web, Desktop, Mobile, and more.
```rust
fn App((cx, props): Component<()>) -> Element {
let mut count = use_state(cx, || 0);
cx.render(rsx!(
h1 { "High-Five counter: {count}" }
button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" }
))
};
```
The Dioxus API and patterns closely resemble React - if this guide is lacking in any general concept or an error message is confusing, we recommend substituting "React" for "Dioxus" in your web search terms. A major goal of Dioxus is to provide a familiar toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc) - if you know React, then you already know Dioxus. If you don't know either, this guide will still help you!
### Web Support
---
The Web is the most-supported target platform for Dioxus. To run on the Web, your app must be compiled to WebAssembly and depend on the `dioxus` crate with the `web` feature enabled. Because of the Wasm limitation, not every crate will work with your web-apps, so you'll need to make sure that your crates work without native system calls (timers, IO, etc).
Because the web is a fairly mature platform, we expect there to be very little API churn for web-based features.
[Jump to the getting started guide for the web.]()
Examples:
- [TodoMVC](https://github.com/dioxusLabs/todomvc/)
- [ECommerce]()
- [Photo Editor]()
[![TODOMVC](https://github.com/DioxusLabs/todomvc/raw/master/example.png)](https://github.com/dioxusLabs/todomvc/)
### SSR Support
---
Dioxus supports server-side rendering! In a pinch, you can literally "debug" the VirtualDom:
```rust
let dom = VirtualDom::new(App);
println!("{:?}, dom");
```
For rendering statically to an `.html` file or from a WebServer, then you'll want to make sure the `ssr` feature is enabled in the `dioxus` crate and use the `dioxus::ssr` API. We don't expect the SSR API to change drastically in the future.
```rust
let contents = dioxus::ssr::render_vdom(&dom, |c| c);
```
[Jump to the getting started guide for SSR.]()
Examples:
- [Example DocSite]()
- [Tide WebServer]()
- [Markdown to fancy HTML generator]()
### Desktop Support
---
The desktop is a powerful target for Dioxus, but is currently limited in capability when compared to the Web platform. Currently, desktop apps are rendered with the platform's WebView library, but your Rust code is running natively on a native thread. This means that browser APIs are *not* available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs *are* accessible, so streaming, WebSockets, filesystem, etc are all viable APIs. In the future, we plan to move to a custom webrenderer-based DOM renderer with WGPU integrations.
Desktop APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart.
[Jump to the getting started guide for Desktop.]()
Examples:
- [File explorer]()
- [Bluetooth scanner]()
- [Device Viewer]()
[![FileExplorerExample](https://github.com/DioxusLabs/file-explorer-example/raw/master/image.png)](https://github.com/dioxusLabs/file-explorer/)
### Mobile Support
---
Mobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with the platform's WebView, meaning that animations, transparency, and native widgets are not currently achievable. In addition, iOS is the only supported Mobile Platform. It is possible to get Dioxus running on Android and rendered with WebView, but the Rust windowing library that Dioxus uses - tao - does not currently supported Android.
Mobile support is currently best suited for CRUD-style apps, ideally for internal teams who need to develop quickly but don't care much about animations or native widgets.
[Jump to the getting started guide for Mobile.]()
Examples:
- [Todo App]()
- [Chat App]()
### LiveView / Server Component Support
---
The internal architecture of Dioxus was designed from day one to support the `LiveView` use-case, where a web server hosts a running app for each connected user. As of today, there is no first-class LiveView support - you'll need to wire this up yourself.
While not currently fully implemented, the expectation is that LiveView apps can be a hybrid between Wasm and server-rendered where only portions of a page are "live" and the rest of the page is either server-rendered, statically generated, or handled by the host SPA.
### Multithreaded Support
---
The Dioxus VirtualDom, sadly, is not currently `Send`. Internally, we use quite a bit of interior mutability which is not thread-safe. This means you can't easily use Dioxus with most web frameworks like Tide, Rocket, Axum, etc.
To solve this, you'll want to spawn a VirtualDom on its own thread and communicate with it via channels.
When working with web frameworks that require `Send`, it is possible to render a VirtualDom immediately to a String - but you cannot hold the VirtualDom across an await point. For retained-state SSR (essentially LiveView), you'll need to create a pool of VirtualDoms.
Ultimately, you can always wrap the VirtualDom with a `Send` type and manually uphold the `Send` guarantees yourself.
+81
View File
@@ -0,0 +1,81 @@
# Summary
- [Introduction](README.md)
- [Getting Setup](setup.md)
- [Hello, World!](hello_world.md)
- [Describing the UI](concepts/00-index.md)
- [Intro to Elements](concepts/vnodes.md)
- [Intro to Components](concepts/components.md)
- [Reusing, Importing, and Exporting Components](concepts/exporting_components.md)
- [Conditional Rendering](concepts/conditional_rendering.md)
- [Lists](concepts/lists.md)
- [Adding Interactivity](concepts/interactivity.md)
- [Event handlers](concepts/event_handlers.md)
- [User Input and Controlled Components](concepts/errorhandling.md)
- [Lifecycle, updates, and effects](concepts/lifecycles.md)
- [Managing State](concepts/managing_state.md)
- [Hooks and Internal State](concepts/hooks.md)
- [Global State](concepts/sharedstate.md)
- [Error handling](concepts/errorhandling.md)
- [Effects](concepts/effects.md)
- [Working with Async](concepts/async.md)
- [Tasks](concepts/asynctasks.md)
- [Suspense](concepts/suspense.md)
- [Async Callbacks](concepts/asynccallbacks.md)
- [Putting it all together](tutorial/index.md)
- [New app](tutorial/new_app.md)
- [Structuring our app](tutorial/structure.md)
- [Defining State](tutorial/state.md)
- [Defining Components](tutorial/components.md)
- [Styling](tutorial/styling.md)
- [Publishing](tutorial/publishing.md)
- [Next Steps and Advanced Topics](final/index.md)
<!-- - [Topics in Depth](depth/topics.md)
- [RSX](depth/rsx.md)
- [Components](depth/components.md)
- [Props](depth/props.md)
- [Memoization](depth/memoization.md)
- [Performance](depth/performance.md)
- [Testing](depth/testing.md)
- [Advanced Guides](tutorial/advanced_guides.md)
- [Memoization](concepts/memoization.md)
- [RSX in Depth](concepts/rsx_in_depth.md)
- [Building Elements with NodeFactory](concepts/rsx.md)
- [Custom Elements](concepts/custom_elements.md)
- [Custom Renderer](concepts/custom_renderer.md)
- [Server-side components](concepts/server_side_components.md)
- [Bundling and Distributing](concepts/bundline.md)
- [Web]()
- [Getting Started]()
- [Down-casting Nodes]()
- [Wrapping Web APIs]()
- [SSR]()
- [Wrapping Web APIs]()
- [Desktop]()
- [Wrapping Web APIs]()
- [Mobile]()
- [Wrapping Web APIs]()
- [Reference Guide]()
- [Anti-patterns]()
- [Children]()
- [Conditional Rendering]()
- [Controlled Inputs]()
- [Custom Elements]()
- [Empty Components]()
- [Error Handling]()
- [Fragments]()
- [Global CSS]()
- [Inline Styles]()
- [Iterators]()
- [Listeners]()
- [Memoization]()
- [Node Refs]()
- [Spread Pattern]()
- [State Management]()
- [Suspense]()
- [task]()
- [Testing]() -->
-----------
[Contributors](misc/contributors.md)
@@ -0,0 +1,273 @@
# Custom Renderer
Dioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer.
Great news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require implementation of the `RealDom` trait for things to function properly.
## The specifics:
Implementing the renderer is fairly straightforward. The renderer needs to:
1. Handle the stream of edits generated by updates to the virtual DOM
2. Register listeners and pass events into the virtual DOM's event system
3. Progress the virtual DOM with an async executor (or disable the suspense API and use `progress_sync`)
Essentially, your renderer needs to implement the `RealDom` trait and generate `EventTrigger` objects to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen.
Internally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves.
For reference, check out the WebSys renderer as a starting point for your custom renderer.
## Trait implementation and DomEdits
The current `RealDom` trait lives in `dioxus_core/diff`. A version of it is provided here (but might not be up-to-date):
```rust
pub trait RealDom<'a> {
fn handle_edit(&mut self, edit: DomEdit);
fn request_available_node(&mut self) -> ElementId;
fn raw_node_as_any(&self) -> &mut dyn Any;
}
```
For reference, the "DomEdit" type is a serialized enum that represents an atomic operation occurring on the RealDom. The variants roughly follow this set:
```rust
enum DomEdit {
PushRoot,
AppendChildren,
ReplaceWith,
CreateTextNode,
CreateElement,
CreateElementNs,
CreatePlaceholder,
NewEventListener,
RemoveEventListener,
SetText,
SetAttribute,
RemoveAttribute,
}
```
The Dioxus diffing mechanism operates as a [stack machine](https://en.wikipedia.org/wiki/Stack_machine) where the "push_root" method pushes a new "real" DOM node onto the stack and "append_child" and "replace_with" both remove nodes from the stack.
### An example
For the sake of understanding, lets consider this example - a very simple UI declaration:
```rust
rsx!( h1 {"hello world"} )
```
To get things started, Dioxus must first navigate to the container of this h1 tag. To "navigate" here, the internal diffing algorithm generates the DomEdit `PushRoot` where the ID of the root is the container.
When the renderer receives this instruction, it pushes the actual Node onto its own stack. The real renderer's stack will look like this:
```rust
instructions: [
PushRoot(Container)
]
stack: [
ContainerNode,
]
```
Next, Dioxus will encounter the h1 node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the DomEdit `CreateElement`. When the renderer receives this instruction, it will create an unmounted node and push into its own stack:
```rust
instructions: [
PushRoot(Container),
CreateElement(h1),
]
stack: [
ContainerNode,
h1,
]
```
Next, Dioxus sees the text node, and generates the `CreateTextNode` DomEdit:
```rust
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world")
]
stack: [
ContainerNode,
h1,
"hello world"
]
```
Remember, the text node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the text node to the h1 element. It depends on the situation, but in this case we use `AppendChildren`. This pops the text node off the stack, leaving the h1 element as the next element in line.
```rust
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world"),
AppendChildren(1)
]
stack: [
ContainerNode,
h1
]
```
We call `AppendChildren` again, popping off the h1 node and attaching it to the parent:
```rust
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world"),
AppendChildren(1),
AppendChildren(1)
]
stack: [
ContainerNode,
]
```
Finally, the container is popped since we don't need it anymore.
```rust
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world"),
AppendChildren(1),
AppendChildren(1),
Pop(1)
]
stack: []
```
Over time, our stack looked like this:
```rust
[]
[Container]
[Container, h1]
[Container, h1, "hello world"]
[Container, h1]
[Container]
[]
```
Notice how our stack is empty once UI has been mounted. Conveniently, this approach completely separates the VirtualDOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits makes Dioxus independent of platform specifics.
Dioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing.
It's important to note that there _is_ one layer of connectedness between Dioxus and the renderer. Dioxus saves and loads elements (the PushRoot edit) with an ID. Inside the VirtualDOM, this is just tracked as a u64.
Whenever a `CreateElement` edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when PushRoot(ID) is generated. Dioxus doesn't reclaim IDs of elements its removed, but your renderer probably will want to. To do this, we suggest using a `SecondarySlotMap` if implementing the renderer in Rust, or just deferring to a HashMap-type approach.
This little demo serves to show exactly how a Renderer would need to process an edit stream to build UIs. A set of serialized EditStreams for various demos is available for you to test your custom renderer against.
## Event loop
Like most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important that your custom renderer can handle those too.
The code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is:
```rust
pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
// Push the body element onto the WebsysDom's stack machine
let mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom());
websys_dom.stack.push(root_node);
// Rebuild or hydrate the virtualdom
self.internal_dom.rebuild(&mut websys_dom)?;
// Wait for updates from the real dom and progress the virtual dom
loop {
let user_input_future = websys_dom.wait_for_event();
let internal_event_future = self.internal_dom.wait_for_event();
match select(user_input_future, internal_event_future).await {
Either::Left((trigger, _)) => trigger,
Either::Right((trigger, _)) => trigger,
}
}
while let Some(trigger) = {
websys_dom.stack.push(body_element.first_child().unwrap());
self.internal_dom
.progress_with_event(&mut websys_dom, trigger)?;
}
}
```
It's important that you decode the real events from your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus `VirtualEvent` type. Your custom event must implement the corresponding event trait. Right now, the VirtualEvent system is modeled almost entirely around the HTML spec, but we are interested in slimming it down.
```rust
fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
match event.type_().as_str() {
"keydown" | "keypress" | "keyup" => {
struct CustomKeyboardEvent(web_sys::KeyboardEvent);
impl dioxus::events::KeyboardEvent for CustomKeyboardEvent {
fn char_code(&self) -> usize { self.0.char_code() }
fn ctrl_key(&self) -> bool { self.0.ctrl_key() }
fn key(&self) -> String { self.0.key() }
fn key_code(&self) -> usize { self.0.key_code() }
fn locale(&self) -> String { self.0.locale() }
fn location(&self) -> usize { self.0.location() }
fn meta_key(&self) -> bool { self.0.meta_key() }
fn repeat(&self) -> bool { self.0.repeat() }
fn shift_key(&self) -> bool { self.0.shift_key() }
fn which(&self) -> usize { self.0.which() }
fn get_modifier_state(&self, key_code: usize) -> bool { self.0.get_modifier_state() }
}
VirtualEvent::KeyboardEvent(Rc::new(event.clone().dyn_into().unwrap()))
}
_ => todo!()
```
## Custom raw elements
If you need to go as far as relying on custom elements for your renderer - you totally can. This still enables you to use Dioxus' reactive nature, component system, shared state, and other features, but will ultimately generate different nodes. All attributes and listeners for the HTML and SVG namespace are shuttled through helper structs that essentially compile away (pose no runtime overhead). You can drop in your own elements any time you want, with little hassle. However, you must be absolutely sure your renderer can handle the new type, or it will crash and burn.
These custom elements are defined as unit structs with trait implementations.
For example, the `div` element is (approximately!) defined as such:
```rust
struct div;
impl div {
/// Some glorious documentation about the class property.
#[inline]
fn class<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr("class", val, None, false)
}
// more attributes
}
```
You've probably noticed that many elements in the `rsx!` and `html!` macros support on-hover documentation. The approach we take to custom elements means that the unit struct is created immediately where the element is used in the macro. When the macro is expanded, the doc comments still apply to the unit struct, giving tons of in-editor feedback, even inside a proc macro.
## Compatibility
Forewarning: not every hook and service will work on your platform. Dioxus wraps things that need to be cross-platform in "synthetic" types. However, downcasting to a native type might fail if the types don't match.
There are three opportunities for platform incompatibilities to break your program:
1. When downcasting elements via `Ref.downcast_ref<T>()`
2. When downcasting events via `Event.downcast_ref<T>()`
3. Calling platform-specific APIs that don't exist
The best hooks will properly detect the target platform and still provide functionality, failing gracefully when a platform is not supported. We encourage - and provide - an indication to the user on what platforms a hook supports. For issues 1 and 2, these return a result as to not cause panics on unsupported platforms. When designing your hooks, we recommend propagating this error upwards into user facing code, making it obvious that this particular service is not supported.
This particular code _will panic_ due to the unwrap on downcast_ref. Try to avoid these types of patterns.
```rust
let div_ref = use_node_ref(cx);
cx.render(rsx!{
div { ref: div_ref, class: "custom class",
button { "click me to see my parent's class"
onclick: move |_| if let Some(div_ref) = div_ref {
log::info!("Div class is {}", div_ref.downcast_ref::<web_sys::Element>().unwrap().class())
}
}
}
})
```
## Conclusion
That should be it! You should have nearly all the knowledge required on how to implement your own renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderer, video game UI, and even augmented reality! If you're interesting in contributing to any of the these projects, don't be afraid to reach out or join the community.
@@ -0,0 +1,32 @@
# Dioxus Liveview
Liveview is a configuration where a server and a client work together to render a Dioxus app. Liveview monomorphizes a web application, eliminating the need for frontend-specific APIs.
This is a developer-friendly alternative to the JAM-stack (Javascript + API + Markdown), combining the Wasm-compatibility and async performance of Rust.
## Why liveview?
### No APIs necessary!
Because Liveview combines the server and the client, you'll find dedicated APIs unnecessary. You'll still want to implement a data-fetching service for Live-apps, but this can be implemented as a crate and shared between apps. This approach was designed to let you model out your data requirements without needing to maintain a public versioned API.
You can find more information to data modeling and fetching for LiveApps in the "Book of Dioxus Patterns".
### Ease of deployment
There is no hassle for deploying Dioxus liveview apps - simply upload the binary to your hosting provider of choice. There simply is no need to deal with intermediate APIs. An app's frontend and backend are tightly coupled to each other, meaning that the backed and frontend are always up to date.
This is especially powerful for small teams, where fast iteration, versioned unique prototypes, and simple deployments are important. As your app grows, Dioxus will happily grow with you, serving up to 100K RPS (thanks to async Rust performance).
### Power of server rendering combined with the reactive nature of JavaScript
Liveview apps are backed by a server and enhanced with web-assembly. This completely eliminates the need to write Javascript to add dynamic content to webpages. Apps are written in only **one** language and require essentially 0 knowledge of build systems, transpiling or ECMAScript versions. Minimum browser support is guaranteed to cover 95% of users, and the Dioxus-CLI can be configured to generate polyfills for even more support.
### Support with LiveHost by Dioxus-Labs
The Dioxus-CLI comes ready for use with premium Dioxus-Labs services, like LiveHost. Simply link up your git repo to the Dioxus LiveHost and start deploying your fullstack Dioxus app today. LiveHost supports:
- Versioned fronted/backend with unique links
- Builtin CI/CD
- Managed and pluggable storage database backends
- Serverless support for minimal latency
- Site Analytics
- Lighthouse optimization
- On-premise support (see license terms)
Dioxus LiveHost is a Dioxus LiveApp management service with all supported features in a single binary. For OSS, we permit free usage and deployment of LiveHost to your favorite hosting provider.
Whitespace-only changes.
+72
View File
@@ -0,0 +1,72 @@
# Core Topics
In this chapter, we'll cover some core topics on how Dioxus works and how to best leverage the features to build a beautiful, reactive app.
At a very high level, Dioxus is simply a Rust framework for _declaring_ user interfaces and _reacting_ to changes.
1) We declare what we want our user interface to look like given a state using Rust-based logic and control flow.
2) We declare how we want our state to change when the user triggers an event.
## Declarative UI
Dioxus is a *declarative* framework. This means that instead of manually writing calls to "create element" and "set element background to red," we simply *declare* what we want the element to look like and let Dioxus handle the differences.
Let's pretend that we have a stoplight we need to control - it has a color state with red, yellow, and green as options.
Using an imperative approach, we would have to manually declare each element and then handlers for advancing the stoplight.
```rust
let container = Container::new();
let green_light = Light::new().color("green").enabled(true);
let yellow_light = Light::new().color("yellow").enabled(false);
let red_light = Light::new().color("red").enabled(false);
container.push(green_light);
container.push(yellow_light);
container.push(red_light);
container.onclick(move |_| {
if red_light.enabled() {
red_light.set_enabled(false);
green_light.set_enabled(true);
} else if yellow_light.enabled() {
yellow_light.set_enabled(false);
red_light.set_enabled(true);
} else if green_light.enabled() {
green_light.set_enabled(false);
yellow_light.set_enabled(true);
}
});
```
As the UI grows in scale, our logic to keep each element in the proper state would grow exponentially. This can become very unwieldy and lead to out-of-sync UIs that harm user experience.
Instead, with Dioxus, we *declare* what we want our UI to look like:
```rust
let state = "red";
rsx!(
Container {
Light { color: "red", enabled: {state == "red"} }
Light { color: "yellow", enabled: {state == "yellow"} }
Light { color: "green", enabled: {state == "green"} }
onclick: |_| {
state = match state {
"green" => "yellow",
"yellow" => "red",
"red" => "green",
}
}
}
)
```
Remember: this concept is not new! Many frameworks are declarative - with React being the most popular. Declarative frameworks tend to be much more enjoyable to work with than imperative frameworks.
Here's some reading about declaring UI in React:
- [https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js](https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js)
- [https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44](https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44)
@@ -0,0 +1,33 @@
# Subscriptions
Yew subscriptions are used to schedule update for components into the future. The `Context` object can create subscriptions:
```rust
fn Component(cx: Component<()>) -> DomTree {
let update = cx.schedule();
// Now, when the subscription is called, the component will be re-evaluated
update.consume();
}
```
Whenever a component's subscription is called, the component will then re-evaluated. You can consider the input properties of
a component to be just another form of subscription. By default, the Dioxus component system automatically diffs a component's props
when the parent function is called, and if the props are different, the child component's subscription is called.
The subscription API exposes this functionality allowing hooks and state management solutions the ability to update components whenever
some state or event occurs outside of the component. For instance, the `use_context` hook uses this to subscribe components that use a
particular context.
```rust
fn use_context<I>(cx: Context<T>) -> I {
}
```
@@ -0,0 +1,82 @@
# Concurrent mode
Concurrent mode provides a mechanism for building efficient asynchronous components. With this feature, components don't need to render immediately, and instead can schedule a future render by returning a future.
To make a component asynchronous, simply change its function signature to async.
```rust
fn Example(cx: Context<()>) -> Vnode {
rsx!{ <div> "Hello world!" </div> }
}
```
becomes
```rust
async fn Example(cx: Context<()>) -> Vnode {
rsx!{ <div> "Hello world!" </div> }
}
```
Now, logic in components can be awaited to delay updates of the component and its children. Like so:
```rust
async fn Example(cx: Context<()>) -> Vnode {
let name = fetch_name().await;
rsx!{ <div> "Hello {name}" </div> }
}
async fetch_name() -> String {
// ...
}
```
This component will only schedule its render once the fetch is complete. However, we _don't_ recommend using async/await directly in your components.
Async is a notoriously challenging yet rewarding tool for efficient tools. If not careful, locking and unlocking shared aspects of the component's context can lead to data races and panics. If a shared resource is locked while the component is awaiting, then other components can be locked or panic when trying to access the same resource. These rules are especially important when references to shared global state are accessed using the context object's lifetime. If mutable references to data captured immutably by the context are taken, then the component will panic, and cause confusion.
Instead, we suggest using hooks and future combinators that can safely utilize the safe guards of the component's Context when interacting with async tasks.
As part of our Dioxus hooks crate, we provide a data loader hook which pauses a component until its async dependencies are ready. This caches requests, reruns the fetch if dependencies have changed, and provides the option to render something else while the component is loading.
```rust
async fn ExampleLoader(cx: Context<()>) -> Vnode {
/*
Fetch, pause the component from rendering at all.
The component is locked while waiting for the request to complete
While waiting, an alternate component is scheduled in its place.
This API stores the result on the Context object, so the loaded data is taken as reference.
*/
let name: &Result<SomeStructure> = use_fetch_data("http://example.com/json", ())
.place_holder(|(cx, props)|rsx!{<div> "loading..." </div>})
.delayed_place_holder(1000, |(cx, props)|rsx!{ <div> "still loading..." </div>})
.await;
match name {
Ok(name) => rsx! { <div> "Hello {something}" </div> },
Err(e) => rsx! { <div> "An error occurred :(" </div>}
}
}
```
```rust
async fn Example(cx: Context<()>) -> DomTree {
// Diff this set between the last set
// Check if we have any outstanding tasks?
//
// Eventually, render the component into the VDOM when the future completes
<div>
<Example />
</div>
// Render a div, queue a component
// Render the placeholder first, then when the component is ready, then render the component
<div>
<Suspense placeholder={html!{<div>"Loading"</div>}}>
<Example />
</Suspense>
</div>
}
```
+56
View File
@@ -0,0 +1,56 @@
# Memoization and the arena allocator
Dioxus differs slightly from other UI virtual doms in some subtle ways due to its memory allocator.
One important aspect to understand is how props are passed down from parent components to children. All "components" (custom user-made UI elements) are tightly allocated together in an arena. However, because props and hooks are generically typed, they are casted to any and allocated on the heap - not in the arena with the components.
With this system, we try to be more efficient when leaving the component arena and entering the heap. By default, props are memoized between renders using COW and context. This makes props comparisons fast - done via ptr comparisons on the cow pointer. Because memoization is done by default, parent re-renders will _not_ cascade to children if the child's props did not change.
https://dmitripavlutin.com/use-react-memo-wisely/
This behavior is defined as an implicit attribute to user components. When in React land you might wrap a component is `react.memo`, Dioxus components are automatically memoized via an implicit attribute. You can manually configure this behavior on any component with "nomemo" to disable memoization.
```rust
fn test() -> DomTree {
html! {
<>
<SomeComponent nomemo />
// same as
<SomeComponent nomemo=true />
</>
}
}
static TestComponent: FC<()> = |(cx, props)|html!{<div>"Hello world"</div>};
static TestComponent: FC<()> = |(cx, props)|{
let g = "BLAH";
html! {
<div> "Hello world" </div>
}
};
#[functional_component]
static TestComponent: FC<{ name: String }> = |(cx, props)|html! { <div> "Hello {name}" </div> };
```
## Why this behavior?
"This is different than React, why differ?".
Take a component likes this:
```rust
fn test(cx: Context<()>) -> DomTree {
let Bundle { alpha, beta, gamma } = use_context::<SomeContext>(cx);
html! {
<div>
<Component name=alpha />
<Component name=beta />
<Component name=gamma />
</div>
}
}
```
While the contents of the destructured bundle might change, not every child component will need to be re-rendered.
+155
View File
@@ -0,0 +1,155 @@
# Signals: Skipping the Diff
In most cases, the traditional VirtualDOM diffing pattern is plenty fast. Dioxus will compare trees of VNodes, find the differences, and then update the Renderer's DOM with the updates. However, this can generate a lot of overhead for certain types of components. In apps where reducing visual latency is a top priority, you can opt into the `Signals` api to entirely disable diffing of hot-path components. Dioxus will then automatically construct a state machine for your component, making updates nearly instant.
Signals build on the same infrastructure that powers asynchronous rendering where in-tree values can be updated outside of the render phase. In async rendering, a future is used as the signal source. With the raw signal API, any value can be used as a signal source.
By default, Dioxus will only try to diff subtrees of components with dynamic content, automatically skipping diffing static content.
## What does this look like?
Your component today might look something like this:
```rust
fn Comp(cx: Context<()>) -> DomTree {
let (title, set_title) = use_state(cx, || "Title".to_string());
cx.render(rsx!{
input {
value: title,
onchange: move |new| set_title(new.value())
}
})
}
```
This component is fairly straightforward - the input updates its own value on every change. However, every call to set_title will re-render the component. If we add a large list, then every time we update the title input, Dioxus will need to diff the entire list, over, and over, and over. This is **a lot** of wasted clock-cycles!
```rust
fn Comp(cx: Context<()>) -> DomTree {
let (title, set_title) = use_state(cx, || "Title".to_string());
cx.render(rsx!{
div {
input {
value: title,
onchange: move |new| set_title(new.value())
}
ul {
{0..10000.map(|f| rsx!{
li { "{f}" }
})}
}
}
})
}
```
Many experienced React developers will just say "this is bad design" - but we consider it to be a pit of failure, rather than a pit of success! That's why signals exist - to push you in a more performant (and ergonomic) direction. Signals let us directly bind values to their final place in the VirtualDOM. Whenever the signal value is updated, Dioxus will only the DOM nodes where that signal is used. Signals are built into Dioxus, so we can directly bind attributes of elements to their updates.
We can use signals to generate a two-way binding between data and the input box. Our text input is now just a two-line component!
```rust
fn Comp(cx: Context<()>) -> DomTree {
let mut title = use_signal(&cx, || String::from("Title"));
cx.render(rsx!(input { value: title }))
}
```
For a slightly more interesting example, this component calculates the sum between two numbers, but totally skips the diffing process.
```rust
fn Calculator(cx: Context<()>) -> DomTree {
let mut a = use_signal(&cx, || 0);
let mut b = use_signal(&cx, || 0);
let mut c = a + b;
rsx! {
input { value: a }
input { value: b }
p { "a + b = {c}" }
}
}
```
Do you notice how we can use built-in operations on signals? Under the hood, we actually create a new derived signal that depends on `a` and `b`. Whenever `a` or `b` update, then `c` will update. If we need to create a new derived signal that's more complex that a basic operation (`std::ops`) we can either chain signals together or combine them:
```rust
let mut a = use_signal(&cx, || 0);
let mut b = use_signal(&cx, || 0);
// Chain signals together using the `with` method
let c = a.with(b).map(|(a, b)| *a + *b);
```
## Deref and DerefMut
If we ever need to get the value out of a signal, we can simply `deref` it.
```rust
let mut a = use_signal(&cx, || 0);
let c = *a + *b;
```
Calling `deref` or `derefmut` is actually more complex than it seems. When a value is derefed, you're essentially telling Dioxus that _this_ element _needs_ to be subscribed to the signal. If a signal is derefed outside of an element, the entire component will be subscribed and the advantage of skipping diffing will be lost. Dioxus will throw an error in the console when this happens to tell you that you're using signals wrong, but your component will continue to work.
## Global Signals
Sometimes you want a signal to propagate across your app, either through far-away siblings or through deeply-nested components. In these cases, we use Dirac: Dioxus's first-class state management toolkit. Dirac atoms automatically implement the Signal API. This component will bind the input element to the `TITLE` atom.
```rust
const TITLE: Atom<String> = || "".to_string();
const Provider: FC<()> = |(cx, props)|{
let title = use_signal(&cx, &TITLE);
rsx!(cx, input { value: title })
};
```
If we use the `TITLE` atom in another component, we can cause updates to flow between components without calling render or diffing either component trees:
```rust
const Receiver: FC<()> = |(cx, props)|{
let title = use_signal(&cx, &TITLE);
log::info!("This will only be called once!");
rsx!(cx,
div {
h1 { "{title}" }
div {}
footer {}
}
)
};
```
Dioxus knows that the receiver's `title` signal is used only in the text node, and skips diffing Receiver entirely, knowing to update _just_ the text node.
If you build a complex app on top of Dirac, you'll likely notice that many of your components simply won't be diffed at all. For instance, our Receiver component will never be diffed once it has been mounted!
## Signals and Iterators
Sometimes you want to use a collection of items. With Signals, you can bypass diffing for collections - a very powerful technique to avoid re-rendering on large collections.
By default, Dioxus is limited when you use iter/map. With the `For` component, you can provide an iterator and a function for the iterator to map to.
Dioxus automatically understands how to use your signals when mixed with iterators through Deref/DerefMut. This lets you efficiently map collections while avoiding the re-rendering of lists. In essence, signals act as a hint to Dioxus on how to avoid un-necessary checks and renders, making your app faster.
```rust
const DICT: AtomFamily<String, String> = |_| {};
const List: FC<()> = |(cx, props)|{
let dict = use_signal(&cx, &DICT);
cx.render(rsx!(
ul {
For { each: dict, map: |k, v| rsx!( li { "{v}" }) }
}
))
};
```
## Remote Signals
Apps that use signals will enjoy a pleasant hybrid of server-side and client-side rendering.
```rust
```
## How does it work?
Signals internally use Dioxus' asynchronous rendering infrastructure to perform updates out of the tree.
+54
View File
@@ -0,0 +1,54 @@
# Subtrees
One way of extending the Dioxus VirtualDom is through the use of "Subtrees." Subtrees are chunks of the VirtualDom tree distinct from the rest of the tree. They still participate in event bubbling, diffing, etc, but will have a separate set of edits generated during the diff phase.
For a VirtualDom that has a root tree with two subtrees, the edits follow a pattern of:
Root
-> Tree 1
-> Tree 2
-> Original root tree
- Root edits
- Tree 1 Edits
- Tree 2 Edits
- Root Edits
The goal of this functionality is to enable things like Portals, Windows, and inline alternative renderers without needing to spin up a new VirtualDom.
With the right renderer plugins, a subtree could be rendered as anything - a 3D scene, SVG, or even as the contents of a new window or modal. This functionality is similar to "Portals" in React, but much more "renderer agnostic." Portals, by nature, are not necessarily cross-platform and rely on renderer functionality, so it makes sense to abstract their purpose into the subtree concept.
The desktop renderer comes pre-loaded with the window and notification subtree plugins, making it possible to render subtrees into entirely different windows.
Subtrees also solve the "bridging" issues in React where two different renderers need two different VirtualDoms to work properly. In Dioxus, you only ever need one VirtualDom and the right renderer plugins.
## API
Due to their importance in the hierarchy, Components - not nodes - are treated as subtree roots.
```rust
fn Subtree<P>(cx: Context, props: P) -> DomTree {
}
fn Window() -> DomTree {
Subtree {
onassign: move |e| {
// create window
}
children()
}
}
fn 3dRenderer -> DomTree {
Subtree {
onassign: move |e| {
// initialize bevy
}
}
}
```
+1
View File
@@ -0,0 +1 @@
# Working with Async
@@ -0,0 +1 @@
# Async Callbacks
+1
View File
@@ -0,0 +1 @@
# Tasks
+1
View File
@@ -0,0 +1 @@
# Bundling and Distributing
+191
View File
@@ -0,0 +1,191 @@
# Introduction to Components
In the previous chapter, we learned about Elements and how they can be composed to create a basic User Interface. In this chapter, we'll learn how to group Elements together to form Components.
In this chapter, we'll learn:
- What makes a Component
- How to model a component and its properties in Dioxus
- How to "think declaratively"
## What is a component?
In short, a component is a special function that takes input properties and outputs an Element. Typically, Components serve a single purpose: group functionality of a User Interface. Much like a function encapsulates some specific computation task, a Component encapsulates some specific rendering task.
### Learning through prior art
Let's take a look at a post on r/rust and see if we can sketch out a component representation.
![Reddit Post](../images/reddit_post.png)
This component has a bunch of important information:
- The score
- The number of comments
- How long ago it was posted
- The url short address
- The title
- The username of the original poster
If we wanted to sketch out these requirements in Rust, we would start with a struct:
```rust
struct PostData {
score: i32,
comment_count: u32,
post_time: Instant,
url: String,
title: String,
original_poster_name: String
}
```
If we look at the layout of the component, we notice quite a few buttons and functionality:
- Upvote/Downvote
- View comments
- Share
- Save
- Hide
- Give award
- Report
- Crosspost
- Filter by site
- View article
- Visit user
If we included all this functionality in one `rsx!` call, it would be huge! Instead, let's break the post down into some core pieces:
![Post as Component](../images/reddit_post_components.png)
- **VoteButton**: Upvote/Downvote
- **TitleCard**: Title, Filter-By-Url
- **MetaCard**: Original Poster, Time Submitted
- **ActionCard**: View comments, Share, Save, Hide, Give award, Report, Crosspost
### Modeling with Dioxus
We can start by sketching out the Element hierarchy using Dioxus. In general, our "Post" component will be comprised of the four sub-components listed above. First, let's define our `Post` component.
Unlike normal functions, Dioxus components must explicitly define a single struct to contain all the inputs. These are commonly called "Properties" (props). Our component will be a combination of these properties and a function to render them.
Our props must implement the `Props` trait and - if the component does not borrow any data - `PartialEq`. Both of these can be done automatically through derive macros:
```rust
#[derive(Props, PartialEq)]
struct PostProps {
id: Uuid,
score: i32,
comment_count: u32,
post_time: Instant,
url: String,
title: String,
original_poster: String
}
```
And our render function:
```rust
fn Post((cx, props): Component<PostProps>) -> Element {
cx.render(rsx!{
div { class: "post-container"
VoteButton {
score: props.score,
}
TitleCard {
title: props.title,
url: props.url,
}
MetaCard {
original_poster: props.original_poster,
post_time: props.post_time,
}
ActionCard {
post_id: props.id
}
}
})
}
```
When declaring a component in `rsx!`, we can pass in properties using the traditional Rust struct syntax. Dioxus will automatically call "into" on the property fields, cloning when necessary. Our `Post` component is simply a collection of smaller components wrapped together in a single container.
Let's take a look at the `VoteButton` component. For now, we won't include any interactivity - just the rendering the score and buttons to the screen.
Most of your Components will look exactly like this: a Props struct and a render function. Every component must take a tuple of `Context` and `&Props` and return an `Element`.
As covered before, we'll build our User Interface with the `rsx!` macro and HTML tags. However, with components, we must actually "render" our HTML markup. Calling `cx.render` converts our "lazy" `rsx!` structure into an `Element`.
```rust
#[derive(PartialEq, Props)]
struct VoteButtonProps {
score: i32
}
fn VoteButton((cx, props): Component<VoteButtonProps>) -> Element {
cx.render(rsx!{
div { class: "votebutton"
div { class: "arrow up" }
div { class: "score", "{props.score}"}
div { class: "arrow down" }
}
})
}
```
## Borrowing
You can avoid clones using borrowed component syntax. For example, let's say we passed the `TitleCard` title as an `&str` instead of `String`. In JavaScript, the string would be copied by reference - none of the contents would be copied, but rather the reference to the string's contents are copied. In Rust, this would be similar to calling `clone` on `Rc<str>`.
Because we're working in Rust, we can choose to either use `Rc<str>`, clone `Title` on every re-render of `Post`, or simply borrow it. In most cases, you'll just want to let `Title` be cloned.
To enable borrowed values for your component, we need to add a lifetime to let the Rust compiler know that the output `Element` borrows from the component's props.
```rust
#[derive(Props)]
struct TitleCardProps<'a> {
title: &'a str,
}
fn TitleCard<'a>((cx, props): Component<'a, TitleCardProps>) -> Element<'a> {
cx.render(rsx!{
h1 { "{props.title}" }
})
}
```
For users of React: Dioxus knows *not* to memoize components that borrow property fields. By default, every component in Dioxus is memoized. This can be disabled by the presence of a non-`'static` borrow.
This means that during the render process, a newer version of `TitleCardProps` will never be compared with a previous version, saving some clock cycles.
## The `Context` object
Though very similar to React, Dioxus is different in a few ways. Most notably, React components will not have a `Context` parameter in the component declaration.
Have you ever wondered how the `useState()` call works in React without a `this` object to actually store the state?
React uses global variables to store this information. Global mutable variables must be carefully managed and are broadly discouraged in Rust programs.
```javascript
function Component(props) {
let [state, set_state] = useState(10);
}
```
Because Dioxus needs to work with the rules of Rust it uses the `Context` object to maintain some internal bookkeeping. That's what the `Context` object is: a place for the component to store state, manage listeners, and allocate elements. Advanced users of Dioxus will want to learn how to properly leverage the `Context` object to build robust and performant extensions for Dioxus.
```rust
fn Post((cx /* <-- our Context object*/, props): Component<PostProps>) -> Element {
cx.render(rsx!{ })
}
```
## Moving forward
Next chapter, we'll talk about composing Elements and Components across files to build a larger Dioxus App.
For more references on components, make sure to check out:
- [Components in depth]()
- [Lifecycles]()
- [The Context object]()
- [Optional Prop fields]()
@@ -0,0 +1,175 @@
# Conditional Rendering
Your components will often need to display different things depending on different conditions. With Dioxus, we can use Rust's normal control flow to conditional hide, show, and modify the structure of our markup.
In this chapter, you'll learn:
- How to return different Elements depending on a condition
- How to conditionally include an Element in your structure
- Common patterns like matching and bool mapping
## Conditionally returning Elements
In some components, you might want to render different markup given some condition. The typical example for conditional rendering is showing a "Log In" screen for users who aren't logged into your app. To break down this condition, we can consider two states:
- Logged in: show the app
- Logged out: show the login screen
Using the knowledge from the previous section on components, we'll start by making the app's props:
```rust
#[derive(Props, PartialEq)]
struct AppProps {
logged_in: bool
}
```
Now that we have a "logged_in" flag accessible in our props, we can render two different screens:
```rust
fn App((cx, props): Component<AppProps>) -> Element {
if props.logged_in {
cx.render(rsx!{
DashboardScreen {}
})
} else {
cx.render(rsx!{
LoginScreen {}
})
}
}
```
When the user is logged in, then this component will return the DashboardScreen. Else, the component will render the LoginScreen.
## Using match statements
Rust provides us algebraic datatypes: enums that can contain values. Using the `match` keyword, we can execute different branches of code given a condition.
For instance, we could run a function that returns a Result:
```rust
fn App((cx, props): Component<()>) -> Element {
match get_name() {
Ok(name) => cx.render(rsx!( "Hello, {name}!" )),
Err(err) => cx.render(rsx!( "Sorry, I don't know your name, because an error occurred: {err}" )),
}
}
```
We can even match against values:
```rust
fn App((cx, props): Component<()>) -> Element {
match get_name() {
"jack" => cx.render(rsx!( "Hey Jack, how's Diane?" )),
"diane" => cx.render(rsx!( "Hey Diana, how's Jack?" )),
name => cx.render(rsx!( "Hello, {name}!" )),
}
}
```
Do note: the `rsx!` macro returns a `Closure`, an anonymous function that has a unique type. Because every closure is unique, we cannot return *just* `rsx!` from a match statement. Every `rsx!` call must be rendered first when used in match statements.
To make patterns like these less verbose, the `rsx!` macro accepts an optional first argument on which it will call `render`. Our previous component can be shortened with this alternative syntax:
```rust
fn App((cx, props): Component<()>) -> Element {
match get_name() {
"jack" => rsx!(cx, "Hey Jack, how's Diane?" ),
"diane" => rsx!(cx, "Hey Diana, how's Jack?" ),
name => rsx!(cx, "Hello, {name}!" ),
}
}
```
This syntax even enables us to write a one-line component:
```rust
static App: Fc<()> = |(cx, props)| rsx!(cx, "hello world!");
```
## Nesting RSX
By looking at other examples, you might have noticed that it's possible to include `rsx!` calls inside other `rsx!` calls. With the curly-brace syntax, we can include anything in our `rsx!` that implements `IntoVnodeList`: a marker trait for iterators that produce Elements. `rsx!` itself implements this trait, so we can include it directly:
```rust
rsx!(
div {
{rsx!(
"more rsx!"
)}
}
)
```
As you might expect, we can refactor this structure into two separate calls using variables:
```rust
let title = rsx!( "more rsx!" );
rsx!(
div {
{title}
}
)
```
In the case of a log-in screen, we might want to display the same NavBar and Footer for both logged in and logged out users. We can model this entirely by assigning a `screen` variable to a different Element depending on a condition:
```rust
let screen = match logged_in {
true => rsx!(cx, DashboardScreen {}),
false => rsx!(cx, LoginScreen {})
};
cx.render(rsx!{
Navbar {}
{screen}
Footer {}
})
```
## Boolean Mapping
In the spirit of highly-functional apps, we suggest using the "boolean mapping" pattern when trying to conditionally hide/show an Element.
By default, Rust lets you convert any `boolean` into any other type by calling `and_then()`. We can exploit this functionality in components by mapping to some Element.
```rust
let show_title = true;
rsx!(
div {
{show_title.and_then(|| rsx!{
"This is the title"
})}
}
)
```
We can use this pattern for many things, including options:
```rust
let user_name = Some("bob");
rsx!(
div {
{user_name.map(|name| rsx!("Hello {name}"))}
}
)
```
## Rendering Nothing
Sometimes, you don't want your component to return anything at all. In these cases, we can pass "None" into our bracket. However, Rust is not able to infer that our `None` corresponds to `Element` so we need to cast it appropriately:
```rust
rsx!(cx, {None as Element} )
```
## Moving Forward:
In this chapter, we learned how to render different Elements from a Component depending on a condition. This is a very powerful building block to assemble complex User Interfaces!
In the next chapter, we'll cover how to renderer lists inside your `rsx!`.
Related Reading:
- [RSX in Depth]()
@@ -0,0 +1 @@
# Custom Elements
@@ -0,0 +1 @@
# Custom Renderer
+1
View File
@@ -0,0 +1 @@
# Effects
+11
View File
@@ -0,0 +1,11 @@
# Error handling
Astute observers might have noticed that `Element` is actually a type alias for `Option<VNode>`. You don't need to know what a `VNode` is, but it's important to recognize that we could actually return nothing at all:
```rust
fn App((cx, props): Component<()>) -> Element {
None
}
```
@@ -0,0 +1 @@
# Event handlers
@@ -0,0 +1,297 @@
# Reusing, Importing, and Exporting Components
As your application grows in size, you'll want to start breaking your UI into components and, eventually, different files. This is a great idea to encapsulate functionality of your UI and scale your team.
In this chapter we'll cover:
- Rust's modules
- Pub/Private components
- Structure for large components
## Breaking it down
Let's say our app looks something like this:
```shell
├── Cargo.toml
└── src
└── main.rs
```
```rust
// main.rs
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(App, |c| c);
}
fn App((cx, props): Component<()>) -> Element {}
#[derive(PartialEq, Props)]
struct PostProps{}
fn Post((cx, props): Component<PostProps>) -> Element {}
#[derive(PartialEq, Props)]
struct VoteButtonsProps {}
fn VoteButtons((cx, props): Component<VoteButtonsProps>) -> Element {}
#[derive(PartialEq, Props)]
struct TitleCardProps {}
fn TitleCard((cx, props): Component<TitleCardProps>) -> Element {}
#[derive(PartialEq, Props)]
struct MetaCardProps {}
fn MetaCard((cx, props): Component<MetaCardProps>) -> Element {}
#[derive(PartialEq, Props)]
struct ActionCardProps {}
fn ActionCard((cx, props): Component<ActionCardProps>) -> Element {}
```
That's a lot of components for one file! We've successfully refactored our app into components, but we should probably start breaking it up into a file for each component.
## Breaking into different files
Fortunately, Rust has a built-in module system that's much cleaner than what you might be used to in JavaScript. Because `VoteButtons`, `TitleCard`, `MetaCard`, and `ActionCard` all belong to the `Post` component, let's put them all in a folder together called "post". We'll make a file for each component and move the props and render function.
```rust
// src/post/action.rs
use dioxus::prelude::*;
#[derive(PartialEq, Props)]
struct ActionCardProps {}
fn ActionCard((cx, props): Component<ActionCardProps>) -> Element {}
```
We should also create a `mod.rs` file in the `post` folder so we can use it from our `main.rs`. Our `Post` component and its props will go into this file.
```shell
├── Cargo.toml
└── src
├── main.rs
└── post
├── vote.rs
├── title.rs
├── meta.rs
├── action.rs
└── mod.rs
```
In our `main.rs`, we'll want to declare the `post` module so we can access our `Post` component.
```rust
// main.rs
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(App, |c| c);
}
mod post;
fn App((cx, props): Component<()>) -> Element {
cx.render(rsx!{
post::Post {
id: Uuid::new_v4(),
score: 10,
comment_count: 10,
post_time: std::Instant::now(),
url: "example".to_string(),
title: "Title".to_string(),
original_poster: "me".to_string()
}
})
}
```
If you tried to build this app right now, you'll get an error message saying that `Post is private, trying changing it to public`. This is because we haven't properly exported our component! To fix this, we need to make sure both the Props and Component are declared as "public":
```rust
// src/post/mod.rs
use dioxus::prelude::*;
#[derive(PartialEq, Props)]
pub struct PostProps {}
pub fn Post((cx, props): Component<PostProps>) -> Element {}
```
While we're here, we also need to make sure each of our subcomponents are included as modules and exported.
Our "post/mod.rs" file will eventually look like this:
```rust
use dioxus::prelude::*;
mod vote;
mod title;
mod meta;
mod action;
#[derive(Props, PartialEq)]
pub struct PostProps {
id: uuid::Uuid,
score: i32,
comment_count: u32,
post_time: std::time::Instant,
url: String,
title: String,
original_poster: String
}
pub fn Post((cx, props): Component<PostProps>) -> Element {
cx.render(rsx!{
div { class: "post-container"
vote::VoteButtons {
score: props.score,
}
title::TitleCard {
title: props.title,
url: props.url,
}
meta::MetaCard {
original_poster: props.original_poster,
post_time: props.post_time,
}
action::ActionCard {
post_id: props.id
}
}
})
}
```
Ultimately, including and exporting components is governed by Rust's module system. [The Rust book is a great resource to learn about these concepts in greater detail.](https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html)
## Final structure:
```shell
├── Cargo.toml
└── src
├── main.rs
└── post
├── vote.rs
├── title.rs
├── meta.rs
├── action.rs
└── mod.rs
```
```rust
// main.rs:
use dioxus::prelude::*;
fn main() {
dioxus::desktop::launch(App, |c| c);
}
mod post;
fn App((cx, props): Component<()>) -> Element {
cx.render(rsx!{
post::Post {
id: Uuid::new_v4(),
score: 10,
comment_count: 10,
post_time: std::Instant::now(),
url: "example".to_string(),
title: "Title".to_string(),
original_poster: "me".to_string()
}
})
}
```
```rust
// src/post/mod.rs
use dioxus::prelude::*;
mod vote;
mod title;
mod meta;
mod action;
#[derive(Props, PartialEq)]
pub struct PostProps {
id: uuid::Uuid,
score: i32,
comment_count: u32,
post_time: std::time::Instant,
url: String,
title: String,
original_poster: String
}
pub fn Post((cx, props): Component<PostProps>) -> Element {
cx.render(rsx!{
div { class: "post-container"
vote::VoteButtons {
score: props.score,
}
title::TitleCard {
title: props.title,
url: props.url,
}
meta::MetaCard {
original_poster: props.original_poster,
post_time: props.post_time,
}
action::ActionCard {
post_id: props.id
}
}
})
}
```
```rust
// src/post/vote.rs
use dioxus::prelude::*;
#[derive(PartialEq, Props)]
pub struct VoteButtonsProps {}
pub fn VoteButtons((cx, props): Component<VoteButtonsProps>) -> Element {}
```
```rust
// src/post/title.rs
use dioxus::prelude::*;
#[derive(PartialEq, Props)]
pub struct TitleCardProps {}
pub fn TitleCard((cx, props): Component<TitleCardProps>) -> Element {}
```
```rust
// src/post/meta.rs
use dioxus::prelude::*;
#[derive(PartialEq, Props)]
pub struct MetaCardProps {}
pub fn MetaCard((cx, props): Component<MetaCardProps>) -> Element {}
```
```rust
// src/post/action.rs
use dioxus::prelude::*;
#[derive(PartialEq, Props)]
pub struct ActionCardProps {}
pub fn ActionCard((cx, props): Component<ActionCardProps>) -> Element {}
```
## Moving forward
Next chapter, we'll start to add use code to hide and show Elements with conditional rendering.
For more reading on components:
- [Components in depth]()
- [Lifecycles]()
- [The Context object]()
- [Optional Prop fields]()
+1
View File
@@ -0,0 +1 @@
# Hooks and Internal State
+1
View File
@@ -0,0 +1 @@
# Adding Interactivity
+1
View File
@@ -0,0 +1 @@
# Lifecycle, updates, and effects
+174
View File
@@ -0,0 +1,174 @@
# Conditional Lists and Keys
You will often want to display multiple similar components from a collection of data.
In this chapter, you will learn:
- How to use iterators in `rsx!`
- How to filter and transform data into a list of Elements
- How to create efficient lists with keys
## Rendering data from lists
Thinking back to our analysis of the `r/reddit` page, we notice a list of data that needs to be rendered: the list of posts. This list of posts is always changing, so we cannot just hardcode the lists into our app like:
```rust
rsx!(
div {
Post {/* some properties */}
Post {/* some properties */}
Post {/* some properties */}
}
)
```
Instead, we need to transform the list of data into a list of Elements.
For convenience, `rsx!` supports any type in curly braces that implements the `IntoVnodeList` trait. Conveniently, every iterator that returns something that can be rendered as an Element also implements `IntoVnodeList`.
As a simple example, let's render a list of names. First, start with our input data:
```rust
let names = ["jim", "bob", "jane", "doe"];
```
Then, we create a new iterator by calling `iter` and then `map`. In our `map` function, we'll place render our template.
```rust
let name_list = names.iter().map(|name| rsx!(
li { "{name}" }
));
```
Finally, we can include this list in the final structure:
```rust
rsx!(
ul {
{name_list}
}
)
```
The HTML-rendered version of this list would follow what you would expect:
```html
<ul>
<li> jim </li>
<li> bob </li>
<li> jane </li>
<li> doe </li>
</ul>
```
### Rendering our posts with a PostList component
Let's start by modeling this problem with a component and some properties.
For this example, we're going to use the borrowed component syntax since we probably have a large list of posts that we don't want to clone every time we render the Post List.
```rust
#[derive(Props, PartialEq)]
struct PostListProps<'a> {
posts: &'a [PostData]
}
```
Next, we're going to define our component:
```rust
fn App((cx, props): Component<PostList>) -> Element {
// First, we create a new iterator by mapping the post array
let posts = props.posts.iter().map(|post| rsx!{
Post {
title: post.title,
age: post.age,
original_poster: post.original_poster
}
});
// Finally, we render the post list inside of a container
cx.render(rsx!{
ul { class: "post-list"
{posts}
}
})
}
```
## Filtering Iterators
Rust's iterators are extremely powerful, especially when used for filtering tasks. When building user interfaces, you might want to display a list of items filtered by some arbitrary check.
As a very simple example, let's set up a filter where we only list names that begin with the letter "J".
Let's make our list of names:
```rust
let names = ["jim", "bob", "jane", "doe"];
```
Then, we create a new iterator by calling `iter`, then `filter`, then `map`. In our `filter` function, we'll only allow "j" names, and in our `map` function, we'll render our template.
```rust
let name_list = names
.iter()
.filter(|name| name.starts_with('j'))
.map(|name| rsx!( li { "{name}" }));
```
Rust's iterators provide us tons of functionality and are significantly easier to work with than JavaScript's map/filter/reduce.
For keen Rustaceans: notice how we don't actually call `collect` on the name list. If we `collected` our filtered list into new Vec, then we would need to make an allocation to store these new elements. Instead, we create an entirely new _lazy_ iterator which will then be consumed by Dioxus in the `render` call.
The `render` method is extraordinarily efficient, so it's best practice to let it do most of the allocations for us.
## Keeping list items in order with key
The examples above demonstrate the power of iterators in `rsx!` but all share the same issue: a lack of "keys". Whenever you render a list of elements, each item in the list must be **uniquely identifiable**. To make each item unique, you need to give it a "key".
In Dioxus, keys are strings that uniquely identifies it among other items in that array:
```rust
rsx!( li { key: "a" } )
```
Keys tell Dioxus which array item each component corresponds to, so that it can match them up later. This becomes important if your array items can move (e.g. due to sorting), get inserted, or get deleted. A well-chosen key helps Dioxus infer what exactly has happened, and make the correct updates to the screen
NB: the language from this section is strongly borrowed from [React's guide on keys](https://reactjs.org/docs/lists-and-keys.html).
### Where to get your key
Different sources of data provide different sources of keys:
- _Data from a database_: If your data is coming from a database, you can use the database keys/IDs, which are unique by nature.
- _Locally generated data_: If your data is generated and persisted locally (e.g. notes in a note-taking app), use an incrementing counter or a package like `uuid` when creating items.
### Rules of keys
- Keys must be unique among siblings. However, it’s okay to use the same keys for Elements in different arrays.
- Keys must not change or that defeats their purpose! Don’t generate them while rendering.
### Why does Dioxus need keys?
Imagine that files on your desktop didn’t have names. Instead, you’d refer to them by their order — the first file, the second file, and so on. You could get used to it, but once you delete a file, it would get confusing. The second file would become the first file, the third file would be the second file, and so on.
File names in a folder and Element keys in an array serve a similar purpose. They let us uniquely identify an item between its siblings. A well-chosen key provides more information than the position within the array. Even if the position changes due to reordering, the key lets Dioxus identify the item throughout its lifetime.
### Gotcha
You might be tempted to use an item’s index in the array as its key. In fact, that’s what Dioxus will use if you don’t specify a key at all. But the order in which you render items will change over time if an item is inserted, deleted, or if the array gets reordered. Index as a key often leads to subtle and confusing bugs.
Similarly, do not generate keys on the fly, `gen_random`. This will cause keys to never match up between renders, leading to all your components and DOM being recreated every time. Not only is this slow, but it will also lose any user input inside the list items. Instead, use a stable ID based on the data.
Note that your components won’t receive key as a prop. It’s only used as a hint by Dioxus itself. If your component needs an ID, you have to pass it as a separate prop:
```rust
Post { key: "{key}", id: "{id}" }
```
## Moving on
In this section, we learned:
- How to render lists of data
- How to use iterator tools to filter and transform data
- How to use keys to render lists efficiently
Moving forward, we'll finally cover user input and interactivity.
@@ -0,0 +1 @@
# Managing State
+1
View File
@@ -0,0 +1 @@
# Memoization
+63
View File
@@ -0,0 +1,63 @@
# VNodes with RSX, HTML, and NodeFactory
Many modern frameworks provide a domain-specific-language for declaring user-interfaces. In the case of React, this language extension is called JSX and must be handled through additional dependencies and pre/post processors to transform your source code. With Rust, we can simply provide a procedural macro in the Dioxus dependency itself that mimics the JSX language.
With Dioxus, we actually ship two different macros - a macro that mimics JSX (the `html!` macro) and a macro that mimics Rust's native nested-struct syntax (the `rsx!` macro). These macros simply transform their inputs into NodeFactory calls.
For instance, this html! call:
```rust
html!(<div> "hello world" </div>)
```
becomes this NodeFactory call:
```rust
|f| f.element(
dioxus_elements::div, // tag
[], // listeners
[], // attributes
[f.static_text("hello world")], // children
None // key
)
```
The NodeFactory API is fairly ergonomic, making it a viable option to use directly. The NodeFactory API is also compile-time correct and has incredible syntax highlighting support. We use what Rust calls a "unit type" - the `dioxus_elements::div` and associated methods to ensure that a `div` can only have attributes associated with `div`s. This lets us tack on relevant documentation, autocomplete support, and jump-to-definition for methods and attributes.
![Compile time correct syntax](../images/compiletimecorrect.png)
## html! macro
The html! macro supports a limited subset of the html standard. Rust's macro parsing tools are somewhat limited, so all text between tags _must be quoted_.
However, writing HTML by hand is a bit tedious - IDE tools for Rust don't support linting/autocomplete/syntax highlighting. We suggest using RSX - it's more natural for Rust programs and _does_ integrate well with Rust IDE tools.
```rust
let name = "jane";
let pending = false;
let count = 10;
dioxus::ssr::render_lazy(html! {
<div>
<p> "Hello, {name}!" </p>
<p> "Status: {pending}!" </p>
<p> "Count {count}!" </p>
</div>
});
```
## rsx! macro
The rsx! macro is a VNode builder macro designed especially for Rust programs. Writing these should feel very natural, much like assembling a struct. VSCode also supports these with code folding, bracket-tabbing, bracket highlighting, section selecting, inline documentation, GOTO definition, and refactoring support.
When helpful, the Dioxus VSCode extension provides a way of converting a selection of HTML directly to RSX, so you can import templates from the web directly into your existing app.
It's also a bit easier on the eyes than HTML.
```rust
dioxus::ssr::render_lazy(rsx! {
div {
p {"Hello, {name}!"}
p {"Status: {pending}!"}
p {"Count {count}!"}
}
});
```
In the next section, we'll cover the `rsx!` macro in more depth.
+229
View File
@@ -0,0 +1,229 @@
# RSX in Depth
The RSX macro makes it very easy to assemble complex UIs with a very natural Rust syntax:
```rust
rsx!(div {
button {
"Add todo",
onclick: move |e| todos.write().new_todo()
}
ul {
class: "todo-list"
(todos.iter().map(|(key, todo)| rsx!(
li {
class: "beautiful-todo"
key: "f"
h3 { "{todo.title}" }
p { "{todo.contents}"}
}
)))
}
})
```
In this section, we'll cover the `rsx!` macro in depth. If you prefer to learn through examples, the `reference` guide has plenty of examples on how to use `rsx!` effectively.
### Element structure
```rust
div {
hidden: false,
"some text"
child {}
Component {}
{/* literal tokens that resolve to vnodes */}
}
```
Each element takes a comma-separated list of expressions to build the node. Roughly, here's how they work:
- `name: value` sets a property on this element.
- `"text"` adds a new text element
- `tag {}` adds a new child element
- `CustomTag {}` adds a new child component
- `{expr}` pastes the `expr` tokens literally. They must be `IntoIterator<T> where T: IntoVnode` to work properly
Commas are entirely optional, but might be useful to delineate between elements and attributes.
The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena. To push you in the right direction, all text-based attributes take `std::fmt::Arguments` directly, so you'll want to reach for `format_args!` when the built-in `f-string` interpolation just doesn't cut it.
### Ignoring `cx.render` with `rsx!(cx, ...)`
Sometimes, writing `cx.render` is a hassle. The `rsx! macro will accept any token followed by a comma as the target to call "render" on:
```rust
cx.render(rsx!( div {} ))
// becomes
rsx!(cx, div {})
```
### Conditional Rendering
Sometimes, you might not want to render an element given a condition. The rsx! macro will accept any tokens directly contained with curly braces, provided they resolve to a type that implements `IntoIterator<VNode>`. This lets us write any Rust expression that resolves to a VNode:
```rust
rsx!({
if enabled {
rsx!(cx, div {"enabled"})
} else {
rsx!(cx, li {"disabled"})
}
})
```
A convenient way of hiding/showing an element is returning an `Option<VNode>`. When combined with `and_then`, we can succinctly control the display state given some boolean:
```rust
rsx!({
a.and_then(rsx!(div {"enabled"}))
})
```
It's important to note that the expression `rsx!()` is typically lazy - this expression must be _rendered_ to produce a VNode. When using match statements, we must render every arm as to avoid the `no two closures are identical` rule that Rust imposes:
```rust
// this will not compile!
match case {
true => rsx!(div {}),
false => rsx!(div {})
}
// the nodes must be rendered first
match case {
true => rsx!(cx, div {}),
false => rsx!(cx, div {})
}
```
### Lists
Again, because anything that implements `IntoIterator<VNode>` is valid, we can use lists directly in our `rsx!`:
```rust
let items = vec!["a", "b", "c"];
cx.render(rsx!{
ul {
{items.iter().map(|f| rsx!(li { "a" }))}
}
})
```
Sometimes, it makes sense to render VNodes into a list:
```rust
let mut items = vec![];
for _ in 0..5 {
items.push(rsx!(cx, li {} ))
}
rsx!(cx, {items} )
```
#### Lists and Keys
When rendering the VirtualDom to the screen, Dioxus needs to know which elements have been added and which have been removed. These changes are determined through a process called "diffing" - an old set of elements is compared to a new set of elements. If an element is removed, then it won't show up in the new elements, and Dioxus knows to remove it.
However, with lists, Dioxus does not exactly know how to determine which elements have been added or removed if the order changes or if an element is added or removed from the middle of the list.
In these cases, it is vitally important to specify a "key" alongside the element. Keys should be persistent between renders.
```rust
fn render_list(cx: Context, items: HashMap<String, Todo>) -> DomTree {
rsx!(cx, ul {
{items.iter().map(|key, item| {
li {
key: key,
h2 { "{todo.title}" }
p { "{todo.contents}" }
}
})}
})
}
```
There have been many guides made for keys in React, so we recommend reading up to understand their importance:
- [React guide on keys](https://reactjs.org/docs/lists-and-keys.html)
- [Importance of keys (Medium)](https://kentcdodds.com/blog/understanding-reacts-key-prop)
### Complete Reference
```rust
let text = "example";
cx.render(rsx!{
div {
h1 { "Example" },
{title}
// fstring interpolation
"{text}"
p {
// Attributes
tag: "type",
// Anything that implements display can be an attribute
abc: 123,
enabled: true,
// attributes also supports interpolation
// `class` is not a restricted keyword unlike JS and ClassName
class: "big small wide short {text}",
class: format_args!("attributes take fmt::Arguments. {}", 99),
tag: {"these tokens are placed directly"}
// Children
a { "abcder" },
// Children with attributes
h2 { "hello", class: "abc-123" },
// Child components
CustomComponent { a: 123, b: 456, key: "1" },
// Child components with paths
crate::components::CustomComponent { a: 123, b: 456, key: "1" },
// Iterators
{ (0..3).map(|i| rsx!( h1 {"{:i}"} )) },
// More rsx!, or even html!
{ rsx! { div { } } },
{ html! { <div> </div> } },
// Matching
// Requires rendering the nodes first.
// rsx! is lazy, and the underlying closures cannot have the same type
// Rendering produces the VNode type
{match rand::gen_range::<i32>(1..3) {
1 => rsx!(cx, h1 { "big" })
2 => rsx!(cx, h2 { "medium" })
_ => rsx!(cx, h3 { "small" })
}}
// Optionals
{true.and_then(|f| rsx!( h1 {"Conditional Rendering"} ))}
// Child nodes
{cx.children()}
// Any expression that is `IntoVNode`
{expr}
}
}
})
```
@@ -0,0 +1 @@
# Server-side components
+1
View File
@@ -0,0 +1 @@
# Global State
+1
View File
@@ -0,0 +1 @@
# Suspense
+29
View File
@@ -0,0 +1,29 @@
## Testing
To test your Rust code, you can annotate any function with the `#[test]` block. In VSCode with RA, this will provide a lens to click and run the test.
```rust
#[test]
fn component_runs() {
assert!(true)
}
```
This will test your Rust code _without_ going through the browser. This is ideal for squashing logic bugs and ensuring components render appropriately when the browsers's DOM is not needed. If you need to run tests in the browser, you can annotate your blocks with the `#[dioxus::test]` block.
```rust
#[dioxus::test]
fn runs_in_browser() {
// ...
}
```
Then, when you run
```
$ dioxus test --chrome
```
Dioxus will build and test your code using the Chrome browser as a harness.
There's a lot more to testing if you dive in, so check out the [Testing]() guide for more information
+159
View File
@@ -0,0 +1,159 @@
# Declaring your first UI with Elements
Every user interface you've ever used is just a symphony of tiny widgets working together to abstract over larger complex functions. In Dioxus, we call these tiny widgets "Elements." Using Components, you can easily compose Elements into larger groups to form even larger structures: Apps.
In this chapter, we'll cover:
- Declaring our first Element
- Composing Elements together
- Element properties
## Declaring our first Element
Because Dioxus is mostly used with HTML/CSS renderers, the default Element "collection" is HTML. Provided the `html` feature is not disabled, we can declare Elements using the `rsx!` macro:
```rust
#use dioxus::prelude::*;
rsx!(
div {}
)
```
As you might expect, we can render this call using Dioxus-SSR to produce valid HTML:
```rust
#use dioxus::prelude::*;
dioxus::ssr::render_lazy(rsx!(
div {}
))
```
Produces:
```html
<div></div>
```
We can construct any valid HTML tag with the `tag {}` pattern and expect the resulting HTML structure to resemble our declaration.
## Composing Elements
Of course, we need more complex structures to make our apps actually useful! Just like HTML, the `rsx!` macro lets us nest Elements inside of each other.
```rust
#use dioxus::prelude::*;
rsx!(
div {
h1 {}
h2 {}
p {}
}
)
```
As you might expect, the generated HTML for this structure would look like:
```html
<div>
<h1></h1>
<h2></h2>
<p></p>
</div>
```
With the default configuration, any Element defined within the `dioxus-html` crate can be declared in this way. To create your own new elements, see the `Custom Elements` Advanced Guide.
## Text Elements
Dioxus also supports a special type of Element: Text. Text Elements do not accept children, but rather just string literals denoted with double quotes.
```rust
rsx! (
"hello world"
)
```
Text Elements can be composed within other Elements:
```rust
rsx! (
div {
h1 { "hello world" }
p { "Some body content" }
}
)
```
Text can also be formatted with any value that implements `Display`. We use [f-string formatting](https://docs.rs/fstrings/0.2.3/fstrings/) - a "coming soon" feature for stable Rust that is familiar for Python and JavaScript users:
```rust
let name = "Bob";
rsx! ( "hello {name}" )
```
Unfortunately, you cannot drop in arbitrary expressions directly into the string literal. In the cases where we need to compute a complex value, we'll want to use `format_args!` directly. Due to specifics of how the `rsx!` macro (we'll cover later), our call to `format_args` must be contained within curly braces *and* square braces.
```rust
rsx!( {[format_args!("Hello {}", if enabled { "Jack" } else { "Bob" } )]} )
```
This is different from React's way of generating arbitrary markup but fits within idiomatic Rust.
Typically, with Dioxus, you'll just want to compute your substrings outside of the `rsx!` call:
```rust
let name = if enabled { "Jack" } else { "Bob" };
rsx! ( "hello {name}" )
```
## Attributes
Every Element in your User Interface will have some sort of properties that the renderer will use when drawing to the screen. These might inform the renderer if the component should be hidden, what its background color should be, or to give it a specific name or ID.
To do this, we use the familiar struct-style syntax that Rust provides. Commas are optional:
```rust
rsx!(
div {
hidden: true,
background_color: "blue",
class: "card color-{mycolor}"
}
)
```
Each field is defined as a method on the element in the `dioxus-html` crate. This prevents you from misspelling a field name and lets us provide inline documentation. When you need to use a field not defined as a method, you have two options:
1) file an issue if the attribute _should_ be enabled
2) add a custom attribute on-the-fly
To use custom attributes, simply put the attribute name in quotes followed by a colon:
```rust
rsx!(
div {
"customAttr": "important data here"
}
)
```
Note: the name of the custom attribute must match exactly what you want the renderer to output. All attributes defined as methods in `dioxus-html` follow the snake_case naming convention. However, they internally translate their snake_case convention to HTML's camelCase convention.
## Listeners
Listeners are a special type of Attribute that only accept functions. Listeners let us attach functionality to our Elements by running a provided closure whenever the specified Listener is triggered.
We'll cover listeners in more depth in the Listeners chapter, but for now, just know that every listener must start with the `on` keyword and can accept either a closure or an expression wrapped in curly braces.
```rust
rsx!(
div {
onclick: move |_| {}
onmouseover: {handler},
}
)
```
## Moving On
This chapter just scratches the surface on how Elements can be defined.
We learned:
- Elements are the basic building blocks of User Interfaces
- Elements can contain other elements
- Elements can either be a named container or text
- Some Elements have properties that the renderer can use to draw the UI to the screen
Next, we'll compose Elements together to form components.
+10
View File
@@ -0,0 +1,10 @@
# Next Steps and Advanced Topics
Congrats! You've made it through the `learning Dioxus` book. Throughout this tutorial, you've learned a ton:
- How to build User Interfaces with Elements
- How to compose Element groups together as Components
- How to handle user input with event listeners
- How to manage global and local state
- How to work with async using tasks, coroutines, and suspense
+161
View File
@@ -0,0 +1,161 @@
# Hello World
Let's put together a simple "hello world" desktop application to get acquainted with Dioxus.
In this chapter, we'll cover:
- Starting a new Dioxus project with Cargo
- Adding Dioxus as a dependency
- Launching our first component as the app root
### A new project with Cargo
First, let's start a new project. Rust has the concept of executables and libraries. Executables have a `main.rs` and libraries have `lib.rs`. A project may have both. Our `hello world` will be an executable - we expect our app to launch when we run it! Cargo provides this for us:
```shell
$ cargo new --bin hello-dioxus
```
Now, we can `cd` into our project and poke around:
```shell
$ cd hello-dioxus
$ tree
.
├── Cargo.toml
├── .git
├── .gitignore
└── src
└── main.rs
```
We are greeted with a pre-initialized git repository, our code folder (`src`) and our project file (`Cargo.toml`).
Our `src` folder holds our code. Our `main.rs` file holds our `fn main` which will be executed when our app is ran.
```shell
$ more src/main.rs
```
```rust
fn main() {
println!("Hello, world!");
}
```
Right now, whenever our app is launched, "Hello world" will be echoed to the terminal.
```shell
$ cargo run
Compiling hello-dioxus v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 0.41s
Running `target/debug/hello-dioxus`
Hello, world!
```
Our `Cargo.toml` file holds our dependencies and project flags.
```shell
$ cat Cargo.toml
```
```toml
[package]
name = "hello-dioxus"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
```
### Adding Dioxus as a dependency
To use the Dioxus library, we'll want to add the most recent version of `Dioxus` to our crate. If you have `cargo edit` installed, simply call:
```shell
$ cargo add dioxus --features desktop
```
It's very important to add `dioxus` with the `desktop` feature for this example. The `dioxus` crate is a batteries-include crate that combines a bunch of utility crates together, ensuring compatibility of the most important parts of the ecosystem. Under the hood, the `dioxus` crate configures various renderers, hooks, debug tooling, and more. The `desktop` feature ensures the we only depend on the smallest set of required crates to compile and render.
If you plan to develop extensions for the `Dioxus` ecosystem, please use the `dioxus` crate with the `core` feature to limit the amount of dependencies your project brings in.
### Our first app
Now, let's edit our `main.rs` file:
```rust
use dioxus::prelude::*;
fn main() {
dioxus::desktop::start(App, |c| c);
}
fn App((cx, props): Component<()>) -> Element {
cx.render(rsx! (
div { "Hello, world!" }
))
}
```
At this point, you could call `cargo run` and be greeted with a simple `Hello, World!` screen:
![hello world](images/helloworld.png)
### Dissecting our example
This bit of code imports everything from the the `prelude` module. This brings into scope the right traits, types, and macros needed for working with Dioxus.
```rust
use dioxus::prelude::*;
```
This initialization code launches a Tokio runtime on a helper thread where your code will run. Then, the WebView renderer will be launched on the main-thread. Due to platform requirements, the main thread is blocked by your app's event loop.
```rust
fn main() {
dioxus::desktop::start(App, |c| c);
}
```
Finally, our app. Every component in Dioxus is a function that takes in `Context` and `Props` and returns an `Element`.
```rust
fn App((cx, props): Component<()>) -> Element {
cx.render(rsx! {
div { "Hello, world!" }
})
}
```
In cases where props need to borrow from their parent, you will need to specify lifetimes using the function syntax:
```rust
fn App<'a>(cx: Component<'a, ()>) -> Element<'a> {
cx.render(rsx! {
div { "Hello, world!" }
})
}
```
Writing `fn App((cx, props): Component<()>) -> Element {` might become tedious. Rust will also let you write functions as static closures, but these types of Components cannot have props that borrow data.
```rust
static App: Fc<()> = |(cx, props)| {
cx.render(rsx! {
div { "Hello, world!" }
})
};
```
### The `Context` object
In React, you'll want to store data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering. In Dioxus, you are given an explicit `Context` object to control how the component renders and stores data.
### The `rsx!` macro
Next, we're greeted with the `rsx!` macro. This lets us add a custom DSL for declaratively building the structure of our app. The semantics of this macro are similar to that of JSX and HTML, though with a familiar Rust-y interface. The `html!` macro is also available for writing components with a JSX/HTML syntax.
The `rsx!` macro is lazy: it does not immediately produce elements or allocates, but rather builds a closure which can be rendered with `cx.render`.
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+1
View File
@@ -0,0 +1 @@
# Contributors
+67
View File
@@ -0,0 +1,67 @@
# Overview
In this chapter, we're going to get "set up" with a small desktop application.
We'll learn about:
- Installing the Rust programming language
- Installing the Dioxus CLI for bundling and developing
- Suggested cargo extensions
For platform-specific guides, check out the [Platform Specific Guides](../platforms/00-index.md).
# Setting up Dioxus
Dioxus requires a few main things to get up and running:
- The [Rust compiler](https://www.rust-lang.org) and associated build tooling
- An editor of your choice, ideally configured with the [Rust-Analyzer LSP plugin](https://rust-analyzer.github.io)
Dioxus integrates very well with the Rust-Analyzer IDE plugin which will provide appropriate syntax highlighting, code navigation, folding, and more.
### Dioxus-CLI for dev server, bundling, etc.
We also recommend installing the Dioxus CLI. The Dioxus CLI automates building and packaging for various targets and integrates with simulators, development servers, and app deployment. To install the CLI, you'll need cargo (should be automatically installed with Rust):
```
$ cargo install dioxus-cli
```
You can update the dioxus-cli at any time with:
```
$ cargo install --force dioxus-cli
```
We provide this 1st-party tool to save you from having to run potentially untrusted code every time you add a crate to your project - as is standard in the NPM ecosystem.
### Suggested extensions
If you want to keep your traditional `npm install XXX` workflow for adding packages, you might want to install `cargo-edit` and a few other fun `cargo` extensions:
- [cargo edit](https://github.com/killercup/cargo-edit) for adding dependencies from the CLI
- [cargo-expand](https://github.com/dtolnay/cargo-expand) for expanding macro calls
- [cargo tree](https://doc.rust-lang.org/cargo/commands/cargo-tree.html) - an integrated cargo command that lets you inspect your dependency tree
That's it! We won't need to touch NPM/WebPack/Babel/Parcel, etc. However, you _can_ configure your app to use WebPack with [traditional WASM-pack tooling](https://rustwasm.github.io/wasm-pack/book/tutorials/hybrid-applications-with-webpack/using-your-library.html).
## Rust Knowledge
With Rust, things like benchmarking, testing, and documentation are included in the language. We strongly recommend going through the official Rust book _completely_. However, our hope is that a Dioxus app can serve as a great first project. With Dioxus you'll learn about:
- Error handling
- Structs, Functions, Enums
- Closures
- Macros
We've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need deep knowledge on async, lifetimes, or smart pointers until you really start building complex Dioxus apps.
We strongly encourage exploring the guides for more information on how to work with the integrated tooling:
- [Testing](Testing.md)
- [Documentation](Documentation.md)
- [Benchmarking](Benchmarking.md)
- [Building](Building.md)
- [Modules](Modules.md)
- [Crates](Crates.md)
@@ -0,0 +1 @@
# Advanced Guides
+1
View File
@@ -0,0 +1 @@
# Defining Components
+13
View File
@@ -0,0 +1,13 @@
# Putting it all together
So far, we've covered the basics of Dioxus. We've talked about:
- Elements
- Components
- Interactivity
- State Management
- Async Actions
- Styling
In this chapter, we'll build a real-world weather app that combines everything we've learned into a cute application that you can run locally. It'll let us monitor different locations simultaneously and periodically check for updates.
+1
View File
@@ -0,0 +1 @@
# New app
+1
View File
@@ -0,0 +1 @@
# Publishing
+1
View File
@@ -0,0 +1 @@
# Defining State
+1
View File
@@ -0,0 +1 @@
# Structuring our app
+1
View File
@@ -0,0 +1 @@
# Styling