From a3ef5c67fa49c839c2373bb3248720b53b83bead Mon Sep 17 00:00:00 2001 From: marc2332 Date: Sat, 21 Sep 2024 13:01:14 +0200 Subject: [PATCH] more docs --- book/src/SUMMARY.md | 2 +- book/src/learn/animation.md | 3 + book/src/learn/components.md | 31 ++-- book/src/learn/hooks.md | 7 +- book/src/learn/i18n.md | 74 ++++++++- book/src/learn/lifecycle.md | 73 +++++++++ book/src/learn/state_management.md | 2 +- book/src/learn/state_management/context.md | 153 ++++++++++++++++++ .../src/learn/state_management/third_party.md | 11 +- book/src/learn/ui.md | 39 +++-- examples/counter.rs | 4 + 11 files changed, 366 insertions(+), 33 deletions(-) create mode 100644 book/src/learn/lifecycle.md diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index b01b95327..56648cab1 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -15,7 +15,7 @@ - [State Management](./learn/state_management.md) - [Signals](./learn/state_management/signals.md) - [Global Signals](./learn/state_management/global_signals.md) - - [Dioxus Hooks](./learn/dioxus_hooks.md) + - [Lifecycle](./learn/lifecycle.md) - [Context](./learn/state_management/context.md) - [Third Party](./learn/state_management/third_party.md) - [Async](./learn/async.md) diff --git a/book/src/learn/animation.md b/book/src/learn/animation.md index 6568ac4f5..7062f966a 100644 --- a/book/src/learn/animation.md +++ b/book/src/learn/animation.md @@ -13,6 +13,7 @@ fn app() -> Element { ctx.with(AnimNum::new(0., 100.).time(200)) }); + // Retrive the animation you returned in `use_animation` let width = animation.get().read().as_f32(); rsx!(rect { @@ -21,6 +22,7 @@ fn app() -> Element { background: "blue" }) } +``` Advanced Example: @@ -46,6 +48,7 @@ fn app() -> Element { ) }); + // Retrieve the animations you returned inside `use_animation` let (width, color) = animation.get(); rsx!(rect { diff --git a/book/src/learn/components.md b/book/src/learn/components.md index de26e6d03..852991143 100644 --- a/book/src/learn/components.md +++ b/book/src/learn/components.md @@ -3,9 +3,13 @@ Freya apps will usually be composed of different components. Components are defined in the form functions that might receive some input as **Props** and return the UI as **Element**. +> You can learn more about how the UI is defined in the [UI](./ui.md) chapter. + This is how a simple root component looks like: ```rs +// Usually, the root component of a Freya app is named `app`, +// but it is not a requirement fn app() -> Element { rsx!( label { @@ -16,29 +20,29 @@ fn app() -> Element { ``` This is obviously fine, but the moment our app grows in size and complexity we might want to split -things out in order to maintain a certain level of modularity and reusability. We can do this with components. - -For example: +things out in order to maintain a certain level of modularity and reusability. We can do this by spliting the UI in different components -This is how a simple root component looks like: +For example, lets create a reusable component: ```rs -// Root component that gets passed to the `launch` function fn app() -> Element { rsx!( + // By declaring this element using `TextLabel` + // we are creating an instance of that component TextLabel { "Number 1" } label { "Number 2" } + // Another instance of the same component TextLabel { "Number 3" } ) } -// Reusable component +// Reusable component that we might call as many times we want #[component] fn TextLabel(text: String) -> Element { rsx!( @@ -49,7 +53,7 @@ fn TextLabel(text: String) -> Element { } ``` -Notice how we anotate our `TextLabel` component with the macro `#[component]`, this will transform every argument of the function (just `text: String` in this case) to a component prop, so we can later use the component in a declarative way in the RSX. +Notice how we anotate our `TextLabel` component with the macro `#[component]`, this will transform every argument of the function (just `text: String` in this case) to a component prop, so we can later use the component prop in a declarative way in the RSX. For more complex components you might want to put the props in an external struct intead of using the `#[components]` macro: @@ -70,19 +74,26 @@ fn TextLabel(TextLabelProps { text }: TextLabelProps) -> Element { ## Renders -Components renders are just when a component function runs, which might be because it is subscribed to a signal and that signal got mutated. Consider this simple component: +Components renders are just when a component function runs, which might be because it is subscribed to a signal and that signal got mutated, or because its props changed. + +> Even though the naming might give you the impression that it means the app will effectively rerender again, it has nothing to do with it, in fact, a component might render a thousand times but it it doesn't generate a new UI Freya will not rerender it. + +Consider this simple component: ```rs #[component] fn CoolComp() -> Element { let mut count = use_signal(|| 0); - // 1 run of this function = 1 render of this component - // So, everytime the `count` signal is mutated, the component rerenders. + // 1 run of this function means 1 render of this component + // So, everytime the `count` signal is mutated, the component rerenders/is recalled. rsx!( label { + // Update the signal value onclick: move |_| count += 1, + + // By embedding the count in this text the component is subscried to any change in the `count` siganal "Increase {count}" } ) diff --git a/book/src/learn/hooks.md b/book/src/learn/hooks.md index f72cd7623..071d825c1 100644 --- a/book/src/learn/hooks.md +++ b/book/src/learn/hooks.md @@ -1,18 +1,21 @@ # Hooks -Hooks are special functions to be used inside of Components. They are usually prefixed with `use`, e.g `use_signal`, `use_effect` +Hooks are special functions to be used inside of Components that lets you handle different things like the state or lifecycle of your component. They are usually prefixed with `use`, e.g `use_signal`, `use_effect`, `use_memo`, etc. # Rules of Hooks +Even though hooks appear to be normal functions they are in fact special so you cannot just call them however you want. + ## 1. They cannot be called conditionally -You cannot do the following because hooks need to maintain their order. +You cannot do the following because hooks need to maintain their order. So, if one component is calling 3 different hooks in one render, and then in another render if just calls 2, it would be breaking this rule. ❌: ```rs #[component] fn MyComponent(value: bool) -> Element { let is_enabled = if value { + // This should be moved out of the conditional use_signal(|| value) } else { true diff --git a/book/src/learn/i18n.md b/book/src/learn/i18n.md index 678b31043..40fb084b4 100644 --- a/book/src/learn/i18n.md +++ b/book/src/learn/i18n.md @@ -2,4 +2,76 @@ You may add i18n (localization) support to your Freya app by using the [**dioxus-i18n**](https://github.com/dioxus-community/dioxus-i18n) crate. -[Code Example](https://github.com/dioxus-community/dioxus-i18n/blob/main/examples/freya.rs). \ No newline at end of file +```fluent +# en-US.ftl + +hello_world = Hello, World! +hello = Hello, {$name}! +``` + + +```fluent +# es-ES.ftl + +hello_world = Hola, Mundo! +hello = Hola, {$name}! +``` + +```rs +// main.rs + +fn main() { + launch(app); +} + +#[allow(non_snake_case)] +fn Body() -> Element { + // Access to the i18n state + let mut i18n = i18n(); + + // Update the current language + let change_to_english = move |_| i18n.set_language(langid!("en-US")); + let change_to_spanish = move |_| i18n.set_language(langid!("es-ES")); + + rsx!( + rect { + rect { + direction: "horizontal", + Button { + onclick: change_to_english, + label { + "English" + } + } + Button { + onclick: change_to_spanish, + label { + "Spanish" + } + } + } + + // Get and subscribe to these messages + label { { t!("hello_world") } } + label { { t!("hello", name: "Dioxus") } } + } + ) +} + +fn app() -> Element { + // Initialize our i18n config + use_init_i18n(|| { + I18nConfig::new(langid!("en-US")) + .with_locale(Locale::new_static( + langid!("en-US"), + include_str!("./en-US.ftl"), + )) + .with_locale(Locale::new_dynamic( + langid!("es-ES"), + "./examples/es-ES.ftl", + )) + }); + + rsx!(Body {}) +} +``` diff --git a/book/src/learn/lifecycle.md b/book/src/learn/lifecycle.md new file mode 100644 index 000000000..6b8c06d37 --- /dev/null +++ b/book/src/learn/lifecycle.md @@ -0,0 +1,73 @@ +# Lifecycle + +Dioxus components can use hooks to manage certain lifecycle situations. + +## Component creation +You can run certain logic when the component is created for the first time by using the `use_hook` hook. + +```rs +fn app() -> Element { + + use_hook(|| { + println!("Component running for the first time!"); + }); + + rsx!(...) +} +``` + +## Component destroyment + +Run some logic when the component is being destroyed. + +```rs +fn app() -> Element { + + use_drop(|| { + println!("Component is being dropped."); + }); + + rsx!(...) +} +``` + +## Side effects + +Run some logic when a signal is changed. + +```rs +fn app() -> Element { + let mut signal = use_signal(|| 1); + + use_effect(move || { + // Because we are reading this signal now the effect is subscribed to any change + let value = signal(); + println!("Value of signal is {value}"); + }); + + rsx!(...) +} +``` + +## Side effects with dependencies + +Run some logic when a signal is changed. + +```rs +fn app() -> Element { + let mut signal = use_signal(|| 1); + let mut other_signal = use_signal(|| 1); + + // Manually specify non-signal values that we might want to react to + use_effect(use_reactive(&signal, |value| { + println!("Value of signal is {value}"); + })); + + // When you need multiple values you can pass a tuple + use_effect(use_reactive(&(signal, other_signal), |(value, other_signal)| { + println!("Value of signals are {value} and {other_signal}"); + })); + + rsx!(...) +} +``` \ No newline at end of file diff --git a/book/src/learn/state_management.md b/book/src/learn/state_management.md index 830a89ff0..3cbf77963 100644 --- a/book/src/learn/state_management.md +++ b/book/src/learn/state_management.md @@ -1,6 +1,6 @@ # State Management -Dioxus and thus Freya apps, don't have a single type of state management. +Dioxus and Freya apps, have multiple ways of state management. See the different options: diff --git a/book/src/learn/state_management/context.md b/book/src/learn/state_management/context.md index e69de29bb..6a81ac038 100644 --- a/book/src/learn/state_management/context.md +++ b/book/src/learn/state_management/context.md @@ -0,0 +1,153 @@ +# Context + +Dioxus offers a way to pass data from parent components to its descendents in a way to avoid [**Prop Drilling**](#prop-drilling). + +## Prop Drilling + +**Prop drilling** is when you want to pass a certain data from one parent component to some nested component, and you start to declare the same prop in each one of the components in between the parent and the target component. This causes a huge unnecessary boilerplate that can be used by using the Context API. + +```rs + +// Parent component +#[component] +fn CompA() -> Element { + rsx!( + CompB { + value: 2 + } + ) +} + +// This component needs the value just so it can pass it to the next component +#[component] +fn CompB(value: usize) -> Element { + rsx!( + CompC { + value + } + ) +} + +// Same as CompB +#[component] +fn CompC(value: usize) -> Element { + rsx!( + CompD { + value + } + ) +} + +// Finally ! Our target component +#[component] +fn CompD(value: usize) -> Element { + rsx!( + label { + "{value}" + } + ) +} + +``` + +## Context + +You can initialize a context that will be identified by its type and be allowed to be accessed from any desdendent from where you intialized it. + + +```rs + +// Parent component +#[component] +fn CompA() -> Element { + // Initialize the context with `1` usize as value + // Any component desdendant of `CompA` will be allowed to access this component + use_provide_context(|| 1); + + rsx!( + CompB { } + ) +} + +#[component] +fn CompB() -> Element { + rsx!( + CompC { } + ) +} + +#[component] +fn CompC() -> Element { + rsx!( + CompD { } + ) +} + +// Our target component +#[component] +fn CompD() -> Element { + // Retrieve our context as we know its type + let value = use_context::(); + + rsx!( + label { + "{value}" + } + ) +} +``` + +### Avoid having context with same type + +Because Context are identified by their type, you cannot do the following: + +```rs +// Parent component +#[component] +fn CompA() -> Element { + use_provide_context(|| 1); + use_provide_context(|| 2); + use_provide_context(|| 3); + + // All these 3 contexts share the same type, `usize`, so each context will replace any other context defined previously, which means that only the third context will actually be accessible + + rsx!( + CompB { } + ) +} +``` + +If you really need to the tree contexts split you can wrap them in different types so each one gets an unique type instead of just `usize`. + +```rs + +struct ValueA(pub usize); +struct ValueB(pub usize); +struct ValueC(pub usize); + +// Parent component +#[component] +fn CompA() -> Element { + use_provide_context(|| ValueA(1)); + use_provide_context(|| ValueB(2)); + use_provide_context(|| ValueC(3)); + + // All these 3 contexts share the same type, `usize`, so each context will replace any other context defined previously, which means that only the third context will actually be accessible + + rsx!( + CompB { } + ) +} + +#[component] +fn CompB() -> Element { + let value_a = use_context::(); + let value_b = use_context::(); + let value_c = use_context::(); + rsx!( + label { + "{value_a.0} {value_b.0} {value_c.0}" + } + ) +} +``` \ No newline at end of file diff --git a/book/src/learn/state_management/third_party.md b/book/src/learn/state_management/third_party.md index f64e38a3f..ae269f639 100644 --- a/book/src/learn/state_management/third_party.md +++ b/book/src/learn/state_management/third_party.md @@ -1,11 +1,6 @@ # Third Party -### dioxus-query - -https://github.com/marc2332/dioxus-query - - -### dioxus-radio - -https://github.com/dioxus-community/dioxus-radio +There are other community crates that offer different approaches to state management. +- [dioxus-query](https://github.com/marc2332/dioxus-query) +- [dioxus-radio](https://github.com/dioxus-community/dioxus-radio) diff --git a/book/src/learn/ui.md b/book/src/learn/ui.md index 4b03e6f3e..41cbd5643 100644 --- a/book/src/learn/ui.md +++ b/book/src/learn/ui.md @@ -29,17 +29,28 @@ This macro is not a standalone-language or anything like that. It is simply a ma The structure for RSX looks like this: ```rs -rect { // Element - background: "red", // Attribute for the element `rect` - width: "100%",// Attribute for the element `rect` - onclick: |_| println!("Clicked!"), // Event handler for the element `rect`, its just a Rust closure - label { // Element children of `rect` - "Hello, World!" // Text Element for the element `label` +// Element, always in lower case +rect { + // Attribute for the element `rect` + background: "red", + // Attribute for the element `rect` + width: "100%", + // Event handler for the element `rect`, can be a function or a closure + onclick: |_| println!("Clicked!"), + // Element child of `rect` + label { + // Text Element for the element `label` + "Hello, World!" + } + // Component child of `rect`, always in PascalCase + CoolComp { + // Prop for the component `CoolComp` + prop: 123 } } ``` -You can reference variables from outside the RSX inside of it: +You can reference variables inside the RSX as well: ```rs let onclick = |_| { @@ -47,6 +58,7 @@ let onclick = |_| { }; let width = "100%"; +let name = "World"; rsx!( rect { @@ -54,7 +66,10 @@ rsx!( width, onclick, label { - "Hello, World!" + "Hello, {name}!" + } + label { + "{1 + 1} is 2" } } ) @@ -63,17 +78,21 @@ rsx!( Or just use if, for-loops, etc.. Inside of the RSX: ```rs -let my_value = false; +let show_text = false; rsx!( rect { for i in 0..5 { label { + // Looped elements must have an unique ID specified through + // the `key` attribute so Dioxus is able to identify them key: "{i}", "Value -> {i}" } } - if my_value { + // When this condition is not met the inner element will + // simply not be rendered + if show_text { label { "Hello, World!" } diff --git a/examples/counter.rs b/examples/counter.rs index 25160e129..e439e005a 100644 --- a/examples/counter.rs +++ b/examples/counter.rs @@ -12,6 +12,10 @@ fn main() { fn app() -> Element { let mut count = use_signal(|| 0); + use_effect(use_reactive(&count, |count| { + + })); + rsx!( rect { height: "50%",