codify "platform" into resolution logic, server-fn/fullstack overhaul (#4596)
overhaul dioxus fullstack using a new implementation of server_fn This PR is a complete rewrite of dioxus fullstack designed to improve compatibility with axum and to fix a number of compilation errors based on feature flag unification plumbing through macros.
This commit is contained in:
1 parent
8421c3aef0
commit
f2b1c6d97f
424 files changed
+17284
-6624
No files matched your search
@@ -0,0 +1,89 @@
|
||||
//! Demonstrates cross-component state sharing using Dioxus' Context API
|
||||
//!
|
||||
//! Features:
|
||||
//! - Context provider initialization
|
||||
//! - Nested component consumption
|
||||
//! - Reactive state updates
|
||||
//! - Error handling for missing context
|
||||
//! - Platform-agnostic implementation
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
const STYLE: Asset = asset!("/examples/assets/context_api.css");
|
||||
|
||||
fn main() {
|
||||
launch(app);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn app() -> Element {
|
||||
// Provide theme context at root level
|
||||
use_context_provider(|| Signal::new(Theme::Light));
|
||||
|
||||
rsx! {
|
||||
Stylesheet { href: STYLE }
|
||||
main {
|
||||
class: "main-container",
|
||||
|
||||
h1 { "Theme Switcher" }
|
||||
ThemeControls {}
|
||||
ThemeDisplay {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
enum Theme {
|
||||
Light,
|
||||
Dark,
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
fn stylesheet(&self) -> &'static str {
|
||||
match self {
|
||||
Theme::Light => "light-theme",
|
||||
Theme::Dark => "dark-theme",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ThemeControls() -> Element {
|
||||
let mut theme = use_theme_context();
|
||||
let current_theme = *theme.read();
|
||||
rsx! {
|
||||
div {
|
||||
class: "controls",
|
||||
button {
|
||||
class: "btn",
|
||||
onclick: move |_| theme.set(Theme::Light),
|
||||
disabled: current_theme== Theme::Light,
|
||||
"Switch to Light"
|
||||
}
|
||||
button {
|
||||
class: "btn",
|
||||
onclick: move |_| theme.set(Theme::Dark),
|
||||
disabled: current_theme == Theme::Dark,
|
||||
"Switch to Dark"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ThemeDisplay() -> Element {
|
||||
let theme = use_theme_context();
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
class: "display {theme.read().stylesheet()}",
|
||||
p { "Current theme: {theme:?}" }
|
||||
p { "Try switching themes using the buttons above!" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn use_theme_context() -> Signal<Theme> {
|
||||
try_use_context::<Signal<Theme>>()
|
||||
.expect("Theme context not found. Ensure <App> is the root component.")
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
//! This example showcases how to use the ErrorBoundary component to handle errors in your app.
|
||||
//!
|
||||
//! The ErrorBoundary component is a special component that can be used to catch panics and other errors that occur.
|
||||
//! By default, Dioxus will catch panics during rendering, async, and handlers, and bubble them up to the nearest
|
||||
//! error boundary. If no error boundary is present, it will be caught by the root error boundary and the app will
|
||||
//! render the error message as just a string.
|
||||
//!
|
||||
//! NOTE: In wasm, panics can currently not be caught by the error boundary. This is a limitation of WASM in rust.
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(|| rsx! { Router::<Route> {} });
|
||||
}
|
||||
|
||||
/// You can use an ErrorBoundary to catch errors in children and display a warning
|
||||
fn Simple() -> Element {
|
||||
rsx! {
|
||||
GoBackButton { "Home" }
|
||||
ErrorBoundary {
|
||||
handle_error: |error: ErrorContext| rsx! {
|
||||
h1 { "An error occurred" }
|
||||
pre { "{error:#?}" }
|
||||
},
|
||||
ParseNumber {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ParseNumber() -> Element {
|
||||
rsx! {
|
||||
h1 { "Error handler demo" }
|
||||
button {
|
||||
onclick: move |_| {
|
||||
// You can return a result from an event handler which lets you easily quit rendering early if something fails
|
||||
let data: i32 = "0.5".parse()?;
|
||||
|
||||
println!("parsed {data}");
|
||||
|
||||
Ok(())
|
||||
},
|
||||
"Click to throw an error"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// You can provide additional context for the Error boundary to visualize
|
||||
fn Show() -> Element {
|
||||
rsx! {
|
||||
GoBackButton { "Home" }
|
||||
div {
|
||||
ErrorBoundary {
|
||||
handle_error: |errors: ErrorContext| {
|
||||
rsx! {
|
||||
for error in errors.error() {
|
||||
// You can downcast the error to see if it's a specific type and render something specific for it
|
||||
if let Some(_error) = error.downcast_ref::<std::num::ParseIntError>() {
|
||||
div {
|
||||
background_color: "red",
|
||||
border: "black",
|
||||
border_width: "2px",
|
||||
border_radius: "5px",
|
||||
p { "Failed to parse data" }
|
||||
Link {
|
||||
to: Route::Home {},
|
||||
"Go back to the homepage"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pre {
|
||||
color: "red",
|
||||
"{error}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
ParseNumberWithShow {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ParseNumberWithShow() -> Element {
|
||||
rsx! {
|
||||
h1 { "Error handler demo" }
|
||||
button {
|
||||
onclick: move |_| {
|
||||
let request_data = "0.5";
|
||||
let data: i32 = request_data.parse()?;
|
||||
println!("parsed {data}");
|
||||
Ok(())
|
||||
},
|
||||
"Click to throw an error"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// On desktop, dioxus will catch panics in components and insert an error automatically
|
||||
fn Panic() -> Element {
|
||||
rsx! {
|
||||
GoBackButton { "Home" }
|
||||
ErrorBoundary {
|
||||
handle_error: |errors: ErrorContext| rsx! {
|
||||
h1 { "Another error occurred" }
|
||||
pre { "{errors:#?}" }
|
||||
},
|
||||
ComponentPanic {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn ComponentPanic() -> Element {
|
||||
panic!("This component panics")
|
||||
}
|
||||
|
||||
#[derive(Routable, Clone, Debug, PartialEq)]
|
||||
enum Route {
|
||||
#[route("/")]
|
||||
Home {},
|
||||
#[route("/simple")]
|
||||
Simple {},
|
||||
#[route("/panic")]
|
||||
Panic {},
|
||||
#[route("/show")]
|
||||
Show {},
|
||||
}
|
||||
|
||||
fn Home() -> Element {
|
||||
rsx! {
|
||||
ul {
|
||||
li {
|
||||
Link {
|
||||
to: Route::Simple {},
|
||||
"Simple errors"
|
||||
}
|
||||
}
|
||||
li {
|
||||
Link {
|
||||
to: Route::Panic {},
|
||||
"Capture panics"
|
||||
}
|
||||
}
|
||||
li {
|
||||
Link {
|
||||
to: Route::Show {},
|
||||
"Show errors"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//! Example: Global signals and memos
|
||||
//!
|
||||
//! This example demonstrates how to use global signals and memos to share state across your app.
|
||||
//! Global signals are simply signals that live on the root of your app and are accessible from anywhere. To access a
|
||||
//! global signal, simply use its methods like a regular signal. Calls to `read` and `write` will be forwarded to the
|
||||
//! signal at the root of your app using the `static`'s address.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
const STYLE: Asset = asset!("/examples/assets/counter.css");
|
||||
|
||||
static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
|
||||
static DOUBLED_COUNT: GlobalMemo<i32> = Memo::global(|| COUNT() * 2);
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
rsx! {
|
||||
Stylesheet { href: STYLE }
|
||||
Increment {}
|
||||
Decrement {}
|
||||
Reset {}
|
||||
Display {}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Increment() -> Element {
|
||||
rsx! {
|
||||
button { onclick: move |_| *COUNT.write() += 1, "Up high!" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Decrement() -> Element {
|
||||
rsx! {
|
||||
button { onclick: move |_| *COUNT.write() -= 1, "Down low!" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Display() -> Element {
|
||||
rsx! {
|
||||
p { "Count: {COUNT}" }
|
||||
p { "Doubled: {DOUBLED_COUNT}" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Reset() -> Element {
|
||||
// Not all write methods are available on global signals since `write` requires a mutable reference. In these cases,
|
||||
// We can simply pull out the actual signal using the signal() method.
|
||||
let mut as_signal = use_hook(|| COUNT.resolve());
|
||||
|
||||
rsx! {
|
||||
button { onclick: move |_| as_signal.set(0), "Reset" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
//! This example shows how you can chain memos together to create a tree of memoized values.
|
||||
//!
|
||||
//! Memos will also pause when their parent component pauses, so if you have a memo that depends on a signal, and the
|
||||
//! signal pauses, the memo will pause too.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut value = use_signal(|| 0);
|
||||
let mut depth = use_signal(|| 0_usize);
|
||||
let items = use_memo(move || (0..depth()).map(|f| f as _).collect::<Vec<isize>>());
|
||||
let state = use_memo(move || value() + 1);
|
||||
|
||||
println!("rendering app");
|
||||
|
||||
rsx! {
|
||||
button { onclick: move |_| value += 1, "Increment" }
|
||||
button { onclick: move |_| depth += 1, "Add depth" }
|
||||
button { onclick: move |_| depth -= 1, "Remove depth" }
|
||||
if depth() > 0 {
|
||||
Child { depth, items, state }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Child(state: Memo<isize>, items: Memo<Vec<isize>>, depth: ReadSignal<usize>) -> Element {
|
||||
// These memos don't get re-computed when early returns happen
|
||||
let state = use_memo(move || state() + 1);
|
||||
let item = use_memo(move || items()[depth() - 1]);
|
||||
let depth = use_memo(move || depth() - 1);
|
||||
|
||||
println!("rendering child: {}", depth());
|
||||
|
||||
rsx! {
|
||||
h3 { "Depth({depth})-Item({item}): {state}"}
|
||||
if depth() > 0 {
|
||||
Child { depth, state, items }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
//! Example: Reducer Pattern
|
||||
//! -----------------
|
||||
//!
|
||||
//! This example shows how to encapsulate state in dioxus components with the reducer pattern.
|
||||
//! This pattern is very useful when a single component can handle many types of input that can
|
||||
//! be represented by an enum.
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
const STYLE: Asset = asset!("/examples/assets/radio.css");
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut state = use_signal(|| PlayerState { is_playing: false });
|
||||
|
||||
rsx!(
|
||||
Stylesheet { href: STYLE }
|
||||
h1 {"Select an option"}
|
||||
|
||||
// Add some cute animations if the radio is playing!
|
||||
div { class: if state.read().is_playing { "bounce" },
|
||||
"The radio is... " {state.read().is_playing()} "!"
|
||||
}
|
||||
|
||||
button { id: "play", onclick: move |_| state.write().reduce(PlayerAction::Pause), "Pause" }
|
||||
button { id: "pause", onclick: move |_| state.write().reduce(PlayerAction::Play), "Play" }
|
||||
)
|
||||
}
|
||||
|
||||
enum PlayerAction {
|
||||
Pause,
|
||||
Play,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct PlayerState {
|
||||
is_playing: bool,
|
||||
}
|
||||
|
||||
impl PlayerState {
|
||||
fn reduce(&mut self, action: PlayerAction) {
|
||||
match action {
|
||||
PlayerAction::Pause => self.is_playing = false,
|
||||
PlayerAction::Play => self.is_playing = true,
|
||||
}
|
||||
}
|
||||
fn is_playing(&self) -> &'static str {
|
||||
match self.is_playing {
|
||||
true => "currently playing!",
|
||||
false => "not currently playing",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
//! A simple example demonstrating how to use signals to modify state from several different places.
|
||||
//!
|
||||
//! This simple example implements a counter that can be incremented, decremented, and paused. It also demonstrates
|
||||
//! that background tasks in use_futures can modify the value as well.
|
||||
//!
|
||||
//! Most signals implement Into<ReadSignal<T>>, making ReadSignal a good default type when building new
|
||||
//! library components that don't need to modify their values.
|
||||
|
||||
use async_std::task::sleep;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(app);
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut running = use_signal(|| true);
|
||||
let mut count = use_signal(|| 0);
|
||||
let mut saved_values = use_signal(|| vec![0.to_string()]);
|
||||
|
||||
// use_memo will recompute the value of the signal whenever the captured signals change
|
||||
let doubled_count = use_memo(move || count() * 2);
|
||||
|
||||
// use_effect will subscribe to any changes in the signal values it captures
|
||||
// effects will always run after first mount and then whenever the signal values change
|
||||
use_effect(move || println!("Count changed to {count}"));
|
||||
|
||||
// We can do early returns and conditional rendering which will pause all futures that haven't been polled
|
||||
if count() > 30 {
|
||||
return rsx! {
|
||||
h1 { "Count is too high!" }
|
||||
button { onclick: move |_| count.set(0), "Press to reset" }
|
||||
};
|
||||
}
|
||||
|
||||
// use_future will spawn an infinitely running future that can be started and stopped
|
||||
use_future(move || async move {
|
||||
loop {
|
||||
if running() {
|
||||
count += 1;
|
||||
}
|
||||
sleep(std::time::Duration::from_millis(400)).await;
|
||||
}
|
||||
});
|
||||
|
||||
// use_resource will spawn a future that resolves to a value
|
||||
let _slow_count = use_resource(move || async move {
|
||||
sleep(std::time::Duration::from_millis(200)).await;
|
||||
count() * 2
|
||||
});
|
||||
|
||||
rsx! {
|
||||
h1 { "High-Five counter: {count}" }
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
button { onclick: move |_| running.toggle(), "Toggle counter" }
|
||||
button { onclick: move |_| saved_values.push(count.to_string()), "Save this value" }
|
||||
button { onclick: move |_| saved_values.clear(), "Clear saved values" }
|
||||
|
||||
// We can do boolean operations on the current signal value
|
||||
if count() > 5 {
|
||||
h2 { "High five!" }
|
||||
}
|
||||
|
||||
// We can cleanly map signals with iterators
|
||||
for value in saved_values.iter() {
|
||||
h3 { "Saved value: {value}" }
|
||||
}
|
||||
|
||||
// We can also use the signal value as a slice
|
||||
if let [first, .., last] = saved_values.read().as_slice() {
|
||||
li { "First and last: {first}, {last}" }
|
||||
} else {
|
||||
"No saved values"
|
||||
}
|
||||
|
||||
// You can pass a value directly to any prop that accepts a signal
|
||||
Child { count: doubled_count() }
|
||||
Child { count: doubled_count }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Child(mut count: ReadSignal<i32>) -> Element {
|
||||
println!("rendering child with count {count}");
|
||||
|
||||
rsx! {
|
||||
h1 { "{count}" }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user