From 6727de447b638b21b231be8f9b7b083f82953768 Mon Sep 17 00:00:00 2001 From: Reinis Mazeiks Date: Wed, 9 Feb 2022 19:35:56 +0200 Subject: [PATCH] Proofread & rephrase chapters 1-5 for readability --- docs/guide/src/README.md | 2 +- .../src/elements/conditional_rendering.md | 64 ++++++------- docs/guide/src/elements/index.md | 4 +- docs/guide/src/elements/lists.md | 94 +++++-------------- docs/guide/src/elements/special_attributes.md | 26 ++--- docs/guide/src/elements/vnodes.md | 14 +-- 6 files changed, 76 insertions(+), 128 deletions(-) diff --git a/docs/guide/src/README.md b/docs/guide/src/README.md index 32943cd7b..bbbd9f85f 100644 --- a/docs/guide/src/README.md +++ b/docs/guide/src/README.md @@ -22,7 +22,7 @@ In general, Dioxus and React share many functional similarities. If this guide i ## 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 Web-Sys. 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. +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) diff --git a/docs/guide/src/elements/conditional_rendering.md b/docs/guide/src/elements/conditional_rendering.md index c13263cf6..5c19de237 100644 --- a/docs/guide/src/elements/conditional_rendering.md +++ b/docs/guide/src/elements/conditional_rendering.md @@ -67,7 +67,7 @@ fn App(cx: Scope)-> Element { } ``` -Do note: the `rsx!` macro returns a `Closure`, an anonymous function that has a unique type. To turn our `rsx!` into Elements, we need to call `cx.render`. +Do note: the `rsx!` macro does not return an Element, but rather a wrapper struct for a `Closure` (an anonymous function). To turn our `rsx!` into an Element, we need to call `cx.render`. To make patterns like these less verbose, the `rsx!` macro accepts an optional first argument on which it will call `render`. Our previous component can be shortened with this alternative syntax: @@ -136,34 +136,6 @@ cx.render(rsx!{ }) ``` - -## Boolean Mapping - -In the spirit of highly-functional apps, we suggest using the "boolean mapping" pattern when trying to conditionally hide/show an Element. - -By default, Rust lets you convert any `boolean` into any other type by calling `and_then()`. We can exploit this functionality in components by mapping to some Element. - -```rust -let show_title = true; -rsx!( - div { - show_title.and_then(|| rsx!{ - "This is the title" - }) - } -) -``` - -We can use this pattern for many things, including options: -```rust -let user_name = Some("bob"); -rsx!( - div { - user_name.map(|name| rsx!("Hello {name}")) - } -) -``` - ## Rendering Nothing Sometimes, you don't want your component to return anything at all. Under the hood, the `Element` type is just an alias for `Option`, so you can simply return `None`. @@ -176,11 +148,37 @@ fn demo(cx: Scope) -> Element { } ``` +## Boolean Mapping + +In the spirit of highly-functional apps, we suggest using the "boolean mapping" pattern when trying to conditionally hide/show an Element. + +By default, Rust lets you convert any `boolean` into an Option of any other type with [`then()`](https://doc.rust-lang.org/std/primitive.bool.html#method.then). We can use this in Components by mapping to some Element. + +```rust +let show_title = true; +rsx!( + div { + // Renders nothing by returning None when show_title is false + show_title.then(|| rsx!{ + "This is the title" + }) + } +) +``` + +We can use this pattern for many things, including options: +```rust +let user_name = Some("bob"); +rsx!( + div { + // Renders nothing if user_name is None + user_name.map(|name| rsx!("Hello {name}")) + } +) +``` + ## Moving Forward: -In this chapter, we learned how to render different Elements from a Component depending on a condition. This is a very powerful building block to assemble complex User Interfaces! +In this chapter, we learned how to render different Elements from a Component depending on a condition. This is a very powerful building block to assemble complex user interfaces! In the next chapter, we'll cover how to renderer lists inside your `rsx!`. - -Related Reading: -- [RSX in Depth]() diff --git a/docs/guide/src/elements/index.md b/docs/guide/src/elements/index.md index aedc39e34..c814945d1 100644 --- a/docs/guide/src/elements/index.md +++ b/docs/guide/src/elements/index.md @@ -68,6 +68,6 @@ Remember: this concept is not new! Many frameworks are declarative - with React Here's some reading about declaring UI in React: -- [https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js](https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js) +- [Difference between declarative and imperative in React.js](https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js), a StackOverflow thread -- [https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44](https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44) +- [Declarative vs Imperative](https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44), a blog post by Myung Kim diff --git a/docs/guide/src/elements/lists.md b/docs/guide/src/elements/lists.md index 41c6a892d..40628c164 100644 --- a/docs/guide/src/elements/lists.md +++ b/docs/guide/src/elements/lists.md @@ -42,7 +42,7 @@ As a simple example, let's render a list of names. First, start with our input d let names = ["jim", "bob", "jane", "doe"]; ``` -Then, we create a new iterator by calling `iter` and then `map`. In our `map` function, we'll place render our template. +Then, we create a new iterator by calling `iter` and then [`map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.map). In our `map` function, we'll render our template. ```rust let name_list = names.iter().map(|name| rsx!( @@ -50,7 +50,7 @@ let name_list = names.iter().map(|name| rsx!( )); ``` -Finally, we can include this list in the final structure: +We can include this list in the final Element: ```rust rsx!( @@ -59,7 +59,8 @@ rsx!( } ) ``` -Or, we can include the iterator inline: + +Rather than storing `name_list` in a temporary variable, we could also include the iterator inline: ```rust rsx!( ul { @@ -70,7 +71,7 @@ rsx!( ) ``` -The HTML-rendered version of this list would follow what you would expect: +The rendered HTML list is what you would expect: ```html ``` -### Rendering our posts with a PostList component - -Let's start by modeling this problem with a component and some properties. - -For this example, we're going to use the borrowed component syntax since we probably have a large list of posts that we don't want to clone every time we render the Post List. - -```rust -#[derive(Props, PartialEq)] -struct PostListProps<'a> { - posts: &'a [PostData] -} -``` -Next, we're going to define our component: - -```rust -fn App(cx: Scope) -> Element { - // First, we create a new iterator by mapping the post array - let posts = cx.props.posts.iter().map(|post| rsx!{ - Post { - title: post.title, - age: post.age, - original_poster: post.original_poster - } - }); - - // Finally, we render the post list inside of a container - cx.render(rsx!{ - ul { class: "post-list" - {posts} - } - }) -} -``` - - ## Filtering Iterators Rust's iterators are extremely powerful, especially when used for filtering tasks. When building user interfaces, you might want to display a list of items filtered by some arbitrary check. -As a very simple example, let's set up a filter where we only list names that begin with the letter "J". +As a very simple example, let's set up a filter where we only list names that begin with the letter "j". -Let's make our list of names: - -```rust -let names = ["jim", "bob", "jane", "doe"]; -``` - -Then, we create a new iterator by calling `iter`, then `filter`, then `map`. In our `filter` function, we'll only allow "j" names, and in our `map` function, we'll render our template. +Using the list from above, let's create a new iterator. Before we render the list with `map` as in the previous example, we'll [`filter`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter) the names to only allow those that start with "j". ```rust let name_list = names @@ -136,53 +96,43 @@ let name_list = names .map(|name| rsx!( li { "{name}" })); ``` -Rust's iterators provide us tons of functionality and are significantly easier to work with than JavaScript's map/filter/reduce. +Rust's Iterators are very versatile – check out [their documentation](https://doc.rust-lang.org/std/iter/trait.Iterator.html) for more things you can do with them! -For keen Rustaceans: notice how we don't actually call `collect` on the name list. If we `collected` our filtered list into new Vec, then we would need to make an allocation to store these new elements. Instead, we create an entirely new _lazy_ iterator which will then be consumed by Dioxus in the `render` call. +For keen Rustaceans: notice how we don't actually call `collect` on the name list. If we `collect`ed our filtered list into new Vec, we would need to make an allocation to store these new elements, which slows down rendering. Instead, we create an entirely new _lazy_ iterator which Dioxus will consume in the `render` call. The `render` method is extraordinarily efficient, so it's best practice to let it do most of the allocations for us. -The `render` method is extraordinarily efficient, so it's best practice to let it do most of the allocations for us. +## Keeping list items in order with `key` -## Keeping list items in order with key +The examples above demonstrate the power of iterators in `rsx!` but all share the same issue: if your array items move (e.g. due to sorting), get inserted, or get deleted, Dioxus has no way of knowing what happened. This can cause Elements to be unnecessarily removed, changed and rebuilt when all that was needed was to change their position – this is inneficient. - -The examples above demonstrate the power of iterators in `rsx!` but all share the same issue: a lack of "keys". Whenever you render a list of elements, each item in the list must be **uniquely identifiable**. To make each item unique, you need to give it a "key". - -In Dioxus, keys are strings that uniquely identifies it among other items in that array: +To solve this problem, each item in the list must be **uniquely identifiable**. You can achieve this by giving it a unique, fixed "key". In Dioxus, a key is a string that identifies an item among others in the list. ```rust rsx!( li { key: "a" } ) ``` -Keys tell Dioxus which array item each component corresponds to, so that it can match them up later. This becomes important if your array items can move (e.g. due to sorting), get inserted, or get deleted. A well-chosen key helps Dioxus infer what exactly has happened, and make the correct updates to the screen - +Now, if an item has already been rendered once, Dioxus can use the key to match it up later to make the correct updates – and avoid unnecessary work. NB: the language from this section is strongly borrowed from [React's guide on keys](https://reactjs.org/docs/lists-and-keys.html). + ### Where to get your key Different sources of data provide different sources of keys: - _Data from a database_: If your data is coming from a database, you can use the database keys/IDs, which are unique by nature. -- _Locally generated data_: If your data is generated and persisted locally (e.g. notes in a note-taking app), use an incrementing counter or a package like `uuid` when creating items. +- _Locally generated data_: If your data is generated and persisted locally (e.g. notes in a note-taking app), keep track of keys along with your data. You can use an incrementing counter or a package like `uuid` to generate keys for new items – but make sure they stay the same for the item's lifetime. + +Remember: keys let Dioxus uniquely identify an item among its siblings. A well-chosen key provides more information than the position within the array. Even if the position changes due to reordering, the key lets Dioxus identify the item throughout its lifetime. ### Rules of keys - Keys must be unique among siblings. However, it’s okay to use the same keys for Elements in different arrays. -- Keys must not change or that defeats their purpose! Don’t generate them while rendering. +- An item's key must not change – **don’t generate them on the fly** while rendering. Otherwise, Dioxus will be unable to keep track of which item is which, and we're back to square one. -### Why does Dioxus need keys? +You might be tempted to use an item's index in the array 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. In all other cases, do not use the index for the key – it will lead to the performance problems described above. -Imagine that files on your desktop didn’t have names. Instead, you’d refer to them by their order — the first file, the second file, and so on. You could get used to it, but once you delete a file, it would get confusing. The second file would become the first file, the third file would be the second file, and so on. - -File names in a folder and Element keys in an array serve a similar purpose. They let us uniquely identify an item between its siblings. A well-chosen key provides more information than the position within the array. Even if the position changes due to reordering, the key lets Dioxus identify the item throughout its lifetime. - -### Gotcha -You might be tempted to use an item’s index in the array as its key. In fact, that’s what Dioxus will use if you don’t specify a key at all. But the order in which you render items will change over time if an item is inserted, deleted, or if the array gets reordered. Index as a key often leads to subtle and confusing bugs. - -Similarly, do not generate keys on the fly, like `gen_random`. This will cause keys to never match up between renders, leading to all your components and DOM being recreated every time. Not only is this slow, but it will also lose any user input inside the list items. Instead, use a stable ID based on the data. - -Note that your components won’t receive 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: +Note that if you pass the key to a [custom component](./components.md) 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: ```rust -Post { key: "{key}", id: "{id}" } +Post { key: "{key}", id: "{key}" } ``` ## Moving on @@ -192,4 +142,4 @@ In this section, we learned: - How to use iterator tools to filter and transform data - How to use keys to render lists efficiently -Moving forward, we'll finally cover user input and interactivity. +Moving forward, we'll learn more about attributes. diff --git a/docs/guide/src/elements/special_attributes.md b/docs/guide/src/elements/special_attributes.md index 460d1cd69..a09ce0bf7 100644 --- a/docs/guide/src/elements/special_attributes.md +++ b/docs/guide/src/elements/special_attributes.md @@ -15,7 +15,7 @@ In this section, we'll cover special attributes built into Dioxus: One thing you might've missed from React is the ability to render raw HTML directly to the DOM. If you're working with pre-rendered assets, output from templates, or output from a JS library, then you might want to pass HTML directly instead of going through Dioxus. In these instances, reach for `dangerous_inner_html`. -For example, shipping a markdown-to-Dioxus converter might significantly bloat your final application size. Instead, you'll want to pre-render your markdown to HTML and then include the HTML directly in your output. We use this approach for the `http://dioxuslabs.com` site: +For example, shipping a markdown-to-Dioxus converter might significantly bloat your final application size. Instead, you'll want to pre-render your markdown to HTML and then include the HTML directly in your output. We use this approach for the [Dioxus homepage](https://dioxuslabs.com): ```rust @@ -30,14 +30,16 @@ fn BlogPost(cx: Scope) -> Element { } ``` -> Note! This attribute is called "dangerous_inner_html" because it is DANGEROUS. If you're not careful, you can easily expose cross-site-scripting (XSS) attacks to your users. If you're handling untrusted input, make sure to escape your HTML before passing it into `dangerous_inner_html`. +> Note! This attribute is called "dangerous_inner_html" because it is **dangerous** to pass it data you don't trust. If you're not careful, you can easily expose cross-site-scripting (XSS) attacks to your users. +> +> If you're handling untrusted input, make sure to sanitize your HTML before passing it into `dangerous_inner_html` – or just pass it to a Text Element to escape any HTML tags. ## Boolean Attributes Most attributes, when rendered, will be rendered exactly as the input you provided. However, some attributes are considered "boolean" attributes and just their presence determines whether or not they affect the output. For these attributes, a provided value of `"false"` will cause them to be removed from the target element. -So the input of: +So this RSX: ```rust rsx!{ @@ -47,14 +49,12 @@ rsx!{ } } ``` -would actually render an output of +wouldn't actually render the `hidden` attribute: ```html
hello
``` -Notice how `hidden` is not present in the final output? - -Not all attributes work like this however. Only *these specific attributes* are whitelisted to have this behavior: +Not all attributes work like this however. *Only the following attributes* have this behavior: - `allowfullscreen` - `allowpaymentrequest` @@ -131,7 +131,7 @@ pub fn StateInput<'a>(cx: Scope<'a, InputProps<'a>>) -> Element { ## Controlled inputs and `value`, `checked`, and `selected` -In Dioxus, there is a distinction between controlled and uncontrolled inputs. Most inputs you'll use are "controlled," meaning we both drive the `value` of the input and react to the `oninput`. +In Dioxus, there is a distinction between controlled and uncontrolled inputs. Most inputs you'll use are controlled, meaning we both drive the `value` of the input and react to the `oninput`. Controlled components: ```rust @@ -153,7 +153,7 @@ let value = use_ref(&cx, || String::from("hello world")); rsx! { input { oninput: move |evt| *value.write_silent() = evt.value.clone(), - // no "value" is driven + // no "value" is driven here – the input keeps track of its own value, and you can't change it } } ``` @@ -162,7 +162,7 @@ rsx! { For element fields that take a handler like `onclick` or `oninput`, Dioxus will let you attach a closure. Alternatively, you can also pass a string using normal attribute syntax and assign this attribute on the DOM. -This lets you escape into JavaScript (only if your renderer can execute JavaScript). +This lets you use JavaScript (only if your renderer can execute JavaScript). ```rust rsx!{ @@ -179,14 +179,14 @@ rsx!{ ## Wrapping up -We've reached just about the end of what you can do with elements without venturing into "advanced" territory. - In this chapter, we learned: - How to declare elements - How to conditionally render parts of your UI - How to render lists - Which attributes are "special" + diff --git a/docs/guide/src/elements/vnodes.md b/docs/guide/src/elements/vnodes.md index 3c136cfc2..4adbf962d 100644 --- a/docs/guide/src/elements/vnodes.md +++ b/docs/guide/src/elements/vnodes.md @@ -56,7 +56,7 @@ With the default configuration, any Element defined within the `dioxus-html` cra ## Text Elements -Dioxus also supports a special type of Element: Text. Text Elements do not accept children, but rather just string literals denoted with double quotes. +Dioxus also supports a special type of Element: Text. Text Elements do not accept children, just a string literal denoted by double quotes. ```rust rsx! ( @@ -74,20 +74,20 @@ rsx! ( ) ``` -Text can also be formatted with any value that implements `Display`. We use [f-string formatting](https://docs.rs/fstrings/0.2.3/fstrings/) - a "coming soon" feature for stable Rust that is familiar for Python and JavaScript users: +Text can also be formatted with any value that implements `Display`. We use the same syntax as Rust [format strings](https://www.rustnote.com/blog/format_strings.html) – which will already be familiar for Python and JavaScript users: ```rust let name = "Bob"; rsx! ( "hello {name}" ) ``` -Unfortunately, you cannot drop in arbitrary expressions directly into the string literal. In the cases where we need to compute a complex value, we'll want to use `format_args!` directly. Due to specifics of how the `rsx!` macro (we'll cover later), our call to `format_args` must be contained within square braces. +Unfortunately, you cannot drop in arbitrary expressions directly into the string literal. In the cases where we need to compute a complex value, we'll want to use `format_args!` directly. Due to specifics of the `rsx!` macro (which we'll cover later), our call to `format_args` must be contained within square braces. ```rust -rsx!( {format_args!("Hello {}", if enabled { "Jack" } else { "Bob" } )] ) +rsx!( [format_args!("Hello {}", if enabled { "Jack" } else { "Bob" } )] ) ``` -Alternatively, `&str` can be included directly, though it must be inside of square braces: +Alternatively, `&str` can be included directly, though it must also be inside square braces: ```rust rsx!( "Hello ", [if enabled { "Jack" } else { "Bob" }] ) @@ -104,7 +104,7 @@ rsx! ( "hello {name}" ) ## Attributes -Every Element in your User Interface will have some sort of properties that the renderer will use when drawing to the screen. These might inform the renderer if the component should be hidden, what its background color should be, or to give it a specific name or ID. +Every Element in your user interface will have some sort of properties that the renderer will use when drawing to the screen. These might inform the renderer if the component should be hidden, what its background color should be, or to give it a specific name or ID. To do this, we use the familiar struct-style syntax that Rust provides: @@ -123,7 +123,7 @@ Each field is defined as a method on the element in the `dioxus-html` crate. Thi 1) file an issue if the attribute _should_ be enabled 2) add a custom attribute on-the-fly -To use custom attributes, simply put the attribute name in quotes followed by a colon: +To use custom attributes, simply put the attribute name in quotes: ```rust rsx!(