Internationalization: move guide to en folder and preserve its history. (#494)

* chore: move guide to en folder

* docs: add english as language
This commit is contained in:
Jon Kelley authored and GitHub committed 2022-07-09 12:31:46 -04:00
1 parent 7fdc72726d
commit c97ca7dff6
69 files changed
+4 -1

No files matched your search

@@ -0,0 +1,26 @@
# Custom Hooks
Hooks are a great way to encapsulate business logic. If none of the existing hooks work for your problem, you can write your own.
## Composing Hooks
To avoid repetition, you can encapsulate business logic based on existing hooks to create a new hook.
For example, if many components need to access an `AppSettings` struct, you can create a "shortcut" hook:
```rust
{{#include ../../examples/hooks_composed.rs:wrap_context}}
```
## Custom Hook Logic
You can use [`cx.use_hook`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Scope.html#method.use_hook) to build your own hooks. In fact, this is what all the standard hooks are built on!
`use_hook` accepts a single closure for initializing the hook. It will be only run the first time the component is rendered. The return value of that closure will be used as the value of the hook – Dioxus will take it, and store it for as long as the component is alive. On every render (not just the first one!), you will get a reference to this value.
> Note: You can implement [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html) for your hook value – it will be dropped then the component is unmounted (no longer in the UI)
Inside the initialization closure, you will typically make calls to other `cx` methods. For example:
- The `use_state` hook tracks state in the hook value, and uses [`cx.schedule_update`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Scope.html#method.schedule_update) to make Dioxus re-render the component whenever it changes.
- The `use_context` hook calls [`cx.consume_context`](https://docs.rs/dioxus/latest/dioxus/prelude/struct.Scope.html#method.consume_context) (which would be expensive to call on every render) to get some context from the scope
@@ -0,0 +1,73 @@
# Dynamic Rendering
Sometimes you want to render different things depending on the state/props. With Dioxus, just describe what you want to see using Rust control flow – the framework will take care of making the necessary changes on the fly if the state or props change!
## Conditional Rendering
To render different elements based on a condition, you could use an `if-else` statement:
```rust
{{#include ../../examples/conditional_rendering.rs:if_else}}
```
> You could also use `match` statements, or any Rust function to conditionally render different things.
### Inspecting `Element` props
Since `Element` is a `Option<VNode>`, components accepting `Element` as a prop can actually inspect its contents, and render different things based on that. Example:
```rust
{{#include ../../examples/component_children_inspect.rs:Clickable}}
```
You can't mutate the `Element`, but if you need a modified version of it, you can construct a new one based on its attributes/children/etc.
## Rendering Nothing
To render nothing, you can return `None` from a component. This is useful if you want to conditionally hide something:
```rust
{{#include ../../examples/conditional_rendering.rs:conditional_none}}
```
This works because the `Element` type is just an alias for `Option<VNode>`
> Again, you may use a different method to conditionally return `None`. For example the boolean's [`then()`](https://doc.rust-lang.org/std/primitive.bool.html#method.then) function could be used.
## Rendering Lists
Often, you'll want to render a collection of components. For example, you might want to render a list of all comments on a post.
For this, Dioxus accepts iterators that produce `Element`s. So we need to:
- Get an iterator over all of our items (e.g., if you have a `Vec` of comments, iterate over it with `iter()`)
- `.map` the iterator to convert each item into a rendered `Element` using `cx.render(rsx!(...))`
- Add a unique `key` attribute to each iterator item
- Include this iterator in the final RSX
Example: suppose you have a list of comments you want to render. Then, you can render them like this:
```rust
{{#include ../../examples/rendering_lists.rs:render_list}}
```
### The `key` Attribute
Every time you re-render your list, Dioxus needs to keep track of which item went where, because the order of items in a list might change – items might be added, removed or swapped. Despite that, Dioxus needs to:
- Keep track of component state
- Efficiently figure out what updates need to be made to the UI
For example, suppose the `CommentComponent` had some state – e.g. a field where the user typed in a reply. If the order of comments suddenly changes, Dioxus needs to correctly associate that state with the same comment – otherwise, the user will end up replying to a different comment!
To help Dioxus keep track of list items, we need to associate each item with a unique key. In the example above, we dynamically generated the unique key. In real applications, it's more likely that the key will come from e.g. a database ID. It doesn't really matter where you get the key from, as long as it meets the requirements
- Keys must be unique in a list
- The same item should always get associated with the same key
- Keys should be relatively small (i.e. converting the entire Comment structure to a String would be a pretty bad key) so they can be compared efficiently
You might be tempted to use an item's index in the list as its key. In fact, that’s what Dioxus will use if you don’t specify a key at all. This is only acceptable if you can guarantee that the list is constant – i.e., no re-ordering, additions or deletions.
> Note that if you pass the key to a component you've made, it won't receive the key as a prop. It’s only used as a hint by Dioxus itself. If your component needs an ID, you have to pass it as a separate prop.
@@ -0,0 +1,66 @@
# Event Handlers
Events are interesting things that happen, usually related to something the user has done. For example, some events happen when the user clicks, scrolls, moves the mouse, or types a character. To respond to these actions and make the UI interactive, we need to handle those events.
Events are associated with elements. For example, we usually don't care about all the clicks that happen within an app, only those on a particular button. To handle events that happen on an element, we must attach the desired event handler to it.
Event handlers are similar to regular attributes, but their name usually starts with `on`- and they accept closures as values. The closure will be called whenever the event is triggered, and will be passed that event.
For example, to handle clicks on an element, we can specify an `onclick` handler:
```rust
{{#include ../../examples/event_click.rs:rsx}}
```
## The `Event` object
Event handlers receive an [`UiEvent`](https://docs.rs/dioxus-core/latest/dioxus_core/struct.UiEvent.html) object containing information about the event. Different types of events contain different types of data. For example, mouse-related events contain [`MouseData`](https://docs.rs/dioxus/latest/dioxus/events/struct.MouseData.html), which tells you things like where the mouse was clicked and what mouse buttons were used.
In the example above, this event data was logged to the terminal:
```
Clicked! Event: UiEvent { data: MouseData { coordinates: Coordinates { screen: (468.0, 109.0), client: (73.0, 25.0), element: (63.0, 15.0), page: (73.0, 25.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } }
Clicked! Event: UiEvent { data: MouseData { coordinates: Coordinates { screen: (468.0, 109.0), client: (73.0, 25.0), element: (63.0, 15.0), page: (73.0, 25.0) }, modifiers: (empty), held_buttons: EnumSet(), trigger_button: Some(Primary) } }
```
To learn what the different event types provide, read the [events module docs](https://docs.rs/dioxus/latest/dioxus/events/index.html).
### Stopping propagation
When you have e.g. a `button` inside a `div`, any click on the `button` is also a click on the `div`. For this reason, Dioxus propagates the click event: first, it is triggered on the target element, then on parent elements. If you want to prevent this behavior, you can call `cancel_bubble()` on the event:
```rust
{{#include ../../examples/event_click.rs:rsx}}
```
## Prevent Default
Some events have a default behavior. For keyboard events, this might be entering the typed character. For mouse events, this might be selecting some text.
In some instances, might want to avoid this default behavior. For this, you can add the `prevent_default` attribute with the name of the handler whose default behavior you want to stop. This attribute is special: you can attach it multiple times for multiple attributes:
```rust
{{#include ../../examples/event_prevent_default.rs:prevent_default}}
```
Any event handlers will still be called.
> Normally, in React or JavaScript, you'd call "preventDefault" on the event in the callback. Dioxus does *not* currently support this behavior. Note: this means you cannot conditionally prevent default behavior.
## Handler Props
Sometimes, you might want to make a component that accepts an event handler. A simple example would be a `FancyButton` component, which accepts an `on_click` handler:
```rust
{{#include ../../examples/event_handler_prop.rs:component_with_handler}}
```
Then, you can use it like any other handler:
```rust
{{#include ../../examples/event_handler_prop.rs:usage}}
```
> Note: just like any other attribute, you can name the handlers anything you want! Though they must start with `on`, for the prop to be automatically turned into an `EventHandler` at the call site.
>
> You can also put custom data in the event, rather than e.g. `MouseData`
+83
View File
@@ -0,0 +1,83 @@
# Hooks and Component State
So far our components, being Rust functions, had no state – they were always rendering the same thing. However, in a UI component, it is often useful to have stateful functionality to build user interactions. For example, you might want to track whether the user has openend a drop-down, and render different things accordingly.
For stateful logic, you can use hooks. Hooks are Rust functions that take a reference to `ScopeState` (in a component, you can pass `&cx`), and provide you with functionality and state.
## `use_state` Hook
[`use_state`](https://docs.rs/dioxus/latest/dioxus/hooks/fn.use_state.html) is one of the simplest hooks.
- You provide a closure that determines the initial value
- `use_state` gives you the current value, and a way to update it by setting it to something else
- When the value updates, `use_state` makes the component re-render, and provides you with the new value
For example, you might have seen the counter example, in which state (a number) is tracked using the `use_state` hook:
```rust
{{#include ../../examples/hooks_counter.rs:component}}
```
![Screenshot: counter app](./images/counter.png)
Every time the component's state changes, it re-renders, and the component function is called, so you can describe what you want the new UI to look like. You don't have to worry about "changing" anything – just describe what you want in terms of the state, and Dioxus will take care of the rest!
> `use_state` returns your value wrapped in a smart pointer of type [`UseState`](https://docs.rs/dioxus/latest/dioxus/hooks/struct.UseState.html). This is why you can both read the value and update it, even within a handler.
You can use multiple hooks in the same component if you want:
```rust
{{#include ../../examples/hooks_counter_two_state.rs:component}}
```
![Screenshot: app with two counters](./images/counter_two_state.png)
## Rules of Hooks
The above example might seem a bit magic, since Rust functions are typically not associated with state. Dioxus allows hooks to maintain state across renders through a reference to `ScopeState`, which is why you must pass `&cx` to them.
But how can Dioxus differentiate between multiple hooks in the same component? As you saw in the second example, both `use_state` functions were called with the same parameters, so how come they can return different things when the counters are different?
```rust
{{#include ../../examples/hooks_counter_two_state.rs:use_state_calls}}
```
This is only possible because the two hooks are always called in the same order, so Dioxus knows which is which. So the order you call hooks matters, which is why you must follow certain rules when using hooks:
1. Hooks may be only used in components or other hooks (we'll get to that later)
2. On every call to the component function
1. The same hooks must be called
2. In the same order
3. Hooks name's must start with `use_` so you don't accidentally confuse them with regular functions
These rules mean that there are certain things you can't do with hooks:
### No Hooks in Conditionals
```rust
{{#include ../../examples/hooks_bad.rs:conditional}}
```
### No Hooks in Closures
```rust
{{#include ../../examples/hooks_bad.rs:closure}}
```
### No Hooks in Loops
```rust
{{#include ../../examples/hooks_bad.rs:loop}}
```
## `use_ref` Hook
`use_state` is great for tracking simple values. However, you may notice in the [`UseState` API](https://docs.rs/dioxus/latest/dioxus/hooks/struct.UseState.html) that the only way to modify its value is to replace it with something else (e.g., by calling `set`, or through one of the `+=`, `-=` operators). This works well when it is cheap to construct a value (such as any primitive). But what if you want to maintain more complex data in the components state?
For example, suppose we want to maintain a `Vec` of values. If we stored it with `use_state`, the only way to add a new value to the list would be to create a new `Vec` with the additional value, and put it in the state. This is expensive! We want to modify the existing `Vec` instead.
Thankfully, there is another hook for that, `use_ref`! It is similar to `use_state`, but it lets you get a mutable reference to the contained data.
Here's a simple example that keeps a list of events in a `use_ref`. We can acquire write access to the state with `.write()`, and then just `.push` a new value to the state:
```rust
{{#include ../../examples/hooks_use_ref.rs:component}}
```
> The return values of `use_state` and `use_ref`, (`UseState` and `UseRef`, respectively) are in some ways similar to [`Cell`](https://doc.rust-lang.org/std/cell/) and [`RefCell`](https://doc.rust-lang.org/std/cell/struct.RefCell.html) – they provide interior mutability. However, these Dioxus wrappers also ensure that the component gets re-rendered whenever you change the state.
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 KiB

+3
View File
@@ -0,0 +1,3 @@
# Interactivity
So far, we've learned how to describe the structure and properties of our user interfaces. However, most interfaces need to be interactive in order to be useful. In this chapter, we describe how to make a Dioxus app that responds to the user.
+86
View File
@@ -0,0 +1,86 @@
# Router
In many of your apps, you'll want to have different "scenes". For a webpage, these scenes might be the different webpages with their own content.
You could write your own scene management solution – quite simply too. However, to save you the effort, Dioxus supports a first-party solution for scene management called Dioxus Router.
## What is it?
For an app like the Dioxus landing page (https://dioxuslabs.com), we want to have different pages. A quick sketch of an app would be something like:
- Homepage
- Blog
- Example showcase
Each of these scenes is independent – we don't want to render both the homepage and blog at the same time.
This is where the router crates come in handy. To make sure we're using the router, simply add the `"router"` feature to your dioxus dependency.
```toml
[dependencies]
dioxus = { version = "0.2", features = ["desktop", "router"] }
```
## Using the router
Unlike other routers in the Rust ecosystem, our router is built declaratively. This makes it possible to compose our app layout simply by arranging components.
```rust
rsx!{
Router {
Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} }
}
}
```
Whenever we visit this app, we will get either the Home component or the Blog component rendered depending on which route we enter at. If neither of these routes match the current location, then nothing will render.
We can fix this one of two ways:
- A fallback 404 page
```rust
rsx!{
Router {
Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} }
Route { to: "", NotFound {} }
}
}
```
- Redirect 404 to home
```rust
rsx!{
Router {
Route { to: "/home", Home {} }
Route { to: "/blog", Blog {} }
Redirect { from: "", to: "/home" }
}
}
```
## Links
For our app to navigate these routes, we can provide clickable elements called Links. These simply wrap `<a>` elements that, when clicked, navigate the app to the given location.
```rust
rsx!{
Link {
to: "/home",
"Go home!"
}
}
```
## More reading
This page is just meant to be a very brief overview of the router to show you that there's a powerful solution already built for a very common problem. For more information about the router, definitely check out its book or check out some of the examples.
The router has its own documentation! [Available here](https://dioxuslabs.com/router/guide/).
@@ -0,0 +1,66 @@
# Sharing State
Often, multiple components need to access the same state. Depending on your needs, there are several ways to implement this.
## Lifting State
One approach to share state between components is to "lift" it up to the nearest common ancestor. This means putting the `use_state` hook in a parent component, and passing the needed values down as props.
For example, suppose we want to build a meme editor. We want to have an input to edit the meme caption, but also a preview of the meme with the caption. Logically, the meme and the input are 2 separate components, but they need access to the same state (the current caption).
> Of course, in this simple example, we could write everything in one component – but it is better to split everything out in smaller components to make the code more reusable and easier to maintain (this is even more important for larger, complex apps).
We start with a `Meme` component, responsible for rendering a meme with a given caption:
```rust
{{#include ../../examples/meme_editor.rs:meme_component}}
```
> Note that the `Meme` component is unaware where the caption is coming from – it could be stored in `use_state`, `use_ref`, or a constant. This ensures that it is very reusable – the same component can be used for a meme gallery without any changes!
We also create a caption editor, completely decoupled from the meme. The caption editor must not store the caption itself – otherwise, how will we provide it to the `Meme` component? Instead, it should accept the current caption as a prop, as well as an event handler to delegate input events to:
```rust
{{#include ../../examples/meme_editor.rs:caption_editor}}
```
Finally, a third component will render the other two as children. It will be responsible for keeping the state and passing down the relevant props.
```rust
{{#include ../../examples/meme_editor.rs:meme_editor}}
```
![Meme Editor Screenshot: An old plastic skeleton sitting on a park bench. Caption: "me waiting for a language feature"](./images/meme_editor_screenshot.png)
## Using Context
Sometimes, some state needs to be shared between multiple components far down the tree, and passing it down through props is very inconvenient.
Suppose now that we want to implement a dark mode toggle for our app. To achieve this, we will make every component select styling depending on whether dark mode is enabled or not.
> Note: we're choosing this approach for the sake of an example. There are better ways to implement dark mode (e.g. using CSS variables). Let's pretend CSS variables don't exist – welcome to 2013!
Now, we could write another `use_state` in the top component, and pass `is_dark_mode` down to every component through props. But think about what will happen as the app grows in complexity – almost every component that renders any CSS is going to need to know if dark mode is enabled or not – so they'll all need the same dark mode prop. And every parent component will need to pass it down to them. Imagine how messy and verbose that would get, especially if we had components several levels deep!
Dioxus offers a better solution than this "prop drilling" – providing context. The [`use_context_provider`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context_provider.html) hook is similar to `use_ref`, but it makes it available through [`use_context`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_context.html) for all children components.
First, we have to create a struct for our dark mode configuration:
```rust
{{#include ../../examples/meme_editor_dark_mode.rs:DarkMode_struct}}
```
Now, in a top-level component (like `App`), we can provide the `DarkMode` context to all children components:
```rust
{{#include ../../examples/meme_editor_dark_mode.rs:context_provider}}
```
As a result, any child component of `App` (direct or not), can access the `DarkMode` context.
```rust
{{#include ../../examples/meme_editor_dark_mode.rs:use_context}}
```
> `use_context` returns `Option<UseSharedState<DarkMode>>` here. If the context has been provided, the value is `Some(UseSharedState)`, which you can call `.read` or `.write` on, similarly to `UseRef`. Otherwise, the value is `None`.
For example, here's how we would implement the dark mode toggle, which both reads the context (to determine what color it should render) and writes to it (to toggle dark mode):
```rust
{{#include ../../examples/meme_editor_dark_mode.rs:toggle}}
```
@@ -0,0 +1,31 @@
# User Input
Interfaces often need to provide a way to input data: e.g. text, numbers, checkboxes, etc. In Dioxus, there are two ways you can work with user input.
## Controlled Inputs
With controlled inputs, you are directly in charge of the state of the input. This gives you a lot of flexibility, and makes it easy to keep things in sync. For example, this is how you would create a controlled text input:
```rust
{{#include ../../examples/input_controlled.rs:component}}
```
Notice the flexibility – you can:
- Also display the same contents in another element, and they will be in sync
- Transform the input every time it is modified (e.g. to make sure it is upper case)
- Validate the input every time it changes
- Have custom logic happening when the input changes (e.g. network request for autocompletion)
- Programmatically change the value (e.g. a "randomize" button that fills the input with nonsense)
## Uncontrolled Inputs
As an alternative to controlled inputs, you can simply let the platform keep track of the input values. If we don't tell a HTML input what content it should have, it be editable anyway (this is built into the webview). This approach can be more performant, but less flexible. For example, it's harder to keep the input in sync with another element.
Since you don't necessarily have the current value of the uncontrolled input in state, you can access it either by listening to `oninput` events (similarly to controlled components), or, if the input is part of a form, you can access the form data in the form events (e.g. `oninput` or `onsubmit`):
```rust
{{#include ../../examples/input_uncontrolled.rs:component}}
```
```
Submitted! UiEvent { data: FormData { value: "", values: {"age": "very old", "date": "1966", "name": "Fred"} } }
```