From e0e32195b25ac2bdef8dc9555a7f9b76b2c0abff Mon Sep 17 00:00:00 2001 From: jkelleyrtp Date: Tue, 15 Aug 2023 20:57:00 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20DioxusLa?= =?UTF-8?q?bs/dioxus@a2df9c2e8960051f609e00a1599a87da471dd730=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/nightly/guide/en/contributing/walkthrough_readme.html | 3 ++- docs/nightly/guide/en/print.html | 3 ++- docs/nightly/guide/en/searchindex.js | 2 +- docs/nightly/guide/en/searchindex.json | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/nightly/guide/en/contributing/walkthrough_readme.html b/docs/nightly/guide/en/contributing/walkthrough_readme.html index caad21c47..5c104061a 100644 --- a/docs/nightly/guide/en/contributing/walkthrough_readme.html +++ b/docs/nightly/guide/en/contributing/walkthrough_readme.html @@ -275,7 +275,8 @@

The rsx! MacroThe rsx! Macro Element { let mut count = use_state(cx, || 0); cx.render(rsx!( h1 { \"High-Five counter: {count}\" } button { onclick: move |_| count += 1, \"Up high!\" } button { onclick: move |_| count -= 1, \"Down low!\" } ))\n} Dioxus is heavily inspired by React. If you know React, getting started with Dioxus will be a breeze. This guide assumes you already know some Rust ! If not, we recommend reading the book to learn Rust first.","breadcrumbs":"Introduction » Introduction","id":"0","title":"Introduction"},"1":{"body":"Desktop apps running natively (no Electron!) in less than 10 lines of code. Incredibly ergonomic and powerful state management. Comprehensive inline documentation – hover and guides for all HTML elements, listeners, and events. Extremely memory efficient – 0 global allocations for steady-state components. Multi-channel asynchronous scheduler for first-class async support. And more! Read the full release post . Multiplatform Dioxus is a portable toolkit, meaning the Core implementation can run anywhere with no platform-dependent linking. Unlike many other Rust frontend toolkits, Dioxus is not intrinsically linked to WebSys. In fact, every element and event listener can be swapped out at compile time. By default, Dioxus ships with the html feature enabled, but this can be disabled depending on your target renderer. Right now, we have several 1st-party renderers: WebSys (for WASM): Great support Tao/Tokio (for Desktop apps): Good support Tao/Tokio (for Mobile apps): Poor support SSR (for generating static markup) TUI/Rink (for terminal-based apps): Experimental","breadcrumbs":"Introduction » Features","id":"1","title":"Features"},"10":{"body":"Create a new crate: cargo new --bin demo\ncd demo Add Dioxus and the desktop renderer as dependencies (this will edit your Cargo.toml): cargo add dioxus\ncargo add dioxus-desktop Edit your main.rs: #![allow(non_snake_case)]\n// import the prelude to get access to the `rsx!` macro and the `Scope` and `Element` types\nuse dioxus::prelude::*; fn main() { // launch the dioxus app in a webview dioxus_desktop::launch(App);\n} // define a component that renders a div with the text \"Hello, world!\"\nfn App(cx: Scope) -> Element { cx.render(rsx! { div { \"Hello, world!\" } })\n}","breadcrumbs":"Getting Started » Desktop » Creating a Project","id":"10","title":"Creating a Project"},"100":{"body":"If you're dealing with a handful of components with minimal nesting, you can just pass the error handle into child components. fn Commandline(cx: Scope) -> Element { let error = use_state(cx, || None); if let Some(error) = **error { return cx.render(rsx!{ \"An error occured\" }); } cx.render(rsx!{ Child { error: error.clone() } Child { error: error.clone() } Child { error: error.clone() } Child { error: error.clone() } })\n} Much like before, our child components can manually set the error during their own actions. The advantage to this pattern is that we can easily isolate error states to a few components at a time, making our app more predictable and robust.","breadcrumbs":"Best Practices » Error Handling » Passing error states through components","id":"100","title":"Passing error states through components"},"101":{"body":"A strategy for handling cascaded errors in larger apps is through signaling an error using global state. This particular pattern involves creating an \"error\" context, and then setting it wherever relevant. This particular method is not as \"sophisticated\" as React's error boundary, but it is more fitting for Rust. To get started, consider using a built-in hook like use_context and use_context_provider or Fermi. Of course, it's pretty easy to roll your own hook too. At the \"top\" of our architecture, we're going to want to explicitly declare a value that could be an error. enum InputError { None, TooLong, TooShort,\n} static INPUT_ERROR: Atom = Atom(|_| InputError::None); Then, in our top level component, we want to explicitly handle the possible error state for this part of the tree. fn TopLevel(cx: Scope) -> Element { let error = use_read(cx, &INPUT_ERROR); match error { TooLong => return cx.render(rsx!{ \"FAILED: Too long!\" }), TooShort => return cx.render(rsx!{ \"FAILED: Too Short!\" }), _ => {} }\n} Now, whenever a downstream component has an error in its actions, it can simply just set its own error state: fn Commandline(cx: Scope) -> Element { let set_error = use_set(cx, &INPUT_ERROR); cx.render(rsx!{ input { oninput: move |evt| { if evt.value.len() > 20 { set_error(InputError::TooLong); } } } })\n} This approach to error handling is best in apps that have \"well defined\" error states. Consider using a crate like thiserror or anyhow to simplify the generation of the error types. This pattern is widely popular in many contexts and is particularly helpful whenever your code generates a non-recoverable error. You can gracefully capture these \"global\" error states without panicking or mucking up state.","breadcrumbs":"Best Practices » Error Handling » Going global","id":"101","title":"Going global"},"102":{"body":"This example shows what not to do and provides a reason why a given pattern is considered an \"AntiPattern\". Most anti-patterns are considered wrong for performance or code re-usability reasons.","breadcrumbs":"Best Practices » Antipatterns » Antipatterns","id":"102","title":"Antipatterns"},"103":{"body":"Fragments don't mount a physical element to the DOM immediately, so Dioxus must recurse into its children to find a physical DOM node. This process is called \"normalization\". This means that deeply nested fragments make Dioxus perform unnecessary work. Prefer one or two levels of fragments / nested components until presenting a true DOM element. Only Component and Fragment nodes are susceptible to this issue. Dioxus mitigates this with components by providing an API for registering shared state without the Context Provider pattern. // ❌ Don't unnecessarily nest fragments let _ = cx.render(rsx!( Fragment { Fragment { Fragment { Fragment { Fragment { div { \"Finally have a real node!\" } } } } } } )); // ✅ Render shallow structures cx.render(rsx!( div { \"Finally have a real node!\" } ))","breadcrumbs":"Best Practices » Antipatterns » Unnecessarily Nested Fragments","id":"103","title":"Unnecessarily Nested Fragments"},"104":{"body":"As described in the dynamic rendering chapter , list items must have unique keys that are associated with the same items across renders. This helps Dioxus associate state with the contained components and ensures good diffing performance. Do not omit keys, unless you know that the list will never change. let data: &HashMap<_, _> = &cx.props.data; // ❌ No keys cx.render(rsx! { ul { data.values().map(|value| rsx!( li { \"List item: {value}\" } )) } }); // ❌ Using index as keys cx.render(rsx! { ul { cx.props.data.values().enumerate().map(|(index, value)| rsx!( li { key: \"{index}\", \"List item: {value}\" } )) } }); // ✅ Using unique IDs as keys: cx.render(rsx! { ul { cx.props.data.iter().map(|(key, value)| rsx!( li { key: \"{key}\", \"List item: {value}\" } )) } })","breadcrumbs":"Best Practices » Antipatterns » Incorrect Iterator Keys","id":"104","title":"Incorrect Iterator Keys"},"105":{"body":"While it is technically acceptable to have a Mutex or a RwLock in the props, they will be difficult to use. Suppose you have a struct User containing the field username: String. If you pass a Mutex prop to a UserComponent component, that component may wish to pass the username as a &str prop to a child component. However, it cannot pass that borrowed field down, since it only would live as long as the Mutex's lock, which belongs to the UserComponent function. Therefore, the component will be forced to clone the username field.","breadcrumbs":"Best Practices » Antipatterns » Avoid Interior Mutability in Props","id":"105","title":"Avoid Interior Mutability in Props"},"106":{"body":"Every time you update the state, Dioxus needs to re-render the component – this is inefficient! Consider refactoring your code to avoid this. Also, if you unconditionally update the state during render, it will be re-rendered in an infinite loop.","breadcrumbs":"Best Practices » Antipatterns » Avoid Updating State During Render","id":"106","title":"Avoid Updating State During Render"},"107":{"body":"","breadcrumbs":"Publishing » Publishing","id":"107","title":"Publishing"},"108":{"body":"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.","breadcrumbs":"Publishing » Desktop » Publishing","id":"108","title":"Publishing"},"109":{"body":"The first thing we'll do is install 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","breadcrumbs":"Publishing » Desktop » Install cargo-bundle","id":"109","title":"Install cargo-bundle"},"11":{"body":"Build single-page applications that run in the browser with Dioxus. To run on the Web, your app must be compiled to WebAssembly and depend on the dioxus and dioxus-web crates. A build of Dioxus for the web will be roughly equivalent to the size of a React build (70kb vs 65kb) but it will load significantly faster because WebAssembly can be compiled as it is streamed . Examples: TodoMVC ECommerce TodoMVC example Note: Because of the limitations of Wasm, not every crate will work with your web apps, so you'll need to make sure that your crates work without native system calls (timers, IO, etc).","breadcrumbs":"Getting Started » Web » Web","id":"11","title":"Web"},"110":{"body":"To get a project setup for bundling, we need to add some flags to our Cargo.toml file. [package]\nname = \"example\"\n# ...other fields... [package.metadata.bundle]\nname = \"DogSearch\"\nidentifier = \"com.dogs.dogsearch\"\nversion = \"1.0.0\"\ncopyright = \"Copyright (c) Jane Doe 2016. All rights reserved.\"\ncategory = \"Developer Tool\"\nshort_description = \"Easily search for Dog photos\"\nlong_description = \"\"\"\nThis app makes it quick and easy to browse photos of dogs from over 200 bree\n\"\"\"","breadcrumbs":"Publishing » Desktop » Setting up your project","id":"110","title":"Setting up your project"},"111":{"body":"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//. For example, a macOS app would look like this: Published App 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.","breadcrumbs":"Publishing » Desktop » Building","id":"111","title":"Building"},"112":{"body":"To build our app and publish it to Github: Make sure GitHub Pages is set up for your repo Build your app with trunk build --release (include --public-url to update asset prefixes if using a project site) Move your generated HTML/CSS/JS/Wasm from dist into the folder configured for Github Pages Add and commit with git Push to GitHub","breadcrumbs":"Publishing » Web » Publishing with Github Pages","id":"112","title":"Publishing with Github Pages"},"113":{"body":"So far you have learned about three different approaches to target the web with Dioxus: Client-side rendering with dioxus-web Server-side rendering with dioxus-liveview Server-side static HTML generation with dioxus-ssr","breadcrumbs":"Fullstack » Fullstack development","id":"113","title":"Fullstack development"},"114":{"body":"Each approach has its tradeoffs: Client-side rendering With Client side rendering, you send the entire content of your application to the client, and then the client generates all of the HTML of the page dynamically. This means that the page will be blank until the JavaScript bundle has loaded and the application has initialized. This can result in slower first render times and makes the page less SEO-friendly . SEO stands for Search Engine Optimization. It refers to the practice of making your website more likely to appear in search engine results. Search engines like Google and Bing use web crawlers to index the content of websites. Most of these crawlers are not able to run JavaScript, so they will not be able to index the content of your page if it is rendered client-side. Client-side rendered applications need to use weakly typed requests to communicate with the server Client-side rendering is a good starting point for most applications. It is well supported and makes it easy to communicate with the client/browser APIs Liveview Liveview rendering communicates with the server over a WebSocket connection. It essentially moves all of the work that Client-side rendering does to the server. This makes it easy to communicate with the server, but more difficult to communicate with the client/browser APIS . Each interaction also requires a message to be sent to the server and back which can cause issues with latency . Because Liveview uses a websocket to render, the page will be blank until the WebSocket connection has been established and the first renderer has been sent form the websocket. Just like with client side rendering, this can make the page less SEO-friendly . Because the page is rendered on the server and the page is sent to the client piece by piece, you never need to send the entire application to the client. The initial load time can be faster than client-side rendering with large applications because Liveview only needs to send a constant small websocket script regardless of the size of the application. Liveview is a good fit for applications that already need to communicate with the server frequently (like real time collaborative apps), but don't need to communicate with as many client/browser APIs Server-side rendering Server-side rendering generates all of the HTML of the page on the server before the page is sent to the client. This means that the page will be fully rendered when it is sent to the client. This results in a faster first render time and makes the page more SEO-friendly. However, it only works for static pages . Server-side rendering is not a good fit for purely static sites like a blog","breadcrumbs":"Fullstack » Summary of Existing Approaches","id":"114","title":"Summary of Existing Approaches"},"115":{"body":"Each of these approaches has its tradeoffs. What if we could combine the best parts of each approach? Fast initial render time like SSR Works well with SEO like SSR Type safe easy communication with the server like Liveview Access to the client/browser APIs like Client-side rendering Fast interactivity like Client-side rendering We can achieve this by rendering the initial page on the server (SSR) and then taking over rendering on the client (Client-side rendering). Taking over rendering on the client is called hydration . Finally, we can use server functions to communicate with the server in a type-safe way. This approach uses both the dioxus-web and dioxus-ssr crates. To integrate those two packages and axum, warp, or salvo, Dioxus provides the dioxus-fullstack crate.","breadcrumbs":"Fullstack » A New Approach","id":"115","title":"A New Approach"},"116":{"body":"This guide assumes you read the Web guide and installed the Dioxus-cli","breadcrumbs":"Fullstack » Getting Started » Getting Started","id":"116","title":"Getting Started"},"117":{"body":"For this guide, we're going to show how to use Dioxus with Axum , but dioxus-fullstack also integrates with the Warp and Salvo web frameworks. Make sure you have Rust and Cargo installed, and then create a new project: cargo new --bin demo\ncd demo Add dioxus and dioxus-fullstack as dependencies: cargo add dioxus\ncargo add dioxus-fullstack --features axum, ssr Next, add all the Axum dependencies. This will be different if you're using a different Web Framework cargo add tokio --features full\ncargo add axum Your dependencies should look roughly like this: [dependencies]\naxum = \"*\"\ndioxus = { version = \"*\" }\ndioxus-fullstack = { version = \"*\", features = [\"axum\", \"ssr\"] }\ntokio = { version = \"*\", features = [\"full\"] } Now, set up your Axum app to serve the Dioxus app. #![allow(non_snake_case, unused)]\nuse dioxus::prelude::*; #[tokio::main]\nasync fn main() { #[cfg(feature = \"ssr\")] { use dioxus_fullstack::prelude::*; let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080)); axum::Server::bind(&addr) .serve( axum::Router::new() .serve_dioxus_application(\"\", ServeConfigBuilder::new(app, ())) .into_make_service(), ) .await .unwrap(); }\n} fn app(cx: Scope) -> Element { let mut count = use_state(cx, || 0); cx.render(rsx! { h1 { \"High-Five counter: {count}\" } button { onclick: move |_| count += 1, \"Up high!\" } button { onclick: move |_| count -= 1, \"Down low!\" } })\n} Now, run your app with cargo run and open http://localhost:8080 in your browser. You should see a server-side rendered page with a counter.","breadcrumbs":"Fullstack » Getting Started » Setup","id":"117","title":"Setup"},"118":{"body":"Right now, the page is static. We can't interact with the buttons. To fix this, we can hydrate the page with dioxus-web. First, modify your Cargo.toml to include two features, one for the server called ssr, and one for the client called web. [dependencies]\n# Common dependancies\ndioxus = { version = \"*\" }\ndioxus-fullstack = { version = \"*\" } # Web dependancies\ndioxus-web = { version = \"*\", features=[\"hydrate\"], optional = true } # Server dependancies\naxum = { version = \"0.6.12\", optional = true }\ntokio = { version = \"1.27.0\", features = [\"full\"], optional = true } [features]\ndefault = []\nssr = [\"axum\", \"tokio\", \"dioxus-fullstack/axum\"]\nweb = [\"dioxus-web\"] Next, we need to modify our main.rs to use either hydrate on the client or render on the server depending on the active features. #![allow(non_snake_case, unused)]\nuse dioxus::prelude::*; fn main() { #[cfg(feature = \"web\")] dioxus_web::launch_cfg(app, dioxus_web::Config::new().hydrate(true)); #[cfg(feature = \"ssr\")] { use dioxus_fullstack::prelude::*; tokio::runtime::Runtime::new() .unwrap() .block_on(async move { let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080)); axum::Server::bind(&addr) .serve( axum::Router::new() .serve_dioxus_application(\"\", ServeConfigBuilder::new(app, ())) .into_make_service(), ) .await .unwrap(); }); }\n} fn app(cx: Scope) -> Element { let mut count = use_state(cx, || 0); cx.render(rsx! { h1 { \"High-Five counter: {count}\" } button { onclick: move |_| count += 1, \"Up high!\" } button { onclick: move |_| count -= 1, \"Down low!\" } })\n} Now, build your client-side bundle with dx build --features web and run your server with cargo run --features ssr. You should see the same page as before, but now you can interact with the buttons!","breadcrumbs":"Fullstack » Getting Started » Hydration","id":"118","title":"Hydration"},"119":{"body":"Let's make the initial count of the counter dynamic based on the current page. Modifying the server To do this, we must remove the serve_dioxus_application and replace it with a custom implementation of its four key functions: Serve static WASM and JS files with serve_static_assets Register server functions with register_server_fns (more information on server functions later) Connect to the hot reload server with connect_hot_reload A custom route that uses SSRState to server-side render the application Modifying the client The only thing we need to change on the client is the props. dioxus-fullstack will automatically serialize the props it uses to server render the app and send them to the client. In the client section of main.rs, we need to add get_root_props_from_document to deserialize the props before we hydrate the app. #![allow(non_snake_case, unused)]\nuse dioxus::prelude::*;\nuse dioxus_fullstack::prelude::*; fn main() { #[cfg(feature = \"web\")] dioxus_web::launch_with_props( app, // Get the root props from the document get_root_props_from_document().unwrap_or_default(), dioxus_web::Config::new().hydrate(true), ); #[cfg(feature = \"ssr\")] { use axum::extract::Path; use axum::extract::State; use axum::routing::get; tokio::runtime::Runtime::new() .unwrap() .block_on(async move { let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080)); axum::Server::bind(&addr) .serve( axum::Router::new() // Serve the dist folder with the static javascript and WASM files created by the dixous CLI .serve_static_assets(\"./dist\") // Register server functions .register_server_fns(\"\") // Connect to the hot reload server in debug mode .connect_hot_reload() // Render the application. This will serialize the root props (the intial count) into the HTML .route( \"/\", get(move | State(ssr_state): State| async move { axum::body::Full::from( ssr_state.render( &ServeConfigBuilder::new( app, 0, ) .build(), ) )}), ) // Render the application with a different intial count .route( \"/:initial_count\", get(move |Path(intial_count): Path, State(ssr_state): State| async move { axum::body::Full::from( ssr_state.render( &ServeConfigBuilder::new( app, intial_count, ) .build(), ) )}), ) .with_state(SSRState::default()) .into_make_service(), ) .await .unwrap(); }); }\n} fn app(cx: Scope) -> Element { let mut count = use_state(cx, || *cx.props); cx.render(rsx! { h1 { \"High-Five counter: {count}\" } button { onclick: move |_| count += 1, \"Up high!\" } button { onclick: move |_| count -= 1, \"Down low!\" } })\n} Now, build your client-side bundle with dx build --features web and run your server with cargo run --features ssr. Navigate to http://localhost:8080/1 and you should see the counter start at 1. Navigate to http://localhost:8080/2 and you should see the counter start at 2.","breadcrumbs":"Fullstack » Getting Started » Sycronizing props between the server and client","id":"119","title":"Sycronizing props between the server and client"},"12":{"body":"The Web is the best-supported target platform for Dioxus. Because your app will be compiled to WASM you have access to browser APIs through wasm-bindgen . Dioxus provides hydration to resume apps that are rendered on the server. See the fullstack getting started guide for more information.","breadcrumbs":"Getting Started » Web » Support","id":"12","title":"Support"},"120":{"body":"dixous-server provides server functions that allow you to call an automatically generated API on the server from the client as if it were a local function. To make a server function, simply add the #[server(YourUniqueType)] attribute to a function. The function must: Be an async function Have arguments and a return type that both implement serialize and deserialize (with serde ). Return a Result with an error type of ServerFnError You must call register on the type you passed into the server macro in your main function before starting your server to tell Dioxus about the server function. Let's continue building on the app we made in the getting started guide. We will add a server function to our app that allows us to double the count on the server. First, add serde as a dependency: cargo add serde Next, add the server function to your main.rs: #![allow(non_snake_case, unused)]\nuse dioxus::prelude::*;\nuse dioxus_fullstack::prelude::*; fn main() { #[cfg(feature = \"web\")] dioxus_web::launch_with_props( app, // Get the root props from the document get_root_props_from_document().unwrap_or_default(), dioxus_web::Config::new().hydrate(true), ); #[cfg(feature = \"ssr\")] { use axum::extract::Path; use axum::extract::State; use axum::routing::get; tokio::runtime::Runtime::new() .unwrap() .block_on(async move { let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080)); axum::Server::bind(&addr) .serve( axum::Router::new() // Serve the dist folder with the static javascript and WASM files created by the dixous CLI .serve_static_assets(\"./dist\") // Register server functions .register_server_fns(\"\") // Connect to the hot reload server in debug mode .connect_hot_reload() // Render the application. This will serialize the root props (the intial count) into the HTML .route( \"/\", get(move |Path(intial_count): Path, State(ssr_state): State| async move { axum::body::Full::from( ssr_state.render( &ServeConfigBuilder::new( app, intial_count, ) .build(), ) )}), ) // Render the application with a different intial count .route( \"/:initial_count\", get(move |Path(intial_count): Path, State(ssr_state): State| async move { axum::body::Full::from( ssr_state.render( &ServeConfigBuilder::new( app, intial_count, ) .build(), ) )}), ) .with_state(SSRState::default()) .into_make_service(), ) .await .unwrap(); }); }\n} fn app(cx: Scope) -> Element { let mut count = use_state(cx, || *cx.props); 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 |_| { to_owned![count]; async move { // Call the server function just like a local async function if let Ok(new_count) = double_server(*count.current()).await { count.set(new_count); } } }, \"Double\" } })\n} #[server(DoubleServer)]\nasync fn double_server(number: usize) -> Result { // Perform some expensive computation or access a database on the server tokio::time::sleep(std::time::Duration::from_secs(1)).await; let result = number * 2; println!(\"server calculated {result}\"); Ok(result)\n} Now, build your client-side bundle with dx build --features web and run your server with cargo run --features ssr. You should see a new button that multiplies the count by 2.","breadcrumbs":"Fullstack » Communicating with the Server » Communicating with the server","id":"120","title":"Communicating with the server"},"121":{"body":"That's it! You've created a full-stack Dioxus app. You can find more examples of full-stack apps and information about how to integrate with other frameworks and desktop renderers in the dioxus-fullstack examples directory .","breadcrumbs":"Fullstack » Communicating with the Server » Conclusion","id":"121","title":"Conclusion"},"122":{"body":"Dioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer. Great news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require processing Mutations and sending UserEvents.","breadcrumbs":"Custom Renderer » Custom Renderer","id":"122","title":"Custom Renderer"},"123":{"body":"Implementing the renderer is fairly straightforward. The renderer needs to: Handle the stream of edits generated by updates to the virtual DOM Register listeners and pass events into the virtual DOM's event system Essentially, your renderer needs to process edits and generate events to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen. Internally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves. For reference, check out the javascript interpreter or tui renderer as a starting point for your custom renderer.","breadcrumbs":"Custom Renderer » The specifics:","id":"123","title":"The specifics:"},"124":{"body":"Dioxus is built around the concept of Templates . Templates describe a UI tree known at compile time with dynamic parts filled at runtime. This is useful internally to make skip diffing static nodes, but it is also useful for the renderer to reuse parts of the UI tree. This can be useful for things like a list of items. Each item could contain some static parts and some dynamic parts. The renderer can use the template to create a static part of the UI once, clone it for each element in the list, and then fill in the dynamic parts.","breadcrumbs":"Custom Renderer » Templates","id":"124","title":"Templates"},"125":{"body":"The Mutation type is a serialized enum that represents an operation that should be applied to update the UI. The variants roughly follow this set: enum Mutation { AppendChildren, AssignId, CreatePlaceholder, CreateTextNode, HydrateText, LoadTemplate, ReplaceWith, ReplacePlaceholder, InsertAfter, InsertBefore, SetAttribute, SetText, NewEventListener, RemoveEventListener, Remove, PushRoot,\n} The Dioxus diffing mechanism operates as a stack machine where the LoadTemplate , CreatePlaceholder , and CreateTextNode mutations pushes a new \"real\" DOM node onto the stack and AppendChildren , InsertAfter , InsertBefore , ReplacePlaceholder , and ReplaceWith all remove nodes from the stack.","breadcrumbs":"Custom Renderer » Mutations","id":"125","title":"Mutations"},"126":{"body":"Dioxus saves and loads elements with IDs. Inside the VirtualDOM, this is just tracked as as a u64. Whenever a CreateElement edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when id is used in a mutation. Dioxus reclaims the IDs of elements when removed. To stay in sync with Dioxus you can use a sparse Vec (Vec