Update docs to reflect changes in master (#670)

* work on updating docs to master version

* more updates

* more polishing

* finish interactivity chapter

* finish updating core guide

* fix grammer mistakes and typos

* more grammer fixes

* add liveview guide

* remove doc build

* WIP custom renderer docs

* add axum as dev-dependancy to guide

* fix examples

* fix overview example

* use md book fork to fix compilation
This commit is contained in:
ealmloff authored and GitHub committed 2023-01-06 15:00:12 -08:00
1 parent a616a8fa9d
commit cd4474cc4f
51 files changed
+870 -549

No files matched your search

+7 -6
View File
@@ -5,13 +5,14 @@ fn main() {
dioxus_desktop::launch(App);
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
// ANCHOR: boolean_attribute
cx.render(rsx! {
div {
hidden: "false",
"hello"
}
})
cx.render(rsx! {
div {
hidden: "false",
"hello"
}
})
// ANCHOR_END: boolean_attribute
}
+32 -31
View File
@@ -5,49 +5,50 @@ fn main() {
dioxus_desktop::launch(App);
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
cx.render(rsx! {
// ANCHOR: OptionalProps_usage
Title {
title: "Some Title",
},
Title {
title: "Some Title",
subtitle: "Some Subtitle",
},
// Providing an Option explicitly won't compile though:
// Title {
// title: "Some Title",
// subtitle: None,
// },
Title {
title: "Some Title",
},
Title {
title: "Some Title",
subtitle: "Some Subtitle",
},
// Providing an Option explicitly won't compile though:
// Title {
// title: "Some Title",
// subtitle: None,
// },
// ANCHOR_END: OptionalProps_usage
// ANCHOR: ExplicitOption_usage
ExplicitOption {
title: "Some Title",
subtitle: None,
},
ExplicitOption {
title: "Some Title",
subtitle: Some("Some Title"),
},
// This won't compile:
// ExplicitOption {
// title: "Some Title",
// },
ExplicitOption {
title: "Some Title",
subtitle: None,
},
ExplicitOption {
title: "Some Title",
subtitle: Some("Some Title"),
},
// This won't compile:
// ExplicitOption {
// title: "Some Title",
// },
// ANCHOR_END: ExplicitOption_usage
// ANCHOR: DefaultComponent_usage
DefaultComponent {
number: 5,
},
DefaultComponent {},
DefaultComponent {
number: 5,
},
DefaultComponent {},
// ANCHOR_END: DefaultComponent_usage
// ANCHOR: IntoComponent_usage
IntoComponent {
string: "some &str",
},
IntoComponent {
string: "some &str",
},
// ANCHOR_END: IntoComponent_usage
})
}
+63 -26
View File
@@ -1,3 +1,4 @@
#![allow(unused)]
#![allow(non_snake_case)]
use dioxus::prelude::*;
@@ -16,6 +17,7 @@ pub fn App(cx: Scope) -> Element {
}
#[inline_props]
#[rustfmt::skip]
fn LogIn<'a>(
cx: Scope<'a>,
is_logged_in: bool,
@@ -23,38 +25,73 @@ fn LogIn<'a>(
on_log_out: EventHandler<'a>,
) -> Element<'a> {
// ANCHOR: if_else
if *is_logged_in {
cx.render(rsx! {
div {
"Welcome!",
button {
onclick: move |_| on_log_out.call(()),
"Log Out",
}
}
})
} else {
cx.render(rsx! {
button {
onclick: move |_| on_log_in.call(()),
"Log In",
}
})
}
if *is_logged_in {
cx.render(rsx! {
"Welcome!"
button {
onclick: move |_| on_log_out.call(()),
"Log Out",
}
})
} else {
cx.render(rsx! {
button {
onclick: move |_| on_log_in.call(()),
"Log In",
}
})
}
// ANCHOR_END: if_else
}
#[inline_props]
#[rustfmt::skip]
fn LogInImproved<'a>(
cx: Scope<'a>,
is_logged_in: bool,
on_log_in: EventHandler<'a>,
on_log_out: EventHandler<'a>,
) -> Element<'a> {
// ANCHOR: if_else_improved
cx.render(rsx! {
// We only render the welcome message if we are logged in
// You can use if statements in the middle of a render block to conditionally render elements
if *is_logged_in {
// Notice the body of this if statment is rsx code, not an expression
"Welcome!"
}
button {
// depending on the value of `is_logged_in`, we will call a different event handler
onclick: move |_| if *is_logged_in {
on_log_in.call(())
}
else{
on_log_out.call(())
},
if *is_logged_in {
// if we are logged in, the button should say "Log Out"
"Log Out"
} else {
// if we are not logged in, the button should say "Log In"
"Log In"
}
}
})
// ANCHOR_END: if_else_improved
}
#[inline_props]
#[rustfmt::skip]
fn LogInWarning(cx: Scope, is_logged_in: bool) -> Element {
// ANCHOR: conditional_none
if *is_logged_in {
return cx.render(rsx!(()));
}
if *is_logged_in {
return None;
}
cx.render(rsx! {
a {
"You must be logged in to comment"
}
})
cx.render(rsx! {
a {
"You must be logged in to comment"
}
})
// ANCHOR_END: conditional_none
}
+8 -7
View File
@@ -5,15 +5,16 @@ fn main() {
dioxus_desktop::launch(App);
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
// ANCHOR: dangerous_inner_html
// this should come from a trusted source
let contents = "live <b>dangerously</b>";
// this should come from a trusted source
let contents = "live <b>dangerously</b>";
cx.render(rsx! {
div {
dangerous_inner_html: "{contents}",
}
})
cx.render(rsx! {
div {
dangerous_inner_html: "{contents}",
}
})
// ANCHOR_END: dangerous_inner_html
}
+7 -6
View File
@@ -5,13 +5,14 @@ fn main() {
dioxus_desktop::launch(App);
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
// ANCHOR: rsx
cx.render(rsx! {
button {
onclick: move |event| println!("Clicked! Event: {event:?}"),
"click me!"
}
})
cx.render(rsx! {
button {
onclick: move |event| println!("Clicked! Event: {event:?}"),
"click me!"
}
})
// ANCHOR_END: rsx
}
+13 -12
View File
@@ -5,20 +5,21 @@ fn main() {
dioxus_desktop::launch(App);
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
// ANCHOR: rsx
cx.render(rsx! {
div {
onclick: move |_event| {},
"outer",
button {
onclick: move |event| {
// now, outer won't be triggered
event.stop_propagation();
},
"inner"
}
cx.render(rsx! {
div {
onclick: move |_event| {},
"outer",
button {
onclick: move |event| {
// now, outer won't be triggered
event.stop_propagation();
},
"inner"
}
})
}
})
// ANCHOR_END: rsx
}
+7 -6
View File
@@ -5,13 +5,14 @@ fn main() {
dioxus_desktop::launch(App);
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
// ANCHOR: prevent_default
cx.render(rsx! {
input {
prevent_default: "oninput",
prevent_default: "onclick",
}
})
cx.render(rsx! {
input {
prevent_default: "oninput",
prevent_default: "onclick",
}
})
// ANCHOR_END: prevent_default
}
@@ -1,12 +1,15 @@
// ANCHOR: all
#![allow(non_snake_case)]
// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types
use dioxus::prelude::*;
fn main() {
// launch the dioxus app in a webview
dioxus_desktop::launch(App);
}
// ANCHOR: component
// define a component that renders a div with the text "Hello, world!"
fn App(cx: Scope) -> Element {
cx.render(rsx! {
div {
@@ -0,0 +1,60 @@
// ANCHOR: all
use axum::{extract::ws::WebSocketUpgrade, response::Html, routing::get, Router};
use dioxus::prelude::*;
// ANCHOR: glue
#[tokio::main]
async fn main() {
let addr: std::net::SocketAddr = ([127, 0, 0, 1], 3030).into();
let view = dioxus_liveview::LiveViewPool::new();
let app = Router::new()
// The root route contains the glue code to connect to the WebSocket
.route(
"/",
get(move || async move {
Html(format!(
r#"
<!DOCTYPE html>
<html>
<head> <title>Dioxus LiveView with Axum</title> </head>
<body> <div id="main"></div> </body>
{glue}
</html>
"#,
// Create the glue code to connect to the WebSocket on the "/ws" route
glue = dioxus_liveview::interpreter_glue(&format!("ws://{addr}/ws"))
))
}),
)
// The WebSocket route is what Dioxus uses to communicate with the browser
.route(
"/ws",
get(move |ws: WebSocketUpgrade| async move {
ws.on_upgrade(move |socket| async move {
// When the WebSocket is upgraded, launch the LiveView with the app component
_ = view.launch(dioxus_liveview::axum_socket(socket), app).await;
})
}),
);
println!("Listening on http://{}", addr);
axum::Server::bind(&addr.to_string().parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
// ANCHOR_END: glue
// ANCHOR: app
fn app(cx: Scope) -> Element {
cx.render(rsx! {
div {
"Hello, world!"
}
})
}
// ANCHOR_END: app
// ANCHOR_END: all
+4 -7
View File
@@ -1,20 +1,17 @@
#![allow(non_snake_case)]
// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types
use dioxus::prelude::*;
fn main() {
// launch the app in the terminal
dioxus_tui::launch(App);
}
// create a component that renders a div with the text "Hello, world!"
fn App(cx: Scope) -> Element {
cx.render(rsx! {
div {
width: "100%",
height: "10px",
background_color: "red",
justify_content: "center",
align_items: "center",
"Hello world!"
"Hello, world!"
}
})
}
+3
View File
@@ -1,10 +1,13 @@
#![allow(non_snake_case)]
// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types
use dioxus::prelude::*;
fn main() {
// launch the web app
dioxus_web::launch(App);
}
// create a component that renders a div with the text "Hello, world!"
fn App(cx: Scope) -> Element {
cx.render(rsx! {
div {
+37 -36
View File
@@ -7,60 +7,61 @@ fn main() {
dioxus_desktop::launch(App);
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
let you_are_happy = true;
let you_know_it = false;
// ANCHOR: conditional
// ❌ don't call hooks in conditionals!
// We must ensure that the same hooks will be called every time
// But `if` statements only run if the conditional is true!
// So we might violate rule 2.
if you_are_happy && you_know_it {
let something = use_state(cx, || "hands");
println!("clap your {something}")
}
// ✅ instead, *always* call use_state
// You can put other stuff in the conditional though
// ❌ don't call hooks in conditionals!
// We must ensure that the same hooks will be called every time
// But `if` statements only run if the conditional is true!
// So we might violate rule 2.
if you_are_happy && you_know_it {
let something = use_state(cx, || "hands");
if you_are_happy && you_know_it {
println!("clap your {something}")
}
println!("clap your {something}")
}
// ✅ instead, *always* call use_state
// You can put other stuff in the conditional though
let something = use_state(cx, || "hands");
if you_are_happy && you_know_it {
println!("clap your {something}")
}
// ANCHOR_END: conditional
// ANCHOR: closure
// ❌ don't call hooks inside closures!
// We can't guarantee that the closure, if used, will be called at the same time every time
let _a = || {
let b = use_state(cx, || 0);
b.get()
};
// ✅ instead, move hook `b` outside
// ❌ don't call hooks inside closures!
// We can't guarantee that the closure, if used, will be called in the same order every time
let _a = || {
let b = use_state(cx, || 0);
let _a = || b.get();
b.get()
};
// ✅ instead, move hook `b` outside
let b = use_state(cx, || 0);
let _a = || b.get();
// ANCHOR_END: closure
let names: Vec<&str> = vec![];
// ANCHOR: loop
// `names` is a Vec<&str>
// `names` is a Vec<&str>
// ❌ Do not use hooks in loops!
// In this case, if the length of the Vec changes, we break rule 2
for _name in &names {
let is_selected = use_state(cx, || false);
println!("selected: {is_selected}");
}
// ❌ Do not use hooks in loops!
// In this case, if the length of the Vec changes, we break rule 2
for _name in &names {
let is_selected = use_state(cx, || false);
println!("selected: {is_selected}");
}
// ✅ Instead, use a hashmap with use_ref
let selection_map = use_ref(cx, HashMap::<&str, bool>::new);
// ✅ Instead, use a hashmap with use_ref
let selection_map = use_ref(cx, HashMap::<&str, bool>::new);
for name in &names {
let is_selected = selection_map.read()[name];
println!("selected: {is_selected}");
}
for name in &names {
let is_selected = selection_map.read()[name];
println!("selected: {is_selected}");
}
// ANCHOR_END: loop
cx.render(rsx!(()))
+15 -2
View File
@@ -7,12 +7,25 @@ fn main() {
// ANCHOR: component
fn App(cx: Scope) -> Element {
// count will be initialized to 0 the first time the component is rendered
let mut count = use_state(cx, || 0);
cx.render(rsx!(
h1 { "High-Five counter: {count}" }
button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" }
button {
onclick: move |_| {
// changing the count will cause the component to re-render
count += 1
},
"Up high!"
}
button {
onclick: move |_| {
// changing the count will cause the component to re-render
count -= 1
},
"Down low!"
}
))
}
// ANCHOR_END: component
+2 -3
View File
@@ -8,13 +8,12 @@ fn main() {
// ANCHOR: component
fn App(cx: Scope) -> Element {
let list = use_ref(cx, Vec::new);
let list_formatted = format!("{:?}", *list.read());
cx.render(rsx!(
p { "Current list: {list_formatted}" }
p { "Current list: {list.read():?}" }
button {
onclick: move |event| {
list.write().push(event)
list.with_mut(|list| list.push(event));
},
"Click me!"
}
+4 -2
View File
@@ -11,9 +11,10 @@ fn main() {
struct DarkMode(bool);
// ANCHOR_END: DarkMode_struct
#[rustfmt::skip]
pub fn App(cx: Scope) -> Element {
// ANCHOR: context_provider
use_shared_state_provider(cx, || DarkMode(false));
use_shared_state_provider(cx, || DarkMode(false));
// ANCHOR_END: context_provider
let is_dark_mode = use_is_dark_mode(cx);
@@ -34,9 +35,10 @@ pub fn App(cx: Scope) -> Element {
}))
}
#[rustfmt::skip]
pub fn use_is_dark_mode(cx: &ScopeState) -> bool {
// ANCHOR: use_context
let dark_mode_context = use_shared_state::<DarkMode>(cx);
let dark_mode_context = use_shared_state::<DarkMode>(cx);
// ANCHOR_END: use_context
dark_mode_context
+69 -31
View File
@@ -12,42 +12,80 @@ struct Comment {
id: usize,
}
#[rustfmt::skip]
pub fn App(cx: Scope) -> Element {
// ANCHOR: render_list
let comment_field = use_state(cx, String::new);
let mut next_id = use_state(cx, || 0);
let comments = use_ref(cx, Vec::<Comment>::new);
let comment_field = use_state(cx, String::new);
let mut next_id = use_state(cx, || 0);
let comments = use_ref(cx, Vec::<Comment>::new);
let comments_lock = comments.read();
let comments_rendered = comments_lock.iter().map(|comment| {
cx.render(rsx!(CommentComponent {
let comments_lock = comments.read();
let comments_rendered = comments_lock.iter().map(|comment| {
rsx!(CommentComponent {
key: "{comment.id}",
comment: comment.clone(),
})
});
cx.render(rsx!(
form {
onsubmit: move |_| {
comments.write().push(Comment {
content: comment_field.get().clone(),
id: *next_id.get(),
});
next_id += 1;
comment_field.set(String::new());
},
input {
value: "{comment_field}",
oninput: |event| comment_field.set(event.value.clone()),
}
input {
r#type: "submit",
}
},
comments_rendered,
))
// ANCHOR_END: render_list
}
#[rustfmt::skip]
pub fn AppForLoop(cx: Scope) -> Element {
// ANCHOR: render_list_for_loop
let comment_field = use_state(cx, String::new);
let mut next_id = use_state(cx, || 0);
let comments = use_ref(cx, Vec::<Comment>::new);
cx.render(rsx!(
form {
onsubmit: move |_| {
comments.write().push(Comment {
content: comment_field.get().clone(),
id: *next_id.get(),
});
next_id += 1;
comment_field.set(String::new());
},
input {
value: "{comment_field}",
oninput: |event| comment_field.set(event.value.clone()),
}
input {
r#type: "submit",
}
},
for comment in &*comments.read() {
// Notice the body of this for loop is rsx code, not an expression
CommentComponent {
key: "{comment.id}",
comment: comment.clone(),
}))
});
cx.render(rsx!(
form {
onsubmit: move |_| {
comments.write().push(Comment {
content: comment_field.get().clone(),
id: *next_id.get(),
});
next_id += 1;
comment_field.set(String::new());
},
input {
value: "{comment_field}",
oninput: |event| comment_field.set(event.value.clone()),
}
input {
r#type: "submit",
}
},
comments_rendered,
))
// ANCHOR_END: render_list
}
}
))
// ANCHOR_END: render_list_for_loop
}
#[inline_props]
+107 -47
View File
@@ -19,91 +19,151 @@ pub fn App(cx: Scope) -> Element {
))
}
#[rustfmt::skip]
pub fn Empty(cx: Scope) -> Element {
// ANCHOR: empty
cx.render(rsx!(div {}))
cx.render(rsx!(div {
// attributes / listeners
// children
}))
// ANCHOR_END: empty
}
#[rustfmt::skip]
pub fn Children(cx: Scope) -> Element {
// ANCHOR: children
cx.render(rsx!(ol {
li {"First Item"}
li {"Second Item"}
li {"Third Item"}
}))
cx.render(rsx!(ol {
li {"First Item"}
li {"Second Item"}
li {"Third Item"}
}))
// ANCHOR_END: children
}
#[rustfmt::skip]
pub fn Fragments(cx: Scope) -> Element {
// ANCHOR: fragments
cx.render(rsx!(
p {"First Item"},
p {"Second Item"},
Fragment {
span { "a group" },
span { "of three" },
span { "items" },
}
))
cx.render(rsx!(
p {"First Item"},
p {"Second Item"},
Fragment {
span { "a group" },
span { "of three" },
span { "items" },
}
))
// ANCHOR_END: fragments
}
#[rustfmt::skip]
pub fn ManyRoots(cx: Scope) -> Element {
// ANCHOR: manyroots
cx.render(rsx!(
p {"First Item"},
p {"Second Item"},
))
// ANCHOR_END: manyroots
}
#[rustfmt::skip]
pub fn Attributes(cx: Scope) -> Element {
// ANCHOR: attributes
cx.render(rsx!(a {
href: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
class: "primary_button",
"Log In"
}))
cx.render(rsx!(a {
href: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
class: "primary_button",
color: "red",
}))
// ANCHOR_END: attributes
}
#[rustfmt::skip]
pub fn VariableAttributes(cx: Scope) -> Element {
// ANCHOR: variable_attributes
let written_in_rust = true;
let button_type = "button";
cx.render(rsx!(button {
disabled: "{written_in_rust}",
class: "{button_type}",
"Rewrite it in rust"
}))
let written_in_rust = true;
let button_type = "button";
cx.render(rsx!(button {
disabled: "{written_in_rust}",
class: "{button_type}",
"Rewrite it in rust"
}))
// ANCHOR_END: variable_attributes
}
#[rustfmt::skip]
pub fn CustomAttributes(cx: Scope) -> Element {
// ANCHOR: custom_attributes
cx.render(rsx!(b {
"customAttribute": "value",
"Rust is Cool"
}))
// ANCHOR_END: custom_attributes
}
#[rustfmt::skip]
pub fn Formatting(cx: Scope) -> Element {
// ANCHOR: formatting
let coordinates = (42, 0);
let country = "es";
cx.render(rsx!(div {
class: "country-{country}",
"Coordinates: {coordinates:?}",
// arbitrary expressions are allowed,
// as long as they don't contain `{}`
div {
"{country.to_uppercase()}"
},
div {
"{7*6}"
},
}))
// ANCHOR_END: formatting
let coordinates = (42, 0);
let country = "es";
cx.render(rsx!(div {
class: "country-{country}",
"position": "{coordinates:?}",
// arbitrary expressions are allowed,
// as long as they don't contain `{}`
div {
"{country.to_uppercase()}"
},
div {
"{7*6}"
},
// {} can be escaped with {{}}
div {
"{{}}"
},
}))
// ANCHOR_END: formatting
}
#[rustfmt::skip]
pub fn Expression(cx: Scope) -> Element {
// ANCHOR: expression
let text = "Dioxus";
cx.render(rsx!(span {
text.to_uppercase()
}))
let text = "Dioxus";
cx.render(rsx!(span {
text.to_uppercase(),
// create a list of text from 0 to 9
(0..10).map(|i| rsx!{ i.to_string() })
}))
// ANCHOR_END: expression
}
#[rustfmt::skip]
pub fn Loops(cx: Scope) -> Element {
// ANCHOR: loops
cx.render(rsx!{
// use a for loop where the body itself is RSX
div {
// create a list of text from 0 to 9
for i in 0..3 {
// NOTE: the body of the loop is RSX not a rust statement
div {
"{i}"
}
}
}
// iterator equivalent
div {
(0..3).map(|i| rsx!{ div { "{i}" } })
}
})
// ANCHOR_END: loops
}
#[rustfmt::skip]
pub fn IfStatements(cx: Scope) -> Element {
// ANCHOR: ifstatements
cx.render(rsx!{
// use if statements without an else
if true {
rsx!(div { "true" })
}
})
// ANCHOR_END: ifstatements
}
+38 -36
View File
@@ -12,49 +12,51 @@ struct ApiResponse {
image_url: String,
}
#[rustfmt::skip]
fn App(cx: Scope) -> Element {
// ANCHOR: use_future
let future = use_future(cx, (), |_| async move {
reqwest::get("https://dog.ceo/api/breeds/image/random")
.await
.unwrap()
.json::<ApiResponse>()
.await
});
// ANCHOR_END: use_future
// ANCHOR: use_future
let future = use_future(cx, (), |_| async move {
reqwest::get("https://dog.ceo/api/breeds/image/random")
.await
.unwrap()
.json::<ApiResponse>()
.await
});
// ANCHOR_END: use_future
// ANCHOR: render
cx.render(match future.value() {
Some(Ok(response)) => rsx! {
button {
onclick: move |_| future.restart(),
"Click to fetch another doggo"
// ANCHOR: render
cx.render(match future.value() {
Some(Ok(response)) => rsx! {
button {
onclick: move |_| future.restart(),
"Click to fetch another doggo"
}
div {
img {
max_width: "500px",
max_height: "500px",
src: "{response.image_url}",
}
div {
img {
max_width: "500px",
max_height: "500px",
src: "{response.image_url}",
}
}
},
Some(Err(_)) => rsx! { div { "Loading dogs failed" } },
None => rsx! { div { "Loading dogs..." } },
})
// ANCHOR_END: render
}
},
Some(Err(_)) => rsx! { div { "Loading dogs failed" } },
None => rsx! { div { "Loading dogs..." } },
})
// ANCHOR_END: render
}
#[rustfmt::skip]
#[inline_props]
fn RandomDog(cx: Scope, breed: String) -> Element {
// ANCHOR: dependency
let future = use_future(cx, (breed,), |(breed,)| async move {
reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
.await
.unwrap()
.json::<ApiResponse>()
.await
});
// ANCHOR_END: dependency
// ANCHOR: dependency
let future = use_future(cx, (breed,), |(breed,)| async move {
reqwest::get(format!("https://dog.ceo/api/breed/{breed}/images/random"))
.await
.unwrap()
.json::<ApiResponse>()
.await
});
// ANCHOR_END: dependency
cx.render(rsx!(()))
}