wip: remove runner on hook and then update docs

This commit is contained in:
Jonathan Kelley 2022-01-02 02:15:04 -05:00
parent 7d41a16208
commit d1560450ba
95 changed files with 1339 additions and 675 deletions

View File

@ -67,3 +67,4 @@ SegVec
contentful
Jank
noderef
reborrow

View File

@ -51,7 +51,7 @@ fn app(cx: Scope) -> Element {
button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" }
))
};
}
```
Dioxus can be used to deliver webapps, desktop apps, static sites, liveview apps, mobile apps (WIP), and more. At its core, Dioxus is entirely renderer agnostic and has great documentation for creating new renderers for any platform.
@ -73,8 +73,8 @@ If you know React, then you already know Dioxus.
<th><a href="https://dioxuslabs.com/guide/">Web</a></th>
<th><a href="https://dioxuslabs.com/guide/">Desktop</a></th>
<th><a href="https://dioxuslabs.com/guide/">Mobile</a></th>
<th><a href="https://dioxuslabs.com/guide/">TUI</a></th>
<th><a href="https://dioxuslabs.com/guide/">State</a></th>
<th><a href="https://dioxuslabs.com/guide/">Docs</a></th>
<th><a href="https://dioxuslabs.com/guide/">Tools</a></th>
<tr>
</table>
@ -83,9 +83,9 @@ If you know React, then you already know Dioxus.
## Examples Projects:
| File Navigator (Desktop) | WiFi scanner (Desktop) | TodoMVC (All platforms) | Ecommerce w/ Tailwind (Liveview) |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| [![File Explorer](https://github.com/DioxusLabs/example-projects/raw/master/file-explorer/image.png)](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer) | [![Wifi Scanner Demo](https://github.com/DioxusLabs/example-projects/raw/master/wifi-scanner/demo_small.png)](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner) | [![TodoMVC example](https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png)](https://github.com/DioxusLabs/example-projects/blob/master/todomvc) | [![Ecommerce Example](https://github.com/DioxusLabs/example-projects/raw/master/ecommerce-site/demo.png)](https://github.com/DioxusLabs/example-projects/blob/master/ecommerce-site) |
| File Navigator (Desktop) | WiFi scanner (Desktop) | TodoMVC (All platforms) | E-commerce w/ Tailwind (Liveview) |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [![File Explorer](https://github.com/DioxusLabs/example-projects/raw/master/file-explorer/image.png)](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer) | [![Wifi Scanner Demo](https://github.com/DioxusLabs/example-projects/raw/master/wifi-scanner/demo_small.png)](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner) | [![TodoMVC example](https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png)](https://github.com/DioxusLabs/example-projects/blob/master/todomvc) | [![E-commerce Example](https://github.com/DioxusLabs/example-projects/raw/master/ecommerce-site/demo.png)](https://github.com/DioxusLabs/example-projects/blob/master/ecommerce-site) |
See the awesome-dioxus page for a curated list of content in the Dioxus Ecosystem.

View File

@ -18,7 +18,7 @@ fn App(cx: Scope) -> Element {
In general, Dioxus and React share many functional similarities. If this guide is lacking in any general concept or an error message is confusing, React's documentation might be more helpful. We are dedicated to providing 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!
> This is introduction book! For advanced topics, check out the [Reference](https://dioxuslabs.com/reference) instead.
> This is an introduction book! For advanced topics, check out the [Reference](https://dioxuslabs.com/reference) instead.
## Multiplatform
@ -29,6 +29,7 @@ Right now, we have several 1st-party renderers:
- Tao/Tokio (for Desktop apps)
- Tao/Tokio (for Mobile apps)
- SSR (for generating static markup)
- TUI/Rink (for terminal-based apps)
### Web Support
---
@ -40,12 +41,10 @@ Because the web is a fairly mature platform, we expect there to be very little A
[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/)
- [TodoMVC](https://github.com/DioxusLabs/example-projects/tree/master/todomvc)
- [ECommerce](https://github.com/DioxusLabs/example-projects/tree/master/ecommerce-site)
[![TodoMVC example](https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png)](https://github.com/DioxusLabs/example-projects/blob/master/todomvc)
### SSR Support
---
Dioxus supports server-side rendering!
@ -59,7 +58,7 @@ let contents = dioxus::ssr::render_vdom(&dom);
[Jump to the getting started guide for SSR.]()
Examples:
- [Example DocSite]()
- [Example DocSite](https://github.com/dioxusLabs/docsite)
- [Tide WebServer]()
- [Markdown to fancy HTML generator]()
@ -72,9 +71,8 @@ Desktop APIs will likely be in flux as we figure out better patterns than our El
[Jump to the getting started guide for Desktop.]()
Examples:
- [File explorer]()
- [Bluetooth scanner]()
- [Device Viewer]()
- [File explorer](https://github.com/dioxusLabs/file-explorer/)
- [WiFi scanner](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner)
[![File ExplorerExample](https://github.com/DioxusLabs/file-explorer-example/raw/master/image.png)](https://github.com/dioxusLabs/file-explorer/)
@ -87,8 +85,7 @@ Mobile support is currently best suited for CRUD-style apps, ideally for interna
[Jump to the getting started guide for Mobile.]()
Examples:
- [Todo App]()
- [Chat App]()
- [Todo App](https://github.com/DioxusLabs/example-projects/blob/master/ios_demo)
### LiveView / Server Component Support
---

View File

@ -3,96 +3,39 @@
- [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)
- [Passing children and attributes](concepts/component_children.md)
- [Conditional Rendering](concepts/conditional_rendering.md)
- [Lists](concepts/lists.md)
- [Adding Interactivity](concepts/interactivity.md)
- [Hooks and Internal State](concepts/hooks.md)
- [Event handlers](concepts/event_handlers.md)
- [User Input and Controlled Components](concepts/user_input.md)
- [Lifecycle, updates, and effects](concepts/lifecycles.md)
<!-- Responding to Events
State: A Component's Memory
Render and Commit
State as a Snapshot
Queueing a Series of State Updates
Updating Objects in State
Updating Arrays in State -->
<!-- Reacting to Input with State
Choosing the State Structure
Sharing State Between Components
Preserving and Resetting State
Extracting State Logic into a Reducer
Passing Data Deeply with Context
Scaling Up with Reducer and Context -->
- [Managing State](concepts/managing_state.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)
- [Describing the UI](elements/index.md)
- [Intro to Elements](elements/vnodes.md)
- [Intro to Components](elements/components.md)
- [Reusing, Importing, and Exporting Components](elements/exporting_components.md)
- [Passing children and attributes](elements/component_children.md)
- [Conditional Rendering](elements/conditional_rendering.md)
- [Lists](elements/lists.md)
- [Adding Interactivity](interactivity/index.md)
- [Hooks and Internal State](interactivity/hooks.md)
- [Event handlers](interactivity/event_handlers.md)
- [User Input and Controlled Components](interactivity/user_input.md)
- [Lifecycle, updates, and effects](interactivity/lifecycles.md)
- [Managing State](state/index.md)
- [Local State](state/localstate.md)
- [Lifting State](state/liftingstate.md)
- [Global State](state/sharedstate.md)
- [Error handling](state/errorhandling.md)
- [Working with Async](async/index.md)
- [Tasks](async/asynctasks.md)
- [Putting it all together: Dog Search Engine](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]() -->
- [Bundling](tutorial/publishing.md)
- [Next Steps and Advanced Topics](final.md)
-----------
[Contributors](misc/contributors.md)
<!-- - [Suspense](concepts/suspense.md) -->
<!-- - [Async Callbacks](concepts/asynccallbacks.md) -->

View File

@ -20,7 +20,7 @@ fn runs_in_browser() {
Then, when you run
```
```console
$ dioxus test --chrome
```

View File

@ -0,0 +1,90 @@
# Tasks
All async code in Dioxus must be explicit and handled through Dioxus' task system.
In this chapter, we'll learn how to spawn new tasks through our `Scope`.
## Spawning a task
You can push any `'static` future into the Dioxus future queue by simply calling `cx.spawn` to spawn a task. Pushing a future returns a `TaskId` which can then be used to cancel the
```rust
fn App(cx: Scope) -> Element {
cx.spawn(async {
let mut count = 0;
loop {
tokio::time::delay(std::instant::Duration::from_millis(500)).await;
count += 1;
println!("Current count is {}", count);
}
});
None
}
```
The future must be `'static` - so any values captured by the task must not carry any references to `cx`. All the Dioxus hooks have a method called `for_async` which will create a slightly more limited handle to the hook for you to use in your async code.
```rust
fn App(cx: Scope) -> Element {
let mut count = use_state(&cx, || 0);
let taskid = cx.spawn({
let mut count = count.for_async();
async {
loop {
tokio::time::delay(std::instant::Duration::from_millis(500)).await;
count += 1;
println!("Current count is {}", count);
}
}
});
}
```
The task will run in the background until it is completed.
> Note: `spawn` will always spawn a *new* future. You probably want to call it from a hook initializer instead of the main body of your component.
When bringing lots of values into your task, we provide the `for_async!` macro which will can `for_async` on all values passed in. For types that implement `ToOwned`, `for_async!` will simply call `ToOwned` for that value.
```rust
fn App(cx: Scope) -> Element {
let mut age = use_state(&cx, || 0);
let mut name = use_state(&cx, || "Bob");
let mut description = use_state(&cx, || "asd".to_string());
let taskid = cx.spawn({
for_async![count, name, description]
async { /* code that uses count/name/description */ }
});
}
```
## Details of Tasks
Calling `spawn` is *not* a hook and will *always* generate a new task. Make sure to only spawn tasks when you want to. You should *probably* not call `spawn` in the main body of your component, since a new task will be spawned on every render.
## Spawning Tokio Tasks (for multithreaded use cases)
Sometimes, you might want to spawn a background task that needs multiple threads or talk to hardware that might block your app code. In these cases, we can can directly spawn a `tokio task` from our future. For Dioxus-Desktop, your task will be spawned onto Tokio's Multithreaded runtime:
```rust
cx.spawn({
tokio::spawn(async {
// some multithreaded work
}).await;
tokio::spawn_blocking(|| {
// some extremely blocking work
}).await;
tokio::spawn_local(|| {
// some !Send work
}).await;
})
```
> Note: Tokio tasks must be `Send`. Most hooks are `Send` compatible, but if they aren't, then you can use `spawn_local` to spawn onto Dioxus-Desktop's `localset`.

View File

@ -0,0 +1,19 @@
# Working with Async
Not all apps you'll build can be self-contained with synchronous code. You'll often need to interact with file systems, network interfaces, hardware, or timers.
So far, we've only talked about building apps with synchronous code, so this chapter will focus integrating asynchronous code into your app.
## The Runtime
By default, Dioxus-Desktop ships with the `Tokio` runtime and automatically sets everything up for you.
## Send/Sync
Writing apps that deal with Send/Sync can be frustrating at times. Under the hood, Dioxus is not currently thread-safe, so any async code you write does *not* need to be `Send/Sync`. That means Cell/Rc/RefCell are all fair game.
All async code in your app is polled on a `LocalSet`, so any async code we w

View File

@ -1 +0,0 @@
# Working with Async

View File

@ -1 +0,0 @@
# Async Callbacks

View File

@ -1 +0,0 @@
# Tasks

View File

@ -1 +0,0 @@
# Bundling and Distributing

View File

@ -1 +0,0 @@
# Custom Elements

View File

@ -1 +0,0 @@
# Custom Renderer

View File

@ -1 +0,0 @@
# Effects

View File

@ -1,11 +0,0 @@
# Handling Events
In the overview for this section, we mentioned how we can modify the state of our component by responding to user-generated events inside of event listeners.
In this section, we'll talk more about event listeners:
- What events are available
- Handling event data
- Mutability in event listeners
## Event Listeners

View File

@ -1 +0,0 @@
# Managing State

View File

@ -1 +0,0 @@
# Memoization

View File

@ -1 +0,0 @@
# Server-side components

View File

@ -1,54 +0,0 @@
# Suspense
Suspense in Dioxus is enabled through placeholder nodes.
just a component that renders nodes into the placeholder after the future is finished?
in react, suspense is just completely pausing diffing while a
```rust
let n = use_suspense(cx || {
cx.render(rsx!{
Suspense {
prom: fut,
callback: || {}
}
})
})
suspense () {
let value = use_state();
if first_render {
push_task({
value.set(fut.await);
});
} else {
callback(value)
}
}
let name = fetch_name().await;
function ProfileDetails() {
// Try to read user info, although it might not have loaded yet
const user = resource.read();
return <h1>{user.name}</h1>;
}
fn ProfileDteails() {
let user = resource.suspend(cx, |l| rsx!("{l}"));
// waits for the resource to be ready and updates the placeholder with the tree
let name = resource.suspend_with(cx, |val| rsx!( div { "hello" "{user}" } ));
cx.render(rsx!(
div {
{user}
{name}
}
))
}
```

View File

@ -1 +0,0 @@
# Fundamental Hooks and use_hook

View File

@ -30,7 +30,7 @@ struct ClickableProps<'a> {
title: &'a str
}
fn clickable(cx: Scope<ClickableProps>) -> Element {
fn Clickable(cx: Scope<ClickableProps>) -> Element {
cx.render(rsx!(
a {
href: "{cx.props.href}"
@ -44,10 +44,10 @@ And then use it in our code like so:
```rust
rsx!(
clickable(
Clickable {
href: "https://google.com"
title: "Link to Google"
)
}
)
```
@ -64,11 +64,11 @@ struct ClickableProps<'a> {
body: Element<'a>
}
fn clickable(cx: Scope<ClickableProps>) -> Element {
fn Clickable(cx: Scope<ClickableProps>) -> Element {
cx.render(rsx!(
a {
href: "{cx.props.href}"
{&cx.props.body}
href: "{cx.props.href}",
&cx.props.body
}
))
}
@ -78,15 +78,17 @@ Then, at the call site, we can render some nodes and pass them in:
```rust
rsx!(
clickable(
Clickable {
href: "https://google.com"
body: cx.render(rsx!(
img { src: "https://www.google.com/logos/doodles/2021/seasonal-holidays-2021-6753651837109324-6752733080595603-cst.gif" }
img { src: "https://www.google.com/logos/doodles/..." }
))
)
}
)
```
## Auto Conversion of the `Children` field
This pattern can become tedious in some instances, so Dioxus actually performs an implicit conversion of any `rsx` calls inside components into `Elements` at the `children` field. This means you must explicitly declare if a component can take children.
```rust
@ -99,26 +101,26 @@ struct ClickableProps<'a> {
fn clickable(cx: Scope<ClickableProps>) -> Element {
cx.render(rsx!(
a {
href: "{cx.props.href}"
{&cx.props.children}
href: "{cx.props.href}",
&cx.props.children
}
))
}
```
And to call `clickable`:
Now, whenever we use `Clickable` in another component, we don't need to call `render` on child nodes - it will happen automatically!
```rust
rsx!(
clickable(
Clickable {
href: "https://google.com"
img { src: "https://www.google.com/logos/doodles/2021/seasonal-holidays-2021-6753651837109324-6752733080595603-cst.gif" }
)
img { src: "https://www.google.com/logos/doodles/...." }
}
)
```
> Note: Passing children into components will break any memoization due to the associated lifetime.
While technically allowed, it's an antipattern to pass children more than once in a component and will probably break your app significantly.
While technically allowed, it's an antipattern to pass children more than once in a component and will probably cause your app to crash.
However, because the `Element` is transparently a `VNode`, we can actually match on it to extract the nodes themselves, in case we are expecting a specific format:
@ -142,10 +144,10 @@ In the cases where you need to pass arbitrary element properties into a componen
```rust
rsx!(
clickable(
Clickable {
"class": "blue-button",
"style": "background: red;"
)
}
)
```
@ -161,7 +163,7 @@ struct ClickableProps<'a> {
fn clickable(cx: Scope<ClickableProps>) -> Element {
cx.render(rsx!(
a {
..{cx.props.attributes},
..cx.props.attributes,
"Any link, anywhere"
}
))
@ -195,9 +197,9 @@ Then, we can attach a listener at the call site:
```rust
rsx!(
clickable(
Clickable {
onclick: move |_| log::info!("Clicked"),
)
}
)
```

View File

@ -110,7 +110,7 @@ When declaring a component in `rsx!`, we can pass in properties using the tradit
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 `Scope` and `&Props` and return an `Element`.
Most of your Components will look exactly like this: a Props struct and a render function. Every component must take a `Scope` generic over some `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`.
@ -145,7 +145,7 @@ struct TitleCardProps<'a> {
title: &'a str,
}
fn TitleCard(cx: Scope<TitleCardProps>) -> Element {
fn TitleCard<'a>(cx: Scope<'a, TitleCardProps<'a>>) -> Element {
cx.render(rsx!{
h1 { "{cx.props.title}" }
})
@ -156,6 +156,42 @@ For users of React: Dioxus knows *not* to memoize components that borrow propert
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 inline_props macro
Yes - *another* macro! However, this one is entirely optional.
For internal components, we provide the `inline_props` macro, which will let you embed your `Props` definition right into the function arguments of your component.
Our title card above would be transformed from:
```rust
#[derive(Props, PartialEq)]
struct TitleCardProps {
title: String,
}
fn TitleCard(cx: Scope<TitleCardProps>) -> Element {
cx.render(rsx!{
h1 { "{cx.props.title}" }
})
}
```
to:
```rust
#[inline_props]
fn TitleCard(cx: Scope, title: String) -> Element {
cx.render(rsx!{
h1 { "{title}" }
})
}
```
Again, this macro is optional and should not be used by library authors since you have less fine-grained control over documentation and optionality.
However, it's great for quickly throwing together an app without dealing with *any* extra boilerplate.
## The `Scope` object
Though very similar to React, Dioxus is different in a few ways. Most notably, React components will not have a `Scope` parameter in the component declaration.

View File

@ -101,14 +101,14 @@ fn App(cx: Scope)-> Element {
## 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:
By looking at other examples, you might have noticed that it's possible to include `rsx!` calls inside other `rsx!` calls. 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!(
rsx!(
"more rsx!"
)}
)
}
)
```
@ -120,7 +120,7 @@ let title = rsx!( "more rsx!" );
rsx!(
div {
{title}
title
}
)
```
@ -136,7 +136,7 @@ let screen = match logged_in {
cx.render(rsx!{
Navbar {}
{screen}
screen,
Footer {}
})
```
@ -152,9 +152,9 @@ By default, Rust lets you convert any `boolean` into any other type by calling `
let show_title = true;
rsx!(
div {
{show_title.and_then(|| rsx!{
show_title.and_then(|| rsx!{
"This is the title"
})}
})
}
)
```
@ -164,20 +164,23 @@ We can use this pattern for many things, including options:
let user_name = Some("bob");
rsx!(
div {
{user_name.map(|name| rsx!("Hello {name}"))}
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:
Sometimes, you don't want your component to return anything at all. Under the hood, the `Element` type is just an alias for `Option<VNode>`, so you can simply return `None`.
This can be helpful in certain patterns where you need to perform some logical side-effects but don't want to render anything.
```rust
rsx!(cx, {None as Element} )
fn demo(cx: Scope) -> Element {
None
}
```
## 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!

View File

@ -25,27 +25,27 @@ fn main() {
dioxus::desktop::launch(App);
}
fn App((cx, props): Component) -> Element {}
fn App(Scope) -> Element {}
#[derive(PartialEq, Props)]
struct PostProps{}
fn Post((cx, props): Component<PostProps>) -> Element {}
fn Post(Scope<PostProps>) -> Element {}
#[derive(PartialEq, Props)]
struct VoteButtonsProps {}
fn VoteButtons((cx, props): Component<VoteButtonsProps>) -> Element {}
fn VoteButtons(Scope<VoteButtonsProps>) -> Element {}
#[derive(PartialEq, Props)]
struct TitleCardProps {}
fn TitleCard((cx, props): Component<TitleCardProps>) -> Element {}
fn TitleCard(Scope<TitleCardProps>) -> Element {}
#[derive(PartialEq, Props)]
struct MetaCardProps {}
fn MetaCard((cx, props): Component<MetaCardProps>) -> Element {}
fn MetaCard(Scope<MetaCardProps>) -> Element {}
#[derive(PartialEq, Props)]
struct ActionCardProps {}
fn ActionCard((cx, props): Component<ActionCardProps>) -> Element {}
fn ActionCard(Scope<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.
@ -61,7 +61,7 @@ use dioxus::prelude::*;
#[derive(PartialEq, Props)]
struct ActionCardProps {}
fn ActionCard((cx, props): Component<ActionCardProps>) -> Element {}
fn ActionCard(Scope<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.
@ -92,7 +92,7 @@ fn main() {
mod post;
fn App((cx, props): Component) -> Element {
fn App(Scope) -> Element {
cx.render(rsx!{
post::Post {
id: Uuid::new_v4(),
@ -116,7 +116,7 @@ use dioxus::prelude::*;
#[derive(PartialEq, Props)]
pub struct PostProps {}
pub fn Post((cx, props): Component<PostProps>) -> Element {}
pub fn Post(Scope<PostProps>) -> Element {}
```
While we're here, we also need to make sure each of our subcomponents are included as modules and exported.
@ -142,7 +142,7 @@ pub struct PostProps {
original_poster: String
}
pub fn Post((cx, props): Component<PostProps>) -> Element {
pub fn Post(Scope<PostProps>) -> Element {
cx.render(rsx!{
div { class: "post-container"
vote::VoteButtons {
@ -191,7 +191,7 @@ fn main() {
mod post;
fn App((cx, props): Component) -> Element {
fn App(Scope) -> Element {
cx.render(rsx!{
post::Post {
id: Uuid::new_v4(),
@ -227,7 +227,7 @@ pub struct PostProps {
original_poster: String
}
pub fn Post((cx, props): Component<PostProps>) -> Element {
pub fn Post(Scope<PostProps>) -> Element {
cx.render(rsx!{
div { class: "post-container"
vote::VoteButtons {
@ -255,7 +255,7 @@ use dioxus::prelude::*;
#[derive(PartialEq, Props)]
pub struct VoteButtonsProps {}
pub fn VoteButtons((cx, props): Component<VoteButtonsProps>) -> Element {}
pub fn VoteButtons(Scope<VoteButtonsProps>) -> Element {}
```
```rust
@ -264,7 +264,7 @@ use dioxus::prelude::*;
#[derive(PartialEq, Props)]
pub struct TitleCardProps {}
pub fn TitleCard((cx, props): Component<TitleCardProps>) -> Element {}
pub fn TitleCard(Scope<TitleCardProps>) -> Element {}
```
```rust
@ -273,7 +273,7 @@ use dioxus::prelude::*;
#[derive(PartialEq, Props)]
pub struct MetaCardProps {}
pub fn MetaCard((cx, props): Component<MetaCardProps>) -> Element {}
pub fn MetaCard(Scope<MetaCardProps>) -> Element {}
```
```rust
@ -282,7 +282,7 @@ use dioxus::prelude::*;
#[derive(PartialEq, Props)]
pub struct ActionCardProps {}
pub fn ActionCard((cx, props): Component<ActionCardProps>) -> Element {}
pub fn ActionCard(Scope<ActionCardProps>) -> Element {}
```
## Moving forward

View File

@ -0,0 +1,73 @@
# 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.set_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 mut state = use_state(&cx, || "red");
cx.render(rsx!(
Container {
Light { color: "red", enabled: state == "red", }
Light { color: "yellow", enabled: state == "yellow", }
Light { color: "green", enabled: state == "green", }
onclick: move |_| {
state.set(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)

View File

@ -87,9 +87,15 @@ Unfortunately, you cannot drop in arbitrary expressions directly into the string
rsx!( {[format_args!("Hello {}", if enabled { "Jack" } else { "Bob" } )]} )
```
Alternatively, `&str` can be included directly, though it must be inside of an array:
```rust
rsx!( "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:
Typically, with Dioxus, you'll just want to compute your substrings outside of the `rsx!` call and leverage the f-string formatting:
```rust
let name = if enabled { "Jack" } else { "Bob" };
@ -100,7 +106,7 @@ rsx! ( "hello {name}" )
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:
To do this, we use the familiar struct-style syntax that Rust provides:
```rust
rsx!(
@ -127,19 +133,29 @@ rsx!(
)
```
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. When using custom attributes, make sure the name of the attribute exactly matches what the renderer is expecting.
> 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. When using custom attributes, make sure the name of the attribute **exactly** matches what the renderer is expecting.
All element attributes must occur *before* child elements. The `rsx!` macro will throw an error if your child elements come before any of your attributes. If you don't see the error, try editing your Rust-Analyzer IDE setting to ignore macro-errors. This is a temporary workaround because Rust-Analyzer currently throws *two* errors instead of just the one we care about.
```rust
// settings.json
{
"rust-analyzer.diagnostics.disabled": [
"macro-error"
],
}
```
## 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.
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 accepts closures.
```rust
rsx!(
div {
onclick: move |_| {}
onmouseover: {handler},
onclick: move |_| log::debug!("div clicked!"),
}
)
```

49
docs/guide/src/final.md Normal file
View File

@ -0,0 +1,49 @@
# Congrats!
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 local and global state
- How to work with async using tasks, coroutines, and suspense
- How to build custom hooks and handlers
With any luck, you followed through the "Putting it All Together" mini guide and have your very own dog search engine app!
# Next Steps and Advanced Topics
Continuing on your journey with Dioxus, you can try a number of things:
- Build a simple TUI app
- Publish your search engine app
- Deploy a WASM app to GitHub
- Design a custom hook
- Contribute to the ecosystem!
There are a number of advanced topics we glossed over:
- The underlying NodeFactory API
- Static elements and templates
- Anti-patterns
- Bundling/distribution
- Working with wasm apps
# Contributing to the ecosystem
Dioxus is still quite young and could use your help!
The core team is actively working on:
- Declarative window management (via Tauri) for Desktop apps
- Portals for Dioxus Core
- Mobile support
- Integration with 3D renderers
- Better async story (suspense, error handling)
- Global state management
- Web development server
- LiveView
- Broader platform support (iOS/Android/TV/embedded)
If there's something specifically interesting to you, don't be afraid to jump in!

View File

@ -1,10 +0,0 @@
# 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

View File

@ -1,4 +1,4 @@
# Hello, World desktop app
# "Hello, World" desktop app
Let's put together a simple "hello world" desktop application to get acquainted with Dioxus.
@ -138,11 +138,13 @@ Writing `fn App(cx: Scope) -> Element {` might become tedious. Rust will also le
static App: Component = |cx| cx.render(rsx!(div { "Hello, world!" }));
```
### What is this `Context` object?
### What is this `Scope` object?
Coming from React, the `Context` object might be confusing. 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.
Coming from React, the `Scope` object might be confusing. 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 `Context` object provides a handful of useful APIs for features like suspense, rendering, and more.
In Dioxus, you are given an explicit `Scope` object to control how the component renders and stores data. The `Scope` object provides a handful of useful APIs for features like suspense, rendering, and more.
For now, just know that `Scope` lets you store state with hooks and render elements with `cx.render`.
## Moving on

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

View File

@ -0,0 +1 @@
# Event handlers

View File

@ -19,16 +19,16 @@ Broadly, there are two types of GUI structures:
Typically, immediate-mode GUIs are simpler to write but can slow down as more features, like styling, are added.
Many GUIs today - including Dioxus - are written in *Retained mode* - your code changes the data of the user interface but the renderer is responsible for actually drawing to the screen. In these cases, our GUI's state sticks around as the UI is rendered.
Many GUIs today are written in *Retained mode* - your code changes the data of the user interface but the renderer is responsible for actually drawing to the screen. In these cases, our GUI's state sticks around as the UI is rendered. To help accommodate retained mode GUIs, like the web browser, Dioxus provides a mechanism to keep state around.
Dioxus, following in the footsteps of React, provides a "Reactive" model for you to design your UI. This model emphasizes one-way data flow and encapsulation. Essentially, your UI code should be as predictable as possible.
> Note: Even though hooks are accessible, you should still prefer to one-way data flow and encapsulation. Your UI code should be as predictable as possible. Dioxus is plenty fast, even for the largest apps.
## Mechanics of Hooks
In order to have state stick around between renders, Dioxus provides the `hook` through the `use_hook` API. This gives us a mutable reference to data returned from the initialization function.
```rust
fn example(cx: Scope) -> Element {
let name: &mut String = cx.use_hook(|| "John Doe".to_string(), |hook| hook);
let name: &mut String = cx.use_hook(|| "John Doe".to_string());
//
}
@ -38,7 +38,7 @@ We can even modify this value directly from an event handler:
```rust
fn example(cx: Scope) -> Element {
let name: &mut String = cx.use_hook(|| "John Doe".to_string(), |hook| hook);
let name: &mut String = cx.use_hook(|| "John Doe".to_string());
cx.render(rsx!(
button {
@ -52,9 +52,9 @@ Mechanically, each call to `use_hook` provides us with `&mut T` for a new value.
```rust
fn example(cx: Scope) -> Element {
let name: &mut String = cx.use_hook(|| "John Doe".to_string(), |hook| hook);
let age: &mut u32 = cx.use_hook(|| 10, |hook| hook);
let friends: &mut Vec<String> = cx.use_hook(|| vec!["Jane Doe".to_string()], |hook| hook);
let name: &mut String = cx.use_hook(|| "John Doe".to_string());
let age: &mut u32 = cx.use_hook(|| 10);
let friends: &mut Vec<String> = cx.use_hook(|| vec!["Jane Doe".to_string()]);
//
}
@ -84,7 +84,7 @@ Consider when we try to pass our `&mut String` into two different handlers:
```rust
fn example(cx: Scope) -> Element {
let name: &mut String = cx.use_hook(|| "John Doe".to_string(), |hook| hook);
let name: &mut String = cx.use_hook(|| "John Doe".to_string());
cx.render(rsx!(
button { onclick: move |_| name.push_str("yes"), }
@ -112,7 +112,7 @@ This example uses the `Cell` type to let us replace the value through interior m
```rust
fn example(cx: Scope) -> Element {
let name: &Cell<&'static str> = cx.use_hook(|| "John Doe", |hook| hook);
let name: &Cell<&'static str> = cx.use_hook(|| Cell::new("John Doe"));
cx.render(rsx!(
button { onclick: move |_| name.set("John"), }
@ -180,6 +180,8 @@ By default, we bundle a handful of hooks in the Dioxus-Hooks package. Feel free
- [use_callback](https://docs.rs/dioxus_hooks/use_callback) - store a callback that implements PartialEq for memoization
- [use_provide_context](https://docs.rs/dioxus_hooks/use_provide_context) - expose state to descendent components
- [use_context](https://docs.rs/dioxus_hooks/use_context) - consume state provided by `use_provide_context`
For a more in-depth guide to building new hooks, checkout out the advanced hook building guide in the reference.
## Wrapping up

View File

@ -139,7 +139,7 @@ fn App(cx: Scope)-> Element {
let mut sec_elapsed = use_state(&cx, || 0);
use_future(&cx, || {
let mut sec_elapsed = sec_elapsed.to_owned();
let mut sec_elapsed = sec_elapsed.for_async();
async move {
loop {
TimeoutFuture::from_ms(1000).await;

View File

@ -58,7 +58,7 @@ That's it! We won't need to touch NPM/WebPack/Babel/Parcel, etc. However, you _c
## 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:
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 Rust project. With Dioxus you'll learn about:
- Error handling
- Structs, Functions, Enums

View File

@ -0,0 +1,8 @@
# Managing State
Every app you'll build with Dioxus will have some sort of state that needs to be maintained and updated as your users interact with it. However, managing state can be particular challenging at times, and is frequently the source of bugs in many GUI frameworks.
In this chapter, we'll cover the various ways to manage state, the appropriate terminology, various patterns, and then take an overview
## Terminology

View File

@ -0,0 +1 @@
# Lifting State

View File

@ -0,0 +1 @@
# Local State

View File

@ -1 +1,65 @@
# Publishing
Congrats! You've made your first Dioxus app that actually does some pretty cool stuff. This app uses your operating system's WebView library, so it's portable to be distributed for other platforms.
In this section, we'll cover how to bundle your app for macOS, Windows, and Linux.
## Install `cargo-bundle`
The first thing we'll do is install [`cargo-bundle`](https://github.com/burtonageo/cargo-bundle). This extension to cargo will make it very easy to package our app for the various platforms.
According to the `cargo-bundle` github page,
*"cargo-bundle is a tool used to generate installers or app bundles for GUI executables built with cargo. It can create .app bundles for Mac OS X and iOS, .deb packages for Linux, and .msi installers for Windows (note however that iOS and Windows support is still experimental). Support for creating .rpm packages (for Linux) and .apk packages (for Android) is still pending."*
To install, simply run
`cargo install cargo-bundle`
## Setting up your project
To get a project setup for bundling, we need to add some flags to our `Cargo.toml` file.
```toml
[package]
name = "example"
# ...other fields...
[package.metadata.bundle]
name = "DogSearch"
identifier = "com.dogs.dogsearch"
version = "1.0.0"
copyright = "Copyright (c) Jane Doe 2016. All rights reserved."
category = "Developer Tool"
short_description = "Easily search for Dog photos"
long_description = """
This app makes it quick and easy to browse photos of dogs from over 200 bree
"""
```
## Building
Following cargo-bundle's instructions, we simply `cargo-bundle --release` to produce a final app with all the optimizations and assets builtin.
Once you've ran `cargo-bundle --release`, your app should be accessible in
`target/release/bundle/<platform>/`.
For example, a macOS app would look like this:
![Published App](../images/publish.png)
Nice! And it's only 4.8 Mb - extremely lean!! Because Dioxus leverages your platform's native WebView, Dioxus apps are extremely memory efficient and won't waste your battery.
> Note: not all CSS works the same on all platforms. Make sure to view your app's CSS on each platform - or web browser (Firefox, Chrome, Safari) before publishing.

View File

@ -0,0 +1,61 @@
# Summary
- [Introduction](README.md)
- [Web](web/intro.md)
- [Getting Started](web/setup.md)
- [Events](concepts/errorhandling.md)
- [Desktop](desktop/intro.md)
- [Getting Started](desktop/setup.md)
- [events](desktop/.md)
- [Mobile](concepts/managing_state.md)
- [Getting Started](concepts/sharedstate.md)
- [Specifics](concepts/errorhandling.md)
- [Working with Async](concepts/async.md)
- [Code Reference]()
- [Code Reference]()
- [Code Reference]()
- [Code Reference]()
- [Code Reference]()
- [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)
- [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]()

View File

@ -21,7 +21,7 @@ fn main() {
}
fn App(cx: Scope) -> Element {
let text = cx.use_hook(|_| vec![String::from("abc=def")], |f| f);
let text = cx.use_hook(|_| vec![String::from("abc=def")]);
let first = text.get_mut(0).unwrap();

View File

@ -12,7 +12,7 @@ use dioxus::prelude::*;
use separator::Separatable;
fn main() {
dioxus::desktop::launch(app);
// dioxus::desktop::launch(app);
}
fn app(cx: Scope) -> Element {

View File

@ -28,7 +28,8 @@ fn app(cx: Scope) -> Element {
let description = use_state(&cx, String::new);
cx.render(rsx!(
body { margin_left: "35%",
body {
margin_left: "35%",
link {
rel: "stylesheet",
href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css",

View File

@ -109,7 +109,7 @@ pub static App: Component = |cx| {
li { class: "Active", a { onclick: move |_| filter.set(FilterState::Active), "Active" }}
li { class: "Completed", a { onclick: move |_| filter.set(FilterState::Completed), "Completed" }}
}
(show_clear_completed).then(|| rsx!(
show_clear_completed.then(|| rsx!(
button { class: "clear-completed", onclick: move |_| clear_completed(),
"Clear completed"
}

View File

@ -2,7 +2,6 @@ use dioxus::prelude::*;
use rand::prelude::*;
fn main() {
// dioxus::web::launch(App);
dioxus::desktop::launch(App);
}

View File

@ -15,8 +15,7 @@ use dioxus::ssr;
fn main() {
let vdom = VirtualDom::new(App);
let content = ssr::render_vdom_cfg(&vdom, |f| f.pre_render(true));
// dioxus::desktop::launch_cfg(App, |c| c.with_prerendered(content));
dioxus::desktop::launch_cfg(App, |c| c.with_prerendered(content));
}
static App: Component = |cx| {

150
examples/inputs.rs Normal file
View File

@ -0,0 +1,150 @@
//! This example roughly shows how events are serialized into Rust from JavaScript.
//!
//! There is some conversion happening when input types are checkbox/radio/select/textarea etc.
use std::sync::Arc;
use dioxus::{events::FormEvent, prelude::*};
fn main() {
dioxus::desktop::launch(App);
}
const FIELDS: &[(&str, &str)] = &[
("button", "Click me!"),
("checkbox", "CHECKBOX"),
("color", ""),
("date", ""),
("datetime-local", ""),
("email", ""),
("file", ""),
("image", ""),
("number", ""),
("password", ""),
("radio", ""),
("range", ""),
("reset", ""),
("search", ""),
("submit", ""),
("tel", ""),
("text", ""),
("time", ""),
("url", ""),
//
// less supported things
("hidden", ""),
("month", ""), // degrades to text most of the time, but works properly as "value'"
("week", ""), // degrades to text most of the time
];
static App: Component = |cx| {
cx.render(rsx! {
div { margin_left: "30px",
// radio group
div {
// handling inputs on divs will catch all input events below
// so the value of our input event will be either huey, dewey, louie, or true/false (because of the checkboxe)
// be mindful in grouping inputs together, as they will all be handled by the same event handler
oninput: move |evt| {
println!("{:?}", evt);
},
div {
input {
id: "huey",
r#type: "radio",
value: "huey",
checked: "",
name: "drone",
}
label {
r#for: "huey",
"Huey"
}
}
div {
input {
id: "dewey",
r#type: "radio",
value: "dewey",
name: "drone",
}
label {
r#for: "dewey",
"Dewey"
}
}
div {
input {
id: "louie",
value: "louie",
r#type: "radio",
name: "drone",
}
label {
r#for: "louie",
"Louie"
}
}
div {
input {
id: "groovy",
value: "groovy",
r#type: "checkbox",
name: "drone",
}
label {
r#for: "groovy",
"groovy"
}
}
}
// elements with driven values will preventdefault automatically.
// you can disable this override with preventdefault: false
div {
input {
id: "pdf",
value: "pdf",
name: "pdf",
r#type: "checkbox",
oninput: move |evt| {
println!("{:?}", evt);
},
}
label {
r#for: "pdf",
"pdf"
}
}
FIELDS.iter().map(|(field, value)| rsx!(
div {
input {
id: "{field}",
name: "{field}",
r#type: "{field}",
value: "{value}",
// checked: "false",
oninput: move |evt: Arc<FormEvent>| {
println!("{:?}", evt);
},
}
label {
r#for: "{field}",
"{field} element"
}
br {}
}
))
}
})
};

View File

@ -24,13 +24,13 @@ use dioxus::prelude::*;
const STYLE: &str = include_str!("./assets/calculator.css");
fn main() {
env_logger::init();
// dioxus::desktop::launch_cfg(App, |cfg| {
// cfg.with_window(|w| {
// w.with_title("Calculator Demo")
// .with_resizable(false)
// .with_inner_size(LogicalSize::new(320.0, 530.0))
// })
// });
dioxus::desktop::launch_cfg(app, |cfg| {
cfg.with_window(|w| {
w.with_title("Calculator Demo")
.with_resizable(false)
.with_inner_size(LogicalSize::new(320.0, 530.0))
})
});
}
fn app(cx: Scope) -> Element {

View File

@ -32,14 +32,14 @@ static App: Component = |cx| {
Link { to: Route::AllUsers { page: 0 }, "List all users" }
Link { to: Route::BlogList { page: 0 }, "Blog posts" }
}
{match route {
match route {
Route::Home => rsx!("Home"),
Route::AllUsers { page } => rsx!("All users - page {page}"),
Route::User { id } => rsx!("User - id: {id}"),
Route::BlogList { page } => rsx!("Blog posts - page {page}"),
Route::BlogPost { post_id } => rsx!("Blog post - post {post_id}"),
Route::NotFound => rsx!("Not found"),
}}
}
footer {}
})
};

View File

@ -42,6 +42,7 @@ fn example(cx: Scope) -> Element {
things_list.iter().map(|f| rsx!{
div {
"{f.a}"
"{f.b}"
}
})

View File

@ -43,21 +43,21 @@ struct WeatherProps {}
static WeatherDisplay: Component<WeatherProps> = |cx| {
//
cx.render(rsx!(
div { class: "flex items-center justify-center flex-col"
div { class: "flex items-center justify-center"
div { class: "flex flex-col bg-white rounded p-4 w-full max-w-xs"
div{ class: "font-bold text-xl"
div { class: "flex items-center justify-center flex-col",
div { class: "flex items-center justify-center",
div { class: "flex flex-col bg-white rounded p-4 w-full max-w-xs",
div{ class: "font-bold text-xl",
"Jon's awesome site!!"
}
div{ class: "text-sm text-gray-500"
div{ class: "text-sm text-gray-500",
"He worked so hard on it :)"
}
div { class: "flex flex-row items-center justify-center mt-6"
div { class: "font-medium text-6xl"
div { class: "flex flex-row items-center justify-center mt-6",
div { class: "font-medium text-6xl",
"1337"
}
}
div { class: "flex flex-row justify-between mt-6"
div { class: "flex flex-row justify-between mt-6",
"Legit made my own React"
}
}

View File

@ -11,12 +11,9 @@ fn app(cx: Scope) -> Element {
div {
"hello world!"
h1 {
"{contents}"
}
h3 {
[contents.as_str()]
}
h1 { "{contents}" }
h3 { [contents.as_str()] }
input {
value: "{contents}",

View File

@ -17,11 +17,7 @@ for more general tasks, we need some way of submitting a future or task into som
```rust
let task = use_hook(
|| { /* */ },
|| { /* update the future if it needs to be updated */ },
|| {}
);
let task = use_hook(|| { /* */ });
cx.poll_future()
// let recoil_event_loop = cx.use_task(move |_| async move {
// loop {

View File

@ -19,7 +19,7 @@ use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
ext::IdentExt,
parse::{Parse, ParseBuffer, ParseStream},
token, Expr, ExprClosure, Ident, Result, Token,
token, Expr, Ident, Result, Token,
};
pub struct Component {

View File

@ -25,16 +25,16 @@ criterion_main!(mbenches);
fn create_rows(c: &mut Criterion) {
static App: Component = |cx| {
let mut rng = SmallRng::from_entropy();
let rows = (0..10_000_usize).map(|f| {
let label = Label::new(&mut rng);
rsx!(Row {
row_id: f,
label: label
})
});
rsx!(cx, table {
tbody {
{rows}
(0..10_000_usize).map(|f| {
let label = Label::new(&mut rng);
rsx!(Row {
row_id: f,
label: label
})
})
}
})
};
@ -58,12 +58,12 @@ fn Row(cx: Scope<RowProps>) -> Element {
cx.render(rsx! {
tr {
td { class:"col-md-1", "{cx.props.row_id}" }
td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
td { class:"col-md-1", onclick: move |_| { /* run onselect */ },
a { class: "lbl", "{adj}" "{col}" "{noun}" }
}
td { class: "col-md-1"
a { class: "remove", onclick: move |_| {/* remove */}
span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
td { class: "col-md-1",
a { class: "remove", onclick: move |_| {/* remove */},
span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
}
}
td { class: "col-md-6" }

View File

@ -12,7 +12,7 @@ fn main() {
}
fn parent(cx: Scope) -> Element {
let value = cx.use_hook(|_| String::new(), |f| f);
let value = cx.use_hook(|_| String::new());
cx.render(rsx! {
div {
@ -33,8 +33,8 @@ struct ChildProps<'a> {
fn child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
cx.render(rsx! {
div {
"it's nested {cx.props.name}"
{&cx.props.children}
"it's nested {cx.props.name}",
&cx.props.children
}
})
}

View File

@ -10,7 +10,7 @@ fn main() {
}
fn parent(cx: Scope) -> Element {
let value = cx.use_hook(|_| String::new(), |f| f);
let value = cx.use_hook(|_| String::new());
cx.render(rsx! {
div {

View File

@ -473,9 +473,16 @@ impl<'bump> DiffState<'bump> {
// Check the most common cases first
// these are *actual* elements, not wrappers around lists
(Text(old), Text(new)) => {
self.diff_text_nodes(old, new, old_node, new_node);
if let Some(root) = old.id.get() {
if old.text != new.text {
self.mutations.set_text(new.text, root.as_u64());
}
self.scopes.update_node(new_node, root);
new.id.set(Some(root));
}
}
(Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
(Placeholder(old), Placeholder(new)) => {
if let Some(root) = old.id.get() {
self.scopes.update_node(new_node, root);
@ -483,10 +490,13 @@ impl<'bump> DiffState<'bump> {
}
}
(Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
// These two sets are pointers to nodes but are not actually nodes themselves
(Component(old), Component(new)) => {
self.diff_component_nodes(old_node, new_node, *old, *new)
}
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
// The normal pathway still works, but generates slightly weird instructions
@ -506,23 +516,6 @@ impl<'bump> DiffState<'bump> {
}
}
fn diff_text_nodes(
&mut self,
old: &'bump VText<'bump>,
new: &'bump VText<'bump>,
_old_node: &'bump VNode<'bump>,
new_node: &'bump VNode<'bump>,
) {
if let Some(root) = old.id.get() {
if old.text != new.text {
self.mutations.set_text(new.text, root.as_u64());
}
self.scopes.update_node(new_node, root);
new.id.set(Some(root));
}
}
fn diff_element_nodes(
&mut self,
old: &'bump VElement<'bump>,

View File

@ -65,10 +65,10 @@ pub(crate) mod innerlude {
}
pub use crate::innerlude::{
Attribute, Component, DioxusElement, DomEdit, Element, ElementId, EventHandler, EventPriority,
IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties, SchedulerMsg, Scope,
ScopeId, ScopeState, TaskId, UserEvent, VComponent, VElement, VFragment, VNode, VPlaceholder,
VText, VirtualDom,
Attribute, Component, DioxusElement, DomEdit, Element, ElementId, ElementIdIterator, Event,
EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory,
Properties, SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, UserEvent, VComponent, VElement,
VFragment, VNode, VPlaceholder, VText, VirtualDom,
};
pub mod prelude {

View File

@ -320,6 +320,18 @@ impl ScopeArena {
pub fn root_node(&self, id: ScopeId) -> &VNode {
self.fin_head(id)
}
// this is totally okay since all our nodes are always in a valid state
pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
let ptr = self.nodes.borrow().get(id.0).cloned();
match ptr {
Some(ptr) => {
let node = unsafe { &*ptr };
Some(unsafe { extend_vnode(node) })
}
None => None,
}
}
}
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
@ -620,7 +632,7 @@ impl ScopeState {
/// struct SharedState(&'static str);
///
/// static App: Component = |cx| {
/// cx.use_hook(|_| cx.provide_context(SharedState("world")), |_| {}, |_| {});
/// cx.use_hook(|_| cx.provide_context(SharedState("world")));
/// rsx!(cx, Child {})
/// }
///
@ -711,17 +723,13 @@ impl ScopeState {
/// ```ignore
/// // use_ref is the simplest way of storing a value between renders
/// fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T) -> &RefCell<T> {
/// use_hook(
/// || Rc::new(RefCell::new(initial_value())),
/// |state| state,
/// )
/// use_hook(|| Rc::new(RefCell::new(initial_value())))
/// }
/// ```
pub fn use_hook<'src, State: 'static, Output: 'src>(
pub fn use_hook<'src, State: 'static>(
&'src self,
initializer: impl FnOnce(usize) -> State,
runner: impl FnOnce(&'src mut State) -> Output,
) -> Output {
) -> &'src mut State {
let mut vals = self.hook_vals.borrow_mut();
let hook_len = vals.len();
@ -731,7 +739,7 @@ impl ScopeState {
vals.push(self.hook_arena.alloc(initializer(hook_len)));
}
let state = vals
vals
.get(cur_idx)
.and_then(|inn| {
self.hook_idx.set(cur_idx + 1);
@ -746,9 +754,7 @@ impl ScopeState {
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
Functions prefixed with "use" should never be called conditionally.
"###,
);
runner(state)
)
}
/// The "work in progress frame" represents the frame that is currently being worked on.

View File

@ -260,6 +260,10 @@ impl VirtualDom {
self.channel.0.clone()
}
pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
self.scopes.get_element(id)
}
/// Add a new message to the scheduler queue directly.
///
///
@ -1003,3 +1007,103 @@ impl EmptyBuilder {
pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
T::builder()
}
pub struct Event<T> {
data: Arc<T>,
_cancel: std::rc::Rc<std::cell::Cell<bool>>,
}
impl<T> std::ops::Deref for Event<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.data.as_ref()
}
}
impl<T> Event<T> {
pub fn cancel(&self) {}
pub fn timestamp(&self) {}
pub fn triggered_element(&self) -> Option<Element> {
None
}
}
pub struct ElementIdIterator<'a> {
vdom: &'a VirtualDom,
// Heuristcally we should never bleed into 5 completely nested fragments/components
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
}
impl<'a> ElementIdIterator<'a> {
pub fn new(vdom: &'a VirtualDom, node: &'a VNode<'a>) -> Self {
Self {
vdom,
stack: smallvec::smallvec![(0, node)],
}
}
}
impl<'a> Iterator for ElementIdIterator<'a> {
type Item = &'a VNode<'a>;
fn next(&mut self) -> Option<&'a VNode<'a>> {
let mut should_pop = false;
let mut returned_node = None;
let mut should_push = None;
while returned_node.is_none() {
if let Some((count, node)) = self.stack.last_mut() {
match node {
// We can only exit our looping when we get "real" nodes
VNode::Element(_) | VNode::Text(_) | VNode::Placeholder(_) => {
// We've recursed INTO an element/text
// We need to recurse *out* of it and move forward to the next
// println!("Found element! Returning it!");
should_pop = true;
returned_node = Some(&**node);
}
// If we get a fragment we push the next child
VNode::Fragment(frag) => {
let _count = *count as usize;
if _count >= frag.children.len() {
should_pop = true;
} else {
should_push = Some(&frag.children[_count]);
}
}
// For components, we load their root and push them onto the stack
VNode::Component(sc) => {
// todo!();
let scope = self.vdom.get_scope(sc.scope.get().unwrap()).unwrap();
// Simply swap the current node on the stack with the root of the component
*node = scope.root_node();
}
}
} else {
// If there's no more items on the stack, we're done!
return None;
}
if should_pop {
self.stack.pop();
if let Some((id, _)) = self.stack.last_mut() {
*id += 1;
}
should_pop = false;
}
if let Some(push) = should_push {
self.stack.push((0, push));
should_push = None;
}
}
returned_node
}
}

View File

@ -11,7 +11,7 @@ fn test_borrowed_state() {
}
fn Parent(cx: Scope) -> Element {
let value = cx.use_hook(|_| String::new(), |f| &*f);
let value = cx.use_hook(|_| String::new());
cx.render(rsx! {
div {

View File

@ -25,7 +25,7 @@ fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
#[test]
fn test_early_abort() {
const app: Component = |cx| {
let val = cx.use_hook(|_| 0, |f| f);
let val = cx.use_hook(|_| 0);
*val += 1;

View File

@ -46,7 +46,7 @@ fn manual_diffing() {
#[test]
fn events_generate() {
static App: Component = |cx| {
let count = cx.use_hook(|_| 0, |f| f);
let count = cx.use_hook(|_| 0);
let inner = match *count {
0 => {
@ -105,7 +105,7 @@ fn events_generate() {
#[test]
fn components_generate() {
static App: Component = |cx| {
let render_phase = cx.use_hook(|_| 0, |f| f);
let render_phase = cx.use_hook(|_| 0);
*render_phase += 1;
cx.render(match *render_phase {
@ -221,7 +221,7 @@ fn components_generate() {
#[test]
fn component_swap() {
static App: Component = |cx| {
let render_phase = cx.use_hook(|_| 0, |f| f);
let render_phase = cx.use_hook(|_| 0);
*render_phase += 1;
cx.render(match *render_phase {

View File

@ -29,7 +29,7 @@ fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
#[test]
fn test_memory_leak() {
fn app(cx: Scope) -> Element {
let val = cx.use_hook(|_| 0, |f| f);
let val = cx.use_hook(|_| 0);
*val += 1;
@ -37,7 +37,7 @@ fn test_memory_leak() {
return None;
}
let name = cx.use_hook(|_| String::from("asd"), |f| f);
let name = cx.use_hook(|_| String::from("asd"));
cx.render(rsx!(
div { "Hello, world!" }
@ -86,7 +86,7 @@ fn test_memory_leak() {
#[test]
fn memo_works_properly() {
fn app(cx: Scope) -> Element {
let val = cx.use_hook(|_| 0, |f| f);
let val = cx.use_hook(|_| 0);
*val += 1;
@ -94,7 +94,7 @@ fn memo_works_properly() {
return None;
}
let name = cx.use_hook(|_| String::from("asd"), |f| f);
let name = cx.use_hook(|_| String::from("asd"));
cx.render(rsx!(
div { "Hello, world!" }
@ -204,7 +204,7 @@ fn free_works_on_root_hooks() {
}
fn app(cx: Scope) -> Element {
let name = cx.use_hook(|_| Droppable(String::from("asd")), |f| f);
let name = cx.use_hook(|_| Droppable(String::from("asd")));
rsx!(cx, div { "{name.0}" })
}
@ -216,7 +216,7 @@ fn free_works_on_root_hooks() {
fn old_props_arent_stale() {
fn app(cx: Scope) -> Element {
dbg!("rendering parent");
let cnt = cx.use_hook(|_| 0, |f| f);
let cnt = cx.use_hook(|_| 0);
*cnt += 1;
if *cnt == 1 {

View File

@ -59,14 +59,7 @@ pub struct WebviewWindowProps<'a> {
pub fn WebviewWindow(cx: Scope<WebviewWindowProps>) -> Element {
let dtcx = cx.consume_state::<RefCell<DesktopContext>>()?;
cx.use_hook(
|_| {
//
},
|state| {
//
},
);
cx.use_hook(|_| {});
// render the children directly
todo!()

View File

@ -4,17 +4,8 @@
<html>
<head>
<!-- enable for mobile support -->
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0 user-scalable=no"
charset="utf-8" />
<!-- <style>
body,
html {
position: fixed;
}
</style> -->
<link rel="stylesheet" href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css">
<script src="https://cdn.plot.ly/plotly-1.52.3.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0 user-scalable=no" charset="utf-8" /> -->
</head>

View File

@ -54,6 +54,11 @@ function serialize_event(event) {
case "submit": {
let target = event.target;
let value = target.value ?? target.textContent;
if (target.type == "checkbox") {
value = target.checked ? "true" : "false";
}
return {
value: value
};
@ -310,32 +315,24 @@ class Interpreter {
element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`);
if (this.listeners[event_name] === undefined) {
this.listeners[event_name] = "bla";
this.listeners[event_name] = true;
this.root.addEventListener(event_name, (event) => {
// console.log("CLICKED");
console.log("handling event", event);
const target = event.target;
const real_id = target.getAttribute(`dioxus-id`);
if (real_id == null) {
alert("no id");
return;
}
// const fields = val.split(".");
// const scope_id = parseInt(fields[0]);
// const real_id = parseInt(fields[1]);
// // console.log(`parsed event with scope_id ${scope_id} and real_id ${real_id}`);
// console.log("message fired");
let contents = serialize_event(event);
let evt = {
rpc.call('user_event', {
event: event_name,
// scope: scope_id,
mounted_dom_id: parseInt(real_id),
contents: contents,
};
contents: serialize_event(event),
});
rpc.call('user_event', evt);
});
}
}

View File

@ -60,37 +60,33 @@ impl<T> ProvidedStateInner<T> {
///
///
pub fn use_context<'a, T: 'static>(cx: &'a ScopeState) -> Option<UseSharedState<'a, T>> {
cx.use_hook(
|_| {
let scope_id = cx.scope_id();
let root = cx.consume_context::<ProvidedState<T>>();
let state = cx.use_hook(|_| {
let scope_id = cx.scope_id();
let root = cx.consume_context::<ProvidedState<T>>();
if let Some(root) = root.as_ref() {
root.borrow_mut().consumers.insert(scope_id);
}
if let Some(root) = root.as_ref() {
root.borrow_mut().consumers.insert(scope_id);
}
let value = root.as_ref().map(|f| f.borrow().value.clone());
SharedStateInner {
root,
value,
scope_id,
needs_notification: Cell::new(false),
}
},
|f| {
//
f.needs_notification.set(false);
match (&f.value, &f.root) {
(Some(value), Some(root)) => Some(UseSharedState {
cx,
value,
root,
needs_notification: &f.needs_notification,
}),
_ => None,
}
},
)
let value = root.as_ref().map(|f| f.borrow().value.clone());
SharedStateInner {
root,
value,
scope_id,
needs_notification: Cell::new(false),
}
});
state.needs_notification.set(false);
match (&state.value, &state.root) {
(Some(value), Some(root)) => Some(UseSharedState {
cx,
value,
root,
needs_notification: &state.needs_notification,
}),
_ => None,
}
}
struct SharedStateInner<T: 'static> {
@ -176,15 +172,12 @@ where
///
///
pub fn use_context_provider<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> T) {
cx.use_hook(
|_| {
let state: ProvidedState<T> = RefCell::new(ProvidedStateInner {
value: Rc::new(RefCell::new(f())),
notify_any: cx.schedule_update_any(),
consumers: HashSet::new(),
});
cx.provide_context(state)
},
|_inner| {},
)
cx.use_hook(|_| {
let state: ProvidedState<T> = RefCell::new(ProvidedStateInner {
value: Rc::new(RefCell::new(f())),
notify_any: cx.schedule_update_any(),
consumers: HashSet::new(),
});
cx.provide_context(state)
});
}

View File

@ -1,6 +1,6 @@
use dioxus_core::{ScopeState, TaskId};
use std::future::Future;
use std::{cell::Cell, pin::Pin, rc::Rc};
use std::{cell::Cell, rc::Rc};
/*
@ -22,61 +22,58 @@ pub fn use_coroutine<'a, F>(
where
F: Future<Output = ()> + 'static,
{
cx.use_hook(
move |_| {
let f = create_future();
let id = cx.push_future(f);
State {
let state = cx.use_hook(move |_| {
let f = create_future();
let id = cx.push_future(f);
State {
running: Default::default(),
id
_id: id
// pending_fut: Default::default(),
// running_fut: Default::default(),
}
},
|state| {
// state.pending_fut.set(Some(Box::pin(f)));
});
// if let Some(fut) = state.running_fut.as_mut() {
// cx.push_future(fut);
// }
// state.pending_fut.set(Some(Box::pin(f)));
// if let Some(fut) = state.running_fut.take() {
// state.running.set(true);
// fut.resume();
// }
// if let Some(fut) = state.running_fut.as_mut() {
// cx.push_future(fut);
// }
// let submit: Box<dyn FnOnce() + 'a> = Box::new(move || {
// let g = async move {
// running.set(true);
// create_future().await;
// running.set(false);
// };
// let p: Pin<Box<dyn Future<Output = ()>>> = Box::pin(g);
// fut_slot
// .borrow_mut()
// .replace(unsafe { std::mem::transmute(p) });
// });
// if let Some(fut) = state.running_fut.take() {
// state.running.set(true);
// fut.resume();
// }
// let submit = unsafe { std::mem::transmute(submit) };
// state.submit.get_mut().replace(submit);
// let submit: Box<dyn FnOnce() + 'a> = Box::new(move || {
// let g = async move {
// running.set(true);
// create_future().await;
// running.set(false);
// };
// let p: Pin<Box<dyn Future<Output = ()>>> = Box::pin(g);
// fut_slot
// .borrow_mut()
// .replace(unsafe { std::mem::transmute(p) });
// });
// if state.running.get() {
// // let mut fut = state.fut.borrow_mut();
// // cx.push_task(|| fut.as_mut().unwrap().as_mut());
// } else {
// // make sure to drop the old future
// if let Some(fut) = state.fut.borrow_mut().take() {
// drop(fut);
// }
// }
CoroutineHandle { cx, inner: state }
},
)
// let submit = unsafe { std::mem::transmute(submit) };
// state.submit.get_mut().replace(submit);
// if state.running.get() {
// // let mut fut = state.fut.borrow_mut();
// // cx.push_task(|| fut.as_mut().unwrap().as_mut());
// } else {
// // make sure to drop the old future
// if let Some(fut) = state.fut.borrow_mut().take() {
// drop(fut);
// }
// }
CoroutineHandle { cx, inner: state }
}
struct State {
running: Rc<Cell<bool>>,
id: TaskId,
_id: TaskId,
// the way this is structure, you can toggle the coroutine without re-rendering the comppnent
// this means every render *generates* the future, which is a bit of a waste
// todo: allocate pending futures in the bump allocator and then have a true promotion

View File

@ -5,40 +5,38 @@ pub fn use_future<'a, T: 'static, F: Future<Output = T> + 'static>(
cx: &'a ScopeState,
f: impl FnOnce() -> F,
) -> (Option<&T>, FutureHandle<'a, T>) {
cx.use_hook(
|_| {
//
let fut = f();
let slot = Rc::new(Cell::new(None));
let updater = cx.schedule_update();
let state = cx.use_hook(|_| {
//
let fut = f();
let slot = Rc::new(Cell::new(None));
let updater = cx.schedule_update();
let _slot = slot.clone();
let new_fut = async move {
let res = fut.await;
_slot.set(Some(res));
updater();
};
let task = cx.push_future(new_fut);
let _slot = slot.clone();
let new_fut = async move {
let res = fut.await;
_slot.set(Some(res));
updater();
};
let task = cx.push_future(new_fut);
UseFutureInner {
needs_regen: true,
slot,
value: None,
task: Some(task),
}
},
|state| {
if let Some(value) = state.slot.take() {
state.value = Some(value);
state.task = None;
}
(
state.value.as_ref(),
FutureHandle {
cx,
value: Cell::new(None),
},
)
UseFutureInner {
needs_regen: true,
slot,
value: None,
task: Some(task),
}
});
if let Some(value) = state.slot.take() {
state.value = Some(value);
state.task = None;
}
(
state.value.as_ref(),
FutureHandle {
cx,
value: Cell::new(None),
},
)
}

View File

@ -14,18 +14,15 @@ use std::{
};
pub fn use_model<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> T) -> UseModel<'a, T> {
cx.use_hook(
|_| UseModelInner {
update_scheduled: Cell::new(false),
update_callback: cx.schedule_update(),
value: RefCell::new(f()),
// tasks: RefCell::new(Vec::new()),
},
|inner| {
inner.update_scheduled.set(false);
UseModel { inner }
},
)
let inner = cx.use_hook(|_| UseModelInner {
update_scheduled: Cell::new(false),
update_callback: cx.schedule_update(),
value: RefCell::new(f()),
// tasks: RefCell::new(Vec::new()),
});
inner.update_scheduled.set(false);
UseModel { inner }
}
pub struct UseModel<'a, T> {
@ -81,21 +78,10 @@ pub fn use_model_coroutine<'a, T, F: Future<Output = ()> + 'static>(
_model: UseModel<T>,
_f: impl FnOnce(AppModels) -> F,
) -> UseModelCoroutine {
cx.use_hook(
|_| {
//
UseModelTaskInner {
task: Default::default(),
}
},
|inner| {
// if let Some(task) = inner.task.get_mut() {
// cx.push_task(|| task);
// }
//
todo!()
},
)
cx.use_hook(|_| UseModelTaskInner {
task: Default::default(),
});
todo!()
}
impl<T> Copy for UseModel<'_, T> {}

View File

@ -6,17 +6,14 @@ use std::{
use dioxus_core::ScopeState;
pub fn use_ref<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> T) -> UseRef<'a, T> {
cx.use_hook(
|_| UseRefInner {
update_scheduled: Cell::new(false),
update_callback: cx.schedule_update(),
value: RefCell::new(f()),
},
|inner| {
inner.update_scheduled.set(false);
UseRef { inner }
},
)
let inner = cx.use_hook(|_| UseRefInner {
update_scheduled: Cell::new(false),
update_callback: cx.schedule_update(),
value: RefCell::new(f()),
});
inner.update_scheduled.set(false);
UseRef { inner }
}
pub struct UseRef<'a, T> {

View File

@ -51,32 +51,28 @@ pub fn use_state<'a, T: 'static>(
cx: &'a ScopeState,
initial_state_fn: impl FnOnce() -> T,
) -> UseState<'a, T> {
cx.use_hook(
move |_| {
let first_val = initial_state_fn();
UseStateInner {
current_val: Rc::new(first_val),
update_callback: cx.schedule_update(),
wip: Rc::new(RefCell::new(None)),
update_scheuled: Cell::new(false),
}
},
move |hook| {
hook.update_scheuled.set(false);
let hook = cx.use_hook(move |_| {
let first_val = initial_state_fn();
UseStateInner {
current_val: Rc::new(first_val),
update_callback: cx.schedule_update(),
wip: Rc::new(RefCell::new(None)),
update_scheuled: Cell::new(false),
}
});
let mut new_val = hook.wip.borrow_mut();
if new_val.is_some() {
// if there's only one reference (weak or otherwise), we can just swap the values
if let Some(val) = Rc::get_mut(&mut hook.current_val) {
*val = new_val.take().unwrap();
} else {
hook.current_val = Rc::new(new_val.take().unwrap());
}
}
hook.update_scheuled.set(false);
let mut new_val = hook.wip.borrow_mut();
if new_val.is_some() {
// if there's only one reference (weak or otherwise), we can just swap the values
if let Some(val) = Rc::get_mut(&mut hook.current_val) {
*val = new_val.take().unwrap();
} else {
hook.current_val = Rc::new(new_val.take().unwrap());
}
}
UseState { inner: &*hook }
},
)
UseState { inner: &*hook }
}
struct UseStateInner<T: 'static> {
current_val: Rc<T>,

View File

@ -7,34 +7,31 @@ pub fn use_suspense<R: 'static, F: Future<Output = R> + 'static>(
create_future: impl FnOnce() -> F,
render: impl FnOnce(&R) -> Element,
) -> Element {
cx.use_hook(
|_| {
let fut = create_future();
let sus = cx.use_hook(|_| {
let fut = create_future();
let wip_value: Rc<Cell<Option<R>>> = Default::default();
let wip_value: Rc<Cell<Option<R>>> = Default::default();
let wip = wip_value.clone();
let new_fut = async move {
let val = fut.await;
wip.set(Some(val));
};
let wip = wip_value.clone();
let new_fut = async move {
let val = fut.await;
wip.set(Some(val));
};
let task = cx.push_future(new_fut);
SuspenseInner {
task,
value: None,
wip_value,
}
},
|sus| {
if let Some(value) = sus.value.as_ref() {
render(&value)
} else {
// generate a placeholder node if the future isnt ready
None
}
},
)
let task = cx.push_future(new_fut);
SuspenseInner {
task,
value: None,
wip_value,
}
});
if let Some(value) = sus.value.as_ref() {
render(&value)
} else {
// generate a placeholder node if the future isnt ready
None
}
}
struct SuspenseInner<R> {

View File

@ -281,8 +281,8 @@ builder_constructors! {
/// ```
///
/// ## References:
/// - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div
/// - https://www.w3schools.com/tags/tag_div.asp
/// - <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div>
/// - <https://www.w3schools.com/tags/tag_div.asp>
div {};
/// Build a
@ -883,7 +883,6 @@ builder_constructors! {
src: Uri,
step: String,
tabindex: usize,
width: isize,
// Manual implementations below...

View File

@ -21,12 +21,12 @@ pub mod on {
$(
$(#[$method_attr])*
pub fn $name<'a, F>(
c: NodeFactory<'a>,
factory: NodeFactory<'a>,
mut callback: F,
) -> Listener<'a>
where F: FnMut(Arc<$wrapper>) + 'a
{
let bump = &c.bump();
let bump = &factory.bump();
// we can't allocate unsized in bumpalo's box, so we need to craft the box manually
// safety: this is essentially the same as calling Box::new() but manually
@ -48,7 +48,7 @@ pub mod on {
callback: bump.alloc(std::cell::RefCell::new(Some(callback))),
};
c.listener(shortname, handler)
factory.listener(shortname, handler)
}
)*
)*
@ -179,8 +179,8 @@ pub mod on {
/// ```
///
/// ## Reference
/// - https://www.w3schools.com/tags/ev_onclick.asp
/// - https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event
/// - <https://www.w3schools.com/tags/ev_onclick.asp>
/// - <https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event>
onclick
/// oncontextmenu
@ -430,7 +430,7 @@ pub mod on {
/// Get the key code as an enum Variant.
///
/// This is intended for things like arrow keys, escape keys, function keys, and other non-international keys.
/// To match on unicode sequences, use the [`key`] method - this will return a string identifier instead of a limited enum.
/// To match on unicode sequences, use the [`KeyboardEvent::key`] method - this will return a string identifier instead of a limited enum.
///
///
/// ## Example

View File

@ -399,7 +399,7 @@ pub trait GlobalAttributes {
height: "height",
/// Specifies how flex items are aligned along the main axis of the flex container after any flexible lengths and auto margins have been resolved.
justify_content: "auto margins have been resolved.",
justify_content: "justify-content",
/// Specify the location of the left edge of the positioned element.
left: "left",
@ -535,6 +535,7 @@ pub trait GlobalAttributes {
/// Sets the horizontal alignment of inline content.
text_align: "text-align",
/// Specifies how the last line of a block or a line right before a forced line break is aligned when is justify.",
text_align_last: "text-align-last",
@ -614,7 +615,7 @@ pub trait GlobalAttributes {
word_wrap: "word-wrap",
/// Specifies a layering or stacking order for positioned elements.
z_index : "z-index ",
z_index : "z-index",
}
aria_trait_methods! {

View File

@ -49,7 +49,8 @@ impl<R: Routable> RouterService<R> {
}
pub fn use_router_service<R: Routable>(cx: &ScopeState) -> Option<&Rc<RouterService<R>>> {
cx.use_hook(|_| cx.consume_state::<RouterService<R>>(), |f| f.as_ref())
cx.use_hook(|_| cx.consume_state::<RouterService<R>>())
.as_ref()
}
/// This hould only be used once per app
@ -57,7 +58,7 @@ pub fn use_router_service<R: Routable>(cx: &ScopeState) -> Option<&Rc<RouterServ
/// You can manually parse the route if you want, but the derived `parse` method on `Routable` will also work just fine
pub fn use_router<R: Routable>(cx: &ScopeState, mut parse: impl FnMut(&str) -> R + 'static) -> &R {
// for the web, attach to the history api
cx.use_hook(
let state = cx.use_hook(
#[cfg(not(feature = "web"))]
|_| {},
#[cfg(feature = "web")]
@ -112,15 +113,13 @@ pub fn use_router<R: Routable>(cx: &ScopeState, mut parse: impl FnMut(&str) -> R
service
},
|state| {
let base = state.base_ur.borrow();
if let Some(base) = base.as_ref() {
//
let path = format!("{}{}", base, state.get_current_route());
}
let history = state.history_service.borrow();
);
state.historic_routes.last().unwrap()
},
)
let base = state.base_ur.borrow();
if let Some(base) = base.as_ref() {
let path = format!("{}{}", base, state.get_current_route());
}
let history = state.history_service.borrow();
state.historic_routes.last().unwrap()
}

View File

@ -4,30 +4,27 @@ use crate::{Routable, RouterCfg, RouterService};
use dioxus_core::ScopeState;
/// Initialize the app's router service and provide access to `Link` components
pub fn use_router<'a, R: Routable>(cx: &'a ScopeState, cfg: impl FnOnce(&mut RouterCfg)) -> &'a R {
cx.use_hook(
|_| {
let svc: RouterService<R> = RouterService {
regen_route: cx.schedule_update(),
pending_routes: RefCell::new(Vec::new()),
};
let first_path = R::default();
cx.provide_context(svc);
UseRouterInner {
svc: cx.consume_context::<RouterService<R>>().unwrap(),
history: vec![first_path],
}
},
|f| {
let mut pending_routes = f.svc.pending_routes.borrow_mut();
pub fn use_router<'a, R: Routable>(cx: &'a ScopeState, _cfg: impl FnOnce(&mut RouterCfg)) -> &'a R {
let router = cx.use_hook(|_| {
let svc: RouterService<R> = RouterService {
regen_route: cx.schedule_update(),
pending_routes: RefCell::new(Vec::new()),
};
let first_path = R::default();
cx.provide_context(svc);
UseRouterInner {
svc: cx.consume_context::<RouterService<R>>().unwrap(),
history: vec![first_path],
}
});
for route in pending_routes.drain(..) {
f.history.push(route);
}
let mut pending_routes = router.svc.pending_routes.borrow_mut();
f.history.last().unwrap()
},
)
for route in pending_routes.drain(..) {
router.history.push(route);
}
router.history.last().unwrap()
}
struct UseRouterInner<R: Routable> {

View File

@ -1,24 +1,87 @@
# Dioxus SSR
<div align="center">
<h1>Dioxus Server-Side Rendering (SSR)</h1>
<p>
<strong>Render Dioxus to valid html.</strong>
</p>
</div>
Render a Dioxus VirtualDOM to a string.
## Resources
This crate is a part of the broader Dioxus ecosystem. For more resources about Dioxus, check out:
- [Getting Started](https://dioxuslabs.com/getting-started)
- [Book](https://dioxuslabs.com/book)
- [Reference](https://dioxuslabs.com/reference)
- [Community Examples](https://github.com/DioxusLabs/community-examples)
## Overview
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
// Our app:
const App: Component = |cx| rsx!(cx, div {"hello world!"});
let app: Component = |cx| cx.render(rsx!(div {"hello world!"}));
// Build the virtualdom from our app
let mut vdom = VirtualDOM::new(App);
// This runs components, lifecycles, etc. without needing a physical dom. Some features (like noderef) won't work.
let mut vdom = VirtualDom::new(app);
let _ = vdom.rebuild();
// Render the entire virtualdom from the root
let text = dioxus::ssr::render_vdom(&vdom);
assert_eq!(text, "<div>hello world!</div>")
```
## Basic Usage
The simplest example is to simply render some `rsx!` nodes to html. This can be done with the [`render_lazy`] api.
```rust
let content = dioxus::ssr::render(rsx!{
div {
(0..5).map(|i| rsx!(
"Number: {i}"
))
}
});
```
## Rendering a VirtualDom
```rust
let mut dom = VirtualDom::new(app);
let _ = dom.rebuild();
let content = dioxus::ssr::render_vdom(&dom);
```
## Configuring output
It's possible to configure the output of the generated HTML.
```rust
let content = dioxus::ssr::render_vdom(&dom, |config| config.pretty(true).prerender(true));
```
## Usage as a writer
We provide the basic `SsrFormatter` object that implements `Display`, so you can integrate SSR into an existing string, or write directly to a file.
```rust
use std::fmt::{Error, Write};
let mut buf = String::new();
let dom = VirtualDom::new(app);
let _ = dom.rebuild();
let args = dioxus::ssr::formatter(dom, |config| config);
buf.write_fmt!(format_args!("{}", args));
```
## Configuration
## Usage in pre-rendering
This crate is particularly useful in pre-generating pages server-side and then selectively loading dioxus client-side to pick up the reactive elements.

View File

@ -1,14 +1,4 @@
//!
//!
//!
//!
//! This crate demonstrates how to implement a custom renderer for Dioxus VNodes via the `TextRenderer` renderer.
//! The `TextRenderer` consumes a Dioxus Virtual DOM, progresses its event queue, and renders the VNodes to a String.
//!
//! While `VNode` supports "to_string" directly, it renders child components as the RSX! macro tokens. For custom components,
//! an external renderer is needed to progress the component lifecycles. The `TextRenderer` shows how to use the Virtual DOM
//! API to progress these lifecycle events to generate a fully-mounted Virtual DOM instance which can be renderer in the
//! `render` method.
#![doc = include_str!("../README.md")]
use std::fmt::{Display, Formatter};
@ -17,7 +7,6 @@ use dioxus_core::exports::bumpalo::Bump;
use dioxus_core::IntoVNode;
use dioxus_core::*;
/// A memory pool for rendering
pub struct SsrRenderer {
inner: bumpalo::Bump,
cfg: SsrConfig,
@ -34,6 +23,7 @@ impl SsrRenderer {
pub fn render_lazy<'a>(&'a mut self, f: LazyNodes<'a, '_>) -> String {
let bump = &mut self.inner as *mut _;
let s = self.render_inner(f);
// reuse the bump's memory
unsafe { (&mut *bump as &mut bumpalo::Bump).reset() };
s
@ -63,13 +53,12 @@ pub fn render_lazy<'a>(f: LazyNodes<'a, '_>) -> String {
// regular component usage. The <'a> lifetime is used to enforce that all calls of IntoVnode use the same allocator.
//
// When LazyNodes are provided, they are FnOnce, but do not come with a allocator selected to borrow from. The <'a>
// lifetime is therefore longer than the lifetime of the allocator which doesn't exist yet.
// lifetime is therefore longer than the lifetime of the allocator which doesn't exist... yet.
//
// Therefore, we cast our local bump alloactor into right lifetime. This is okay because our usage of the bump arena
// is *definitely* shorter than the <'a> lifetime, and we return owned data - not borrowed data.
// Therefore, we know that no references are leaking.
let _b: &'a Bump = unsafe { std::mem::transmute(borrowed) };
// is *definitely* shorter than the <'a> lifetime, and we return *owned* data - not borrowed data.
let _b = unsafe { std::mem::transmute::<&Bump, &'a Bump>(borrowed) };
let root = f.into_vnode(NodeFactory::new(_b));
format!(
@ -85,6 +74,7 @@ pub fn render_lazy<'a>(f: LazyNodes<'a, '_>) -> String {
pub fn render_vdom(dom: &VirtualDom) -> String {
format!("{:}", TextRenderer::from_vdom(dom, SsrConfig::default()))
}
pub fn render_vdom_cfg(dom: &VirtualDom, cfg: impl FnOnce(SsrConfig) -> SsrConfig) -> String {
format!(
"{:}",

View File

@ -1,5 +1,5 @@
//! <div align="center">
//! <h1>🌗🚀 📦 Dioxus</h1>
//! <h1>🌗🚀 Dioxus</h1>
//! <p>
//! <strong>A concurrent, functional, virtual DOM for Rust</strong>
//! </p>
@ -37,7 +37,7 @@
//! All Dioxus apps are built by composing functions that take in a `Scope` which is generic over some `Properties` and return an `Element`.
//! A `Scope` holds relevant state data for the the currently-rendered component.
//!
//! To launch an app, we use the `launch` method for the specific renderer we want to use. In the launch function, was pass the app's `Component`.
//! To launch an app, we use the `launch` method for the specific renderer we want to use. In the launch function, we pass the app's `Component`.
//!
//! ```rust
//! use dioxus::prelude::*;
@ -51,10 +51,89 @@
//! }
//! ```
//!
//! ## Elements & your first component
//!
//! To assemble UI trees with Diouxs, 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.
//!
//! Any element in `rsx!` can have attributes, listeners, and children. For
//! consistency, we force all attributes and listeners to be listed *before*
//! children.
//!
//! ```rust
//! let value = "123";
//!
//! rsx!(
//! div {
//! class: "my-class {value}", // <--- attribute
//! onclick: move |_| log::info!("clicked!"), // <--- listener
//! h1 { "hello world" }, // <--- child
//! }
//! )
//! ```
//!
//! The rsx macro accepts attributes in "struct form" and then will parse the rest
//! of the body as child elements and rust expressions. Any rust expression that
//! implements `IntoIterator<Item = impl IntoVNode>` will be parsed as a child.
//!
//! ```rust
//! rsx!(
//! div {
//! (0..10).map(|_| rsx!(span { "hello world" }))
//! }
//! )
//!
//! ```
//!
//! Used within components, the rsx! macro must be rendered into an `Element` with
//! the `render` function on Scope.
//!
//! If we want to omit the boilerplate of `cx.render`, we can simply pass in
//! `cx` as the first argument of rsx. This is sometimes useful when we need to
//! render nodes in match statements.
//!
//! ```rust
//! fn example(cx: Scope) -> Element {
//!
//! // both of these are equivalent
//! cx.render(rsx!("hello world"))
//!
//! rsx!(cx, "hello world!")
//! }
//! ```
//!
//! Putting everything together, we can write a simple component that a list of
//! elements:
//!
//! ```rust
//! fn app(cx: Scope) -> Element {
//! let name = "dave";
//! cx.render(rsx!(
//! h1 { "Hello, {name}!" }
//! div {
//! class: "my-class",
//! id: "my-id",
//!
//! (0..5).map(|i| rsx!(
//! div { key: "{i}"
//! "FizzBuzz: {i}"
//! }
//! ))
//!
//! }
//! ))
//! }
//! ```
//!
//! ## 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 use the `()` type. In Dioxus, all properties are memoized by default!
//! 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 use the `()` type or simply omit the type altogether.
//!
//! In Dioxus, all properties are memoized by default!
//!
//! ```rust
//! fn App(cx: Scope) -> Element {
@ -67,7 +146,9 @@
//! }
//! ```
//!
//! Our `Header` component takes in a `title` and a `color` property, which we delcare on an explicit `HeaderProps` struct.
//! Our `Header` component takes in a `title` and a `color` property, which we
//! delcare on an explicit `HeaderProps` struct.
//!
//! ```
//! // The `Props` derive macro lets us add additional functionality to how props are interpreted.
//! #[derive(Props, PartialEq)]
@ -86,9 +167,68 @@
//! }
//! ```
//!
//! Components may use the `inline_props` macro to completely inline the props
//! definition into the function arguments.
//!
//! ```rust
//! #[inline_props]
//! fn Header(cx: Scope, title: String, color: String) -> Element {
//! cx.render(rsx!(
//! div {
//! background_color: "{color}"
//! h1 { "{title}" }
//! }
//! ))
//! }
//! ```
//!
//! Components may also borrow data from their parent component. We just need to
//! attach some lifetimes to the props struct.
//! > Note: we don't need to derive `PartialEq` for borrowed props since they cannot be memoized.
//!
//! ```rust
//! #[derive(Props)]
//! struct HeaderProps<'a> {
//! title: &'a str,
//! color: &'a str,
//! }
//!
//! fn Header<'a>(cx: Scope<'a, HeaderProps<'a>>) -> Element {
//! cx.render(rsx!(
//! div {
//! background_color: "{cx.props.color}"
//! h1 { "{cx.props.title}" }
//! }
//! ))
//! }
//! ```
//!
//! Components that beging with an uppercase letter may be called through
//! traditional curly-brace syntax like so:
//!
//! ```rust
//! rsx!(
//! Header { title: "My App" }
//! )
//! ```
//!
//! Alternatively, if your components begin with a lowercase letter, you can use
//! the function call syntax:
//!
//! ```rust
//! rsx!(
//! header( title: "My App" )
//! )
//! ```
//!
//! ## Hooks
//!
//! While components are reusable forms of UI elements, hooks are reusable forms of logic. All hooks start with `use_`. We can use hooks to declare state.
//! While components are reusable forms of UI elements, hooks are reusable forms
//! of logic. Hooks provide us a way of retrieving state from `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 state and modify it from within listeners.
//!
//! ```rust
//! fn app(cx: Scope) -> Element {
@ -98,10 +238,11 @@
//! }
//! ```
//!
//! Hooks are sensitive to how they are used. To use hooks, you must abide by the ["rules of hooks" (borrowed from react)](https://reactjs.org/docs/hooks-rules.html):
//! - Hooks should not be called in callbacks
//! - Hooks should not be called in out of order
//! - Hooks should not be called in loops or conditionals
//! Hooks are sensitive to how they are used. To use hooks, you must abide by the
//! ["rules of hooks" (borrowed from react)](https://reactjs.org/docs/hooks-rules.html):
//! - 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
//!
//! In a sense, hooks let us add a field of state to our component without declaring
//! an explicit struct. However, this means we need to "load" the struct in the right
@ -112,7 +253,7 @@
//! ```rust
//! fn use_username(cx: &ScopeState, id: Uuid) -> bool {
//! let users = use_context::<Users>(cx);
//! users.get(&id).is_some().map(|user| user.logged_in).ok_or(false)
//! users.get(&id).map(|user| user.logged_in).ok_or(false)
//! }
//! ```
//!
@ -120,7 +261,7 @@
//!
//! ```rust
//! fn use_mut_string(cx: &ScopeState) -> &mut String {
//! cx.use_hook(|_| "Hello".to_string(), |hook| hook)
//! cx.use_hook(|_| "Hello".to_string())
//! }
//! ```
//!