├── .gitignore ├── static └── fonts │ └── JetBrainsMono-Regular.woff2 ├── README.md ├── config.toml ├── LICENSE.md ├── templates └── index.html ├── sass └── style.scss └── content └── _index.md /.gitignore: -------------------------------------------------------------------------------- 1 | public -------------------------------------------------------------------------------- /static/fonts/JetBrainsMono-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marc2332/dioxus-cheatsheet/HEAD/static/fonts/JetBrainsMono-Regular.woff2 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🦀 Dioxus Cheatsheet 2 | 3 | Website: https://dioxus-cheatsheet.vercel.app/ 4 | 5 | ⚠️ **Work in progress** cheatsheet for [🧬 Dioxus](https://github.com/DioxusLabs/dioxus) made with [Zola](https://www.getzola.org/). 6 | 7 | Feel free to make any suggestion, corrections or whatever, **contributions** are welcome 🚪👋!! 8 | 9 | ## Development 10 | 11 | ```bash 12 | zola serve 13 | ``` 14 | 15 | MIT License -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | # The URL the site will be built for 2 | base_url = "https://dioxus-cheatsheet.vercel.app/" 3 | 4 | # Whether to automatically compile all Sass files in the sass directory 5 | compile_sass = true 6 | 7 | # Whether to build a search index to be used later on by a JavaScript library 8 | build_search_index = true 9 | 10 | [markdown] 11 | # Whether to do syntax highlighting 12 | # Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola 13 | highlight_code = true 14 | highlight_theme = "gruvbox-dark" 15 | 16 | [extra] 17 | # Put all your custom variables here 18 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Marc Espín Sanz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ section.title }} 9 | 10 | 11 | 12 | 13 | {% block title %}{{ config.title }}{% endblock title %} 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 |

{{ section.title }}

24 |

⚠️ WORK IN PROGRESS ! ⚠️

25 |

Source Code 🐙

26 | {{ section.content | safe }} 27 |
28 |
29 |
30 | 31 | 32 | -------------------------------------------------------------------------------- /sass/style.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "JetBrains Mono"; 3 | src: url("/fonts/JetBrainsMono-Regular.woff2") format("woff2"); 4 | font-weight: 500; 5 | font-style: normal; 6 | font-display: swap; 7 | } 8 | 9 | 10 | :root { 11 | background: #222; 12 | color: #ccc; 13 | } 14 | 15 | .title { 16 | text-align: center; 17 | margin-top: 60px; 18 | margin-bottom: 30px; 19 | font-size: 40px; 20 | } 21 | 22 | .subtitle { 23 | text-align: center; 24 | margin: 20px 0px; 25 | } 26 | 27 | .small-subtitle { 28 | text-align: center; 29 | margin-top: 10px; 30 | margin-bottom: 40px; 31 | } 32 | 33 | a { 34 | color: #4183c4; 35 | } 36 | 37 | .page { 38 | margin: 0 auto; 39 | width: 800px; 40 | 41 | @media screen and (max-width: 800px) { 42 | & { 43 | padding: 10px; 44 | width: 90%; 45 | } 46 | } 47 | } 48 | 49 | * { 50 | font-family: system-ui; 51 | } 52 | 53 | toc { 54 | display: flex; 55 | margin-right: auto; 56 | margin-left: 200px; 57 | width: 70%; 58 | column { 59 | width: 50%; 60 | } 61 | @media screen and (max-width: 800px) { 62 | & { 63 | display: block; 64 | margin-right: auto; 65 | margin-left: auto; 66 | width: 80%; 67 | } 68 | } 69 | } 70 | 71 | pre { 72 | padding: 15px; 73 | border-radius: 10px; 74 | overflow: auto; 75 | } 76 | 77 | code > * { 78 | font-family: 'JetBrains Mono'; 79 | } 80 | 81 | .zola-anchor { 82 | margin-left: -30px; 83 | font-size: 1.25rem; 84 | opacity: 0; 85 | } 86 | 87 | h1:hover, h2:hover, h3:hover, h4:hover { 88 | .zola-anchor { 89 | opacity: 1; 90 | } 91 | } -------------------------------------------------------------------------------- /content/_index.md: -------------------------------------------------------------------------------- 1 | +++ 2 | title = "🦀 Dioxus Library Cheatsheet" 3 | insert_anchor_links = "left" 4 | in_search_index = true 5 | +++ 6 | 7 | 8 | 9 | 10 | **Components** 11 | 12 | - [Components](#components) 13 | - [Props](#props) 14 | - [RSX](#rsx) 15 | - ⚠️ Lifecycle 16 | 17 | **State** 18 | 19 | - [Local](#local-state) 20 | - [Shared](#shared-state) 21 | - [Global](#global-state) 22 | 23 | **Hooks** 24 | 25 | - ⚠️ Rules 26 | - ⚠️ Custom 27 | 28 | 29 | 30 | 31 | 32 | **Patterns** 33 | 34 | - [Composition](#composition) 35 | - [Memoization](#memoization) 36 | 37 | **Dioxus Standard Library** 38 | 39 | - ⚠️ Color Scheme 40 | - ⚠️ Localization (i18n) 41 | - ⚠️ Channels 42 | 43 | **Renderers** 44 | 45 | - ⚠️ Web 46 | - ⚠️ Desktop 47 | - ⚠️ TUI 48 | - ⚠️ Blitz 49 | - ⚠️ Liveview 50 | - ⚠️ SSR 51 | - ⚠️ Fullstack 52 | - ⚠️ Freya 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ### Overview 61 | 62 | **Dioxus** is a declarative library for building user interfaces (UI) in Rust 🦀. It is inspired by React, so it shares many of it's concepts like **Components** and **Hooks**. Dioxus is renderer agnostic, so you can use it with any renderer that is available, for example, there are renderers for web (WASM), Desktop (Webview), TUI, Blitz (WGPU), LiveView, Freya (non-official Skia renderer), etc. 63 | 64 | ### Components 65 | 66 | **Components** are the building blocks of your application. They encapsulate contain the declared UI of your app. 67 | In Dioxus they are expressed in the form of functions. 68 | 69 | ```rust 70 | fn Component(cx: Scope) -> Element { 71 | render!( 72 | div { 73 | "Hello World" 74 | } 75 | ) 76 | } 77 | ``` 78 | 79 | The function takes a `Scope` as an argument and returns an `Element`. 80 | 81 | - **Scopes** are unique to their components. This is why you will need to pass it to **Hooks** when calling them. 82 | - **Element** is the representation of your UI that will be generated by `render!()` or `cx.render(rsx!())`. 83 | 84 | ## RSX 85 | 86 | RSX is a special syntax used to declare the UI that integrates with Rust. 87 | 88 | ```rust 89 | fn app(cx: Scope) -> Element { 90 | render!( 91 | div {} // A div element 92 | Component {} // The `CoolComponent` component 93 | ) 94 | } 95 | 96 | fn CoolComponent(cx: Scope) -> Element { 97 | let some_value = 123; 98 | 99 | render!( 100 | button { // A button element 101 | width: "100%", // An attribute 102 | onclick: |evt| println!("{evt:?}"), // An event listener 103 | p { // A nested element 104 | height: "20px", 105 | "Value is {some_value}" // A text node 106 | } 107 | } 108 | ) 109 | } 110 | ``` 111 | 112 | ### Local State 113 | 114 | Components can have local state, this is useful for storing data that is only relevant to the component itself. This can be archived by using the `use_state` or `use_ref` hooks. 115 | 116 | #### `use_state` 117 | 118 | The `use_state` hook is used to store data that will change over time. It takes an initial value and returns a `UseState` object. 119 | 120 | Every time you modify the state, the component will be re-run. 121 | 122 | ```rust 123 | fn Component(cx: Scope) -> Element { 124 | let counter = use_state(cx, || 0); 125 | 126 | // This will be printed every time the state changes 127 | println!("{counter}"); 128 | 129 | let onclick = |_| { 130 | // You can use the normal operators to update the state 131 | counter += 1; 132 | 133 | // Or you can use the set and get functions 134 | counter.set(counter.get() + 1); 135 | 136 | // You can also use the modify function 137 | counter.modify(|counter| counter + 1); 138 | 139 | // Or the with_mut function 140 | counter.modify(|counter| *counter += 1); 141 | }; 142 | 143 | render!( 144 | button { 145 | onclick: onclick, 146 | "{counter}" 147 | } 148 | ) 149 | } 150 | ``` 151 | 152 | #### `use_ref` 153 | 154 | The use_ref hook is used to store data that will change over time. It takes an initial value and returns a `UseRef` object. 155 | The difference with `use_state` is that `use_ref` can be modified without re-running the component, which can be useful when you need to hold a value across renders that is used asynchronously. 156 | 157 | ```rust 158 | fn Component(cx: Scope) -> Element { 159 | let counter = use_ref(cx, || 0); 160 | 161 | let onclick = |_| { 162 | // Silently increment the counter, this will not make the component re-run 163 | counter.write_silent() = counter.read() + 1; 164 | }; 165 | 166 | let double_and_update = |_| { 167 | // Double the counter, this will make the component re-run 168 | counter.write() = counter.read() * 2; 169 | }; 170 | 171 | render!( 172 | button { 173 | onclick: increment_silently, 174 | "Increment Silently" 175 | } 176 | p { 177 | "{counter}" 178 | } 179 | button { 180 | onclick: double_and_update, 181 | "Double and update" 182 | } 183 | ) 184 | } 185 | ``` 186 | 187 | ### Shared State 188 | 189 | Shared state is used to store data that is shared across components. This can be archived by using the `use_shared_state` and `use_shared_state_provider` hooks. 190 | 191 | #### `use_shared_state_provider` 192 | 193 | You can use `use_shared_state_provider` to share a certain value all the way down to it's children. 194 | 195 | ```rust 196 | 197 | #[derive(Debug, Clone)] 198 | enum Theme { 199 | Light, 200 | Dark 201 | } 202 | 203 | fn App(cx: Scope) -> Element { 204 | // All the children of this component will have access to this shared state 205 | // See `use_shared_state` on how to actually use it 206 | use_shared_state_provider(cx, || Theme::Dark); 207 | 208 | render!( 209 | CoolChild {} 210 | ) 211 | } 212 | ``` 213 | 214 | #### `use_shared_state` 215 | 216 | You can use `use_shared_state` to access a value shared by one of the component ancestors. 217 | 218 | ```rust 219 | 220 | #[derive(Debug, Clone)] 221 | enum Theme { 222 | Light, 223 | Dark 224 | } 225 | 226 | fn CoolChild(cx: Scope) -> Element { 227 | // Access the shared state 228 | let theme = use_shared_state::(cx); 229 | 230 | // You can read it with the read function 231 | let is_light = theme.read() == Theme::Light; 232 | 233 | let onclick = |_| { 234 | // Or modify it with the write function 235 | theme.write() = match theme.read() { 236 | Theme::Light => Theme::Dark, 237 | Theme::Dark => Theme::Light 238 | }; 239 | }; 240 | 241 | render!( 242 | button { 243 | onclick: onclick, 244 | "Click to toggle the theme: {theme:?}" 245 | } 246 | ) 247 | } 248 | ``` 249 | 250 | ### Global State 251 | 252 | ### Providers 253 | 254 | Providers are usually Hooks/Components that provide a certain value down to all it's children 255 | 256 | Example: 257 | 258 | ```rust 259 | 260 | fn App(cx: Scope) -> Element { 261 | render!( 262 | // Only Components that are desdendants of ThemeProvider will have access to the state 263 | ThemeProvider { 264 | CoolContainer { // ✅ Will have access 265 | CoolButton { // ✅ Will have access 266 | "Hello World" 267 | } 268 | } 269 | } 270 | NotCoolText { // ❌ Will not have access 271 | "This text is not cool" 272 | } 273 | ) 274 | } 275 | ``` 276 | 277 | ### Props 278 | 279 | Props are parameters for Components, but they are different to normal function arguments: 280 | 281 | - Can be optional 282 | - Can have default types 283 | - Can be memoized 284 | 285 | Props as struct: 286 | 287 | ```rust 288 | #[derive(Props)] 289 | struct CoolProps { 290 | value: i32 291 | } 292 | 293 | fn CoolValue(cx: Scope) -> Element { 294 | let value = cx.props.value; 295 | render!( 296 | p { 297 | "Value: {value}" 298 | } 299 | ) 300 | } 301 | 302 | fn Component(cx: Scope) -> Element { 303 | let value = use_state(cx, || 0); 304 | render!( 305 | button { 306 | onclick: |evt| { 307 | value += 1; 308 | }, 309 | CoolValue { 310 | value: *value.get() 311 | } 312 | } 313 | ) 314 | } 315 | ``` 316 | 317 | Inline props: 318 | 319 | ```rust 320 | #[inline_props] 321 | fn CoolValue(cx: Scope, value: i32) -> Element { 322 | render!( 323 | p { 324 | "Value: {value}" 325 | } 326 | ) 327 | } 328 | 329 | fn Component(cx: Scope) -> Element { 330 | let value = use_state(cx, || 0); 331 | render!( 332 | button { 333 | onclick: |evt| { 334 | value += 1; 335 | }, 336 | CoolValue { 337 | value: *value.get() 338 | } 339 | } 340 | ) 341 | } 342 | ``` 343 | 344 | Props with event handlers: 345 | 346 | ```rust 347 | struct CoolEvent { 348 | value: i32 349 | } 350 | 351 | #[derive(Props)] 352 | struct CoolProps<'a> { 353 | value: i32, 354 | oncoolevent: EventHandler<'a, CoolEvent> 355 | } 356 | 357 | fn CoolValue(cx: Scope) -> Element { 358 | let value = cx.props.value; 359 | render!( 360 | button { 361 | onclick: |_| cx.props.oncoolevent.call(CoolEvent { value: value + 1}), 362 | "Value: {value}" 363 | } 364 | ) 365 | } 366 | 367 | fn Component(cx: Scope) -> Element { 368 | let value = use_state(cx, || 0); 369 | render!( 370 | CoolValue { 371 | oncoolevent: |evt| { 372 | value.set(evt.value) 373 | }, 374 | value: *value.get() 375 | } 376 | ) 377 | } 378 | ``` 379 | 380 | Optional props: 381 | 382 | ```rust 383 | 384 | #[derive(Props)] 385 | struct CoolProps { 386 | #[props(optional)] 387 | value: Option, 388 | } 389 | 390 | fn CoolValue(cx: Scope) -> Element { 391 | let value = cx.props.value.unwrap_or(123); 392 | render!( 393 | p { 394 | "Value: {value}" 395 | } 396 | ) 397 | } 398 | 399 | fn Component(cx: Scope) -> Element { 400 | render!( 401 | CoolValue { } 402 | ) 403 | } 404 | ``` 405 | 406 | Optional props with default values: 407 | 408 | ```rust 409 | #[derive(Props)] 410 | struct CoolProps<'a> { 411 | #[props(optional, default = 123)] 412 | value: i32, 413 | } 414 | 415 | fn CoolValue(cx: Scope) -> Element { 416 | let value = cx.props.value; 417 | render!( 418 | p { 419 | "Value: {value}" 420 | } 421 | ) 422 | } 423 | 424 | fn Component(cx: Scope) -> Element { 425 | render!( 426 | CoolValue { } 427 | ) 428 | } 429 | ``` 430 | 431 | Auto convert prop values: 432 | 433 | ```rust 434 | #[derive(Props)] 435 | struct CoolProps<'a> { 436 | #[props(into)] 437 | text: String, 438 | } 439 | 440 | fn CoolValue(cx: Scope) -> Element { 441 | let text = cx.props.text; 442 | render!( 443 | p { 444 | "text: {text}" 445 | } 446 | ) 447 | } 448 | 449 | fn Component(cx: Scope) -> Element { 450 | render!( 451 | CoolValue { 452 | text: "Hello World" 453 | } 454 | ) 455 | } 456 | ``` 457 | 458 | ### Hooks 459 | 460 | Hooks are functions that allow you to do certain things in your components. They are usually prefixed with `use_`. Because they are simple functions you can create your own hooks: 461 | 462 | ```rust 463 | #[derive(Clone)] 464 | struct UseCounter { 465 | state: UseState 466 | } 467 | 468 | impl UseCounter { 469 | fn increment(&self) { 470 | self.state.set(self.state.get() + 1); 471 | } 472 | 473 | fn value(&self) -> i32 { 474 | *self.state.get() 475 | } 476 | } 477 | 478 | fn use_counter(cx: ScopeState) -> UseCounter { 479 | let state = use_state(cx, || 0); 480 | UseCounter { state } 481 | } 482 | 483 | fn Component(cx: Scope) -> Element { 484 | let counter = use_counter(); 485 | 486 | render!( 487 | button { 488 | onclick: |_| counter.increment(), 489 | "{counter.value()}" 490 | } 491 | ) 492 | } 493 | ``` 494 | 495 | ### Composition 496 | 497 | Composition is the ability to combine multiple components to form a connected UI flow. Because you use components directly it becomes easier to create more complex scenarios. 498 | 499 | Without composition 🥲: 500 | 501 | ```rust 502 | struct CoolOption(String, bool); 503 | 504 | fn app() -> Element { 505 | render!( 506 | CoolDropdown { 507 | options: vec![ 508 | CoolOption("Option 1".to_string(), false), 509 | CoolOption("Option 2".to_string(), true), 510 | CoolOption("Option 3".to_string()) 511 | ] 512 | } 513 | ) 514 | } 515 | ``` 516 | 517 | With composition 😎: 518 | 519 | ```rust 520 | fn app() -> Element { 521 | render!( 522 | CoolDropdown { 523 | CoolDropdownOption { 524 | big: true, 525 | "Option 1" 526 | } 527 | CoolDropdownOption { 528 | "Option 2" 529 | } 530 | CoolDropdownOption { 531 | "Option 3" 532 | } 533 | } 534 | ) 535 | } 536 | ``` 537 | 538 | ### Memoization 539 | 540 | Memoization (or caching) is used to avoid re-running components unnecessarily. 541 | 542 | Without memoization 🥲: 543 | 544 | ```rust 545 | 546 | #[inline_props] 547 | fn CoolComponent<'a>(cx: Scope<'a>, name: &'a str) -> Element<'a> { 548 | render!( 549 | rect { 550 | "Hello, {name}!" 551 | } 552 | ) 553 | } 554 | 555 | fn app(cx: Scope) -> Element { 556 | 557 | // `CoolComponent` will re-run whenever the component `app` re-runs, 558 | // which is always as it's not an owned value. 559 | 560 | render!( 561 | CoolComponent { 562 | name: "World" 563 | } 564 | ) 565 | } 566 | ``` 567 | 568 | With memoization 😎: 569 | 570 | ```rust 571 | 572 | #[inline_props] 573 | fn CoolComponent(cx: Scope, name: String) -> Element { 574 | render!( 575 | rect { 576 | "Hello, {name}!" 577 | } 578 | ) 579 | } 580 | 581 | fn app(cx: Scope) -> Element { 582 | 583 | // Even when this component `app` re-runs multiple times, 584 | // the `CoolComponent` will only run once and re-run when the `name` prop changes, 585 | // which is essentially never as it's harcoded to "World" and is an owned value. 586 | 587 | render!( 588 | CoolComponent { 589 | name: "World".to_string() 590 | } 591 | ) 592 | } 593 | ``` 594 | --------------------------------------------------------------------------------