diff --git a/.github/workflows/publish-website.yml b/.github/workflows/publish-website.yml index a62c68e1a4a..15167502349 100644 --- a/.github/workflows/publish-website.yml +++ b/.github/workflows/publish-website.yml @@ -63,7 +63,7 @@ jobs: targets: website channelId: "${{ env.CHANNEL_ID }}" # link to the next version because that's what we care about - commentURLPath: "/next" + commentURLPath: "/docs/next" # PR information prNumber: "${{ env.PR_NUMBER }}" prBranchName: "${{ env.PR_BRANCH }}" diff --git a/README.md b/README.md index 8b8a97ff154..f4f2528687e 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@

Documentation (stable) | - Documentation (latest) + Documentation (latest) | Examples | diff --git a/examples/function_todomvc/README.md b/examples/function_todomvc/README.md index 97e9c651a3d..3e7cd06f3a3 100644 --- a/examples/function_todomvc/README.md +++ b/examples/function_todomvc/README.md @@ -6,7 +6,7 @@ This is an implementation of [TodoMVC](http://todomvc.com/) for Yew using functi ## Concepts -- Uses [`function_components`](https://yew.rs/next/concepts/function-components) +- Uses [`function_components`](https://yew.rs/docs/next/concepts/function-components) - Uses [`gloo_storage`](https://gloo-rs.web.app/docs/storage) to persist the state ## Improvements diff --git a/website/docs/advanced-topics/optimizations.md b/website/docs/advanced-topics/optimizations.md index 56cc3a3a2fd..5be06464a47 100644 --- a/website/docs/advanced-topics/optimizations.md +++ b/website/docs/advanced-topics/optimizations.md @@ -107,7 +107,7 @@ It is possible to configure release builds to be smaller using the available set `[profile.release]` section of your `Cargo.toml`. -```text +```toml, title=Cargo.toml [profile.release] # less code to include into binary panic = 'abort' diff --git a/website/docs/concepts/components/callbacks.md b/website/docs/concepts/components/callbacks.md index 9213be3bb3d..bed13179266 100644 --- a/website/docs/concepts/components/callbacks.md +++ b/website/docs/concepts/components/callbacks.md @@ -1,95 +1,9 @@ --- title: "Callbacks" -description: "ComponentLink and Callbacks" --- -## Component's `Scope<_>` API - -The component "`Scope`" is the mechanism through which components are able to create callbacks and update themselves -using messages. We obtain a reference to this by calling `link()` on the context object passed to the component. - -### `send_message` - -Sends a message to the component. -Messages are handled by the `update` method which determines whether the component should re-render. - -### `send_message_batch` - -Sends multiple messages to the component at the same time. -This is similar to `send_message` but if any of the messages cause the `update` method to return `true`, -the component will re-render after all messages in the batch have been processed. - -If the given vector is empty, this function doesn't do anything. - -### `callback` - -Create a callback that will send a message to the component when it is executed. -Under the hood, it will call `send_message` with the message returned by the provided closure. - -There is a different method called `callback_once` which accepts a `FnOnce` instead of a `Fn`. -You should use this with care though, as the resulting callback will panic if executed more than once. - -```rust -use yew::{html, Component, Context, Html}; - -enum Msg { - Text(String), -} - -struct Comp; - -impl Component for Comp { - - type Message = Msg; - type Properties = (); - - fn create(_ctx: &Context) -> Self { - Self - } - - fn view(&self, ctx: &Context) -> Html { - // Create a callback that accepts some text and sends it - // to the component as the `Msg::Text` message variant. - // highlight-next-line - let cb = ctx.link().callback(|text: String| Msg::Text(text)); - - // The previous line is needlessly verbose to make it clearer. - // It can be simplified it to this: - // highlight-next-line - let cb = ctx.link().callback(Msg::Text); - - // Will send `Msg::Text("Hello World!")` to the component. - // highlight-next-line - cb.emit("Hello World!".to_owned()); - - html! { - // html here - } - } -} -``` - -### `batch_callback` - -Create a callback that will send a batch of messages to the component when it is executed. -The difference to `callback` is that the closure passed to this method doesn't have to return a message. -Instead, the closure can return either `Vec` or `Option` where `Msg` is the component's message type. - -`Vec` is treated as a batch of messages and uses `send_message_batch` under the hood. - -`Option` calls `send_message` if it is `Some`. If the value is `None`, nothing happens. -This can be used in cases where, depending on the situation, an update isn't required. - -This is achieved using the `SendAsMessage` trait which is only implemented for these types. -You can implement `SendAsMessage` for your own types which allows you to use them in `batch_callback`. - -Like `callback`, this method also has a `FnOnce` counterpart, `batch_callback_once`. -The same restrictions apply as for `callback_once`. - ## Callbacks -_\(This might need its own short page.\)_ - Callbacks are used to communicate with services, agents, and parent components within Yew. Internally their type is just `Fn` wrapped in `Rc` to allow them to be cloned. diff --git a/website/docs/concepts/components/introduction.md b/website/docs/concepts/components/introduction.md index abc1a274b70..3c11cab0e43 100644 --- a/website/docs/concepts/components/introduction.md +++ b/website/docs/concepts/components/introduction.md @@ -91,7 +91,7 @@ impl Component for MyComponent { } ``` -For usage details, check out [the `html!` guide](./../html.md). +For usage details, check out [the `html!` guide](../html/introduction). ### Rendered diff --git a/website/docs/concepts/components/scope.md b/website/docs/concepts/components/scope.md new file mode 100644 index 00000000000..0b070cca9e1 --- /dev/null +++ b/website/docs/concepts/components/scope.md @@ -0,0 +1,87 @@ +--- +title: "Scope" +description: "Component's Scope" +--- + +## Component's `Scope<_>` API + +The component "`Scope`" is the mechanism through which components are able to create callbacks and update themselves +using messages. We obtain a reference to this by calling `link()` on the context object passed to the component. + +### `send_message` + +Sends a message to the component. +Messages are handled by the `update` method which determines whether the component should re-render. + +### `send_message_batch` + +Sends multiple messages to the component at the same time. +This is similar to `send_message` but if any of the messages cause the `update` method to return `true`, +the component will re-render after all messages in the batch have been processed. + +If the given vector is empty, this function doesn't do anything. + +### `callback` + +Create a callback that will send a message to the component when it is executed. +Under the hood, it will call `send_message` with the message returned by the provided closure. + +There is a different method called `callback_once` which accepts a `FnOnce` instead of a `Fn`. +You should use this with care though, as the resulting callback will panic if executed more than once. + +```rust +use yew::{html, Component, Context, Html}; + +enum Msg { + Text(String), +} + +struct Comp; + +impl Component for Comp { + + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + // Create a callback that accepts some text and sends it + // to the component as the `Msg::Text` message variant. + // highlight-next-line + let cb = ctx.link().callback(|text: String| Msg::Text(text)); + + // The previous line is needlessly verbose to make it clearer. + // It can be simplified it to this: + // highlight-next-line + let cb = ctx.link().callback(Msg::Text); + + // Will send `Msg::Text("Hello World!")` to the component. + // highlight-next-line + cb.emit("Hello World!".to_owned()); + + html! { + // html here + } + } +} +``` + +### `batch_callback` + +Create a callback that will send a batch of messages to the component when it is executed. +The difference to `callback` is that the closure passed to this method doesn't have to return a message. +Instead, the closure can return either `Vec` or `Option` where `Msg` is the component's message type. + +`Vec` is treated as a batch of messages and uses `send_message_batch` under the hood. + +`Option` calls `send_message` if it is `Some`. If the value is `None`, nothing happens. +This can be used in cases where, depending on the situation, an update isn't required. + +This is achieved using the `SendAsMessage` trait which is only implemented for these types. +You can implement `SendAsMessage` for your own types which allows you to use them in `batch_callback`. + +Like `callback`, this method also has a `FnOnce` counterpart, `batch_callback_once`. +The same restrictions apply as for `callback_once`. diff --git a/website/docs/concepts/html/fragments.md b/website/docs/concepts/html/fragments.md index 8673d2c864a..9347643f831 100644 --- a/website/docs/concepts/html/fragments.md +++ b/website/docs/concepts/html/fragments.md @@ -2,9 +2,15 @@ title: "Fragments" --- +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + The `html!` macro always requires a single root node. In order to get around this restriction, you can use an "empty tag" (these are also called "fragments"). + + + ```rust use yew::html; @@ -14,15 +20,24 @@ html! {

}; + ``` -```rust ,compile_fail +
+ + + +```rust, compile_fail use yew::html; -/* error: only one root html element allowed */ +// error: only one root html element allowed html! {

}; + ``` + +
+
diff --git a/website/docs/concepts/wasm-bindgen/introduction.md b/website/docs/concepts/wasm-bindgen/introduction.md index 16af569e554..6ca37613180 100644 --- a/website/docs/concepts/wasm-bindgen/introduction.md +++ b/website/docs/concepts/wasm-bindgen/introduction.md @@ -1,13 +1,14 @@ --- -title: "Project Setup" -sidebar_label: Introduction +title: "`wasm-bindgen`" +sidebar_label: wasm-bindgen +slug: /concepts/wasm-bindgen --- [`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) is a library and tool to facilitate high-level interactions between Wasm modules and JavaScript; it is built with Rust by [The Rust and WebAssembly Working Group](https://rustwasm.github.io/). -Yew builds off wasm-bindgen and specifically uses the following of its crates: +Yew builds off `wasm-bindgen` and specifically uses the following of its crates: - [`js-sys`](https://crates.io/crates/js-sys) - [`wasm-bindgen`](https://crates.io/crates/wasm-bindgen) @@ -28,14 +29,14 @@ over using `wasm-bindgen`. ## [`wasm-bindgen`](https://crates.io/crates/wasm-bindgen) This crate provides many of the building blocks for the rest of the crates above. In this section we -are only going to cover two main areas of the `wasm-bindgen` crate and that is the macro and some of -the types / traits you will see pop up again and again. +are only going to cover two main areas of the `wasm-bindgen` crate and that is the macro and some +types / traits you will see pop up again and again. ### `#[wasm_bindgen]` macro The `#[wasm_bindgen]` macro, in a high level view, is your translator between Rust and JavaScript, it allows you to describe imported JavaScript types in terms of Rust and vice versa. Using this macro -is more advanced and you shouldn't need to reach for it unless you are trying to interop with an +is more advanced, and you shouldn't need to reach for it unless you are trying to interop with an external JavaScript library. The `js-sys` and `web-sys` crates are essentially imported types using this macro for JavaScript types and the browser API respectively. @@ -80,7 +81,7 @@ _This example was adapted from [1.2 Using console.log of The `wasm-bindgen` Guid Inheritance between JavaScript classes is a big part of the language and is a major part of how the Document Object Model (DOM). When types are imported using `wasm-bindgen` you can -also add attributes that describe it's inheritance. +also add attributes that describe its inheritance. In Rust this inheritance is simulated using the [`Deref`](https://doc.rust-lang.org/std/ops/trait.Deref.html) and [`AsRef`](https://doc.rust-lang.org/std/convert/trait.AsRef.html) traits. An example of this @@ -122,7 +123,7 @@ _[`JsValue` documentation](https://rustwasm.github.io/wasm-bindgen/api/wasm_bind Rust has a strong type system and JavaScript...doesn't 😞 So in order for Rust to maintain these strong types but still be convenient the web assembly group came up with a pretty neat trait `JsCast`. -Its job is to help you move from one JavaScript "type" to another, which sounds vague but it means +Its job is to help you move from one JavaScript "type" to another, which sounds vague, but it means that if you have one type which you know is really another then you can use the functions of `JsCast` to jump from one type to the other. It's a nice trait to get to know when working with `web-sys`, `wasm_bindgen`, `js-sys` - you'll notice lots of types will implement `JsCast` from those crates. @@ -132,10 +133,10 @@ unsure what type a certain object is you can try to cast it which returns possib [`Option`](https://doc.rust-lang.org/std/option/enum.Option.html) and [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html). -A common example of this in [`web-sys`](wasm-bindgen/web-sys) is when you are trying to get the +A common example of this in [`web-sys`](web-sys) is when you are trying to get the target of an event, you might know what the target element is but the [`web_sys::Event`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Event.html) API will always return an [`Option`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Event.html#method.target) -so you will need to cast it to the element type so you can call it's methods. +so you will need to cast it to the element type. so you can call its methods. ```rust // need to import the trait. diff --git a/website/docs/concepts/wasm-bindgen/web-sys.md b/website/docs/concepts/wasm-bindgen/web-sys.md index db19edd6cdf..16d2845804e 100644 --- a/website/docs/concepts/wasm-bindgen/web-sys.md +++ b/website/docs/concepts/wasm-bindgen/web-sys.md @@ -1,3 +1,5 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; The [`web-sys` crate](https://crates.io/crates/web-sys) provides bindings for Web APIs. This is procedurally generated from browser WebIDL which is why some of the names are so long and why @@ -86,6 +88,9 @@ The two code blocks below do essentially the same thing, the first is using `Nod the second is using [`JsCast::dyn_into`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html#method.dyn_into) on the `web_sys::Node` returned from `NodeRef::get`. + + + ```rust use web_sys::HtmlInputElement; use yew::NodeRef; @@ -97,6 +102,9 @@ fn with_node_ref_cast(node_ref: NodeRef) { } ``` + + + ```rust use wasm_bindgen::JsCast; use web_sys::HtmlInputElement; @@ -111,6 +119,10 @@ fn with_jscast(node_ref: NodeRef) { } ``` + + + + ## JavaScript example to Rust This section is to help show that any examples that use JavaScript to interact with the Web APIs diff --git a/website/docs/getting-started/build-a-sample-app.md b/website/docs/getting-started/build-a-sample-app.md index b1444392a6f..0b6509cc27b 100644 --- a/website/docs/getting-started/build-a-sample-app.md +++ b/website/docs/getting-started/build-a-sample-app.md @@ -40,7 +40,7 @@ edition = "2018" [dependencies] # you can check the latest version here: https://crates.io/crates/yew -yew = "0.17" +yew = "0.19" ``` ### Update main.rs @@ -53,7 +53,7 @@ The line `yew::start_app::()` inside `main()` starts your application and If you would like to start your application with any dynamic properties, you can instead use `yew::start_app_with_props::(..)`. ::: -```rust ,no_run +```rust ,no_run, title=main.rs use yew::prelude::*; enum Msg { @@ -106,7 +106,7 @@ fn main() { Finally, add an `index.html` file in the root directory of your app. -```html +```html, title=index.html diff --git a/website/docs/getting-started/project-setup/introduction.md b/website/docs/getting-started/project-setup/introduction.md index 39c95e87deb..6f03a94d772 100644 --- a/website/docs/getting-started/project-setup/introduction.md +++ b/website/docs/getting-started/project-setup/introduction.md @@ -10,7 +10,7 @@ Your local development environment will need a couple of tools to compile, build ## Installing Rust -To install Rust follow the [official instructions](https://www.rust-lang.org/tools/install). +To install Rust, follow the [official instructions](https://www.rust-lang.org/tools/install). :::important The minimum supported Rust version (MSRV) for Yew is `1.49.0`. Older versions can cause unexpected issues accompanied by incomprehensible error messages. diff --git a/website/docs/intro.md b/website/docs/intro.md deleted file mode 100644 index 5e8bc3d296f..00000000000 --- a/website/docs/intro.md +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: "Introduction" -slug: / ---- - -## What is Yew? - -**Yew** is a modern [Rust](https://www.rust-lang.org/) framework for creating multi-threaded -front-end web apps using [WebAssembly](https://webassembly.org/). - -* It features a **component-based** framework which makes it easy to create interactive UIs. -Developers who have experience with frameworks like [React](https://reactjs.org/) and -[Elm](https://elm-lang.org/) should feel quite at home when using Yew. -* It achieves **great performance** by minimizing DOM API calls and by helping developers to easily -offload processing to background threads using web workers. -* It supports **JavaScript interoperability**, allowing developers to leverage NPM packages and -integrate with existing JavaScript applications. - -### Join Us 😊 - -* You can report bugs and discuss features on the [GitHub issues page](https://github.com/yewstack/yew/issues) -* We love pull requests. Check out the [good first issues](https://github.com/yewstack/yew/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) -if you'd like to help out! -* Our [Discord chat](https://discord.gg/VQck8X4) is very active and is a great place to ask -questions - -### Ready to dive in? - -Click the link below to learn how to build your first Yew app and learn from community-built example -projects - -[Getting started](./../getting-started/project-setup.md) - -### Still not convinced? - -This project is built on cutting edge technology and is great for developers who like to develop the -foundational projects of tomorrow. We think that the speed and reliability of the technologies on -which Yew is built are set to become the standard for fast and resilient web applications of the -future. - -#### Wait, why WebAssembly? - -WebAssembly _\(Wasm\)_ is a portable low-level language that Rust can compile to. It runs at native -speeds in the browser and is interoperable with JavaScript and supported in all major modern -browsers. For ideas on how to get the most out of WebAssembly for your app, check out this list of -[use cases](https://webassembly.org/docs/use-cases/). - -It should be noted that using Wasm is not \(yet\) a silver bullet for improving the performance of -web apps. As of the present, using DOM APIs from WebAssembly is still slower than calling them -directly from JavaScript. This is a temporary issue which the -[WebAssembly Interface Types](https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md) proposal aims to resolve. If you would like to learn more, check out this -[excellent article](https://hacks.mozilla.org/2019/08/webassembly-interface-types/) (from Mozilla) -which describes the proposal. - -#### Ok, but why Rust? - -Rust is blazing fast and reliable with its rich type system and ownership model. It has a tough -learning curve but is well worth the effort. Rust has been voted the most loved programming -language in Stack Overflow's Developer Survey five years in a row: -[2016](https://insights.stackoverflow.com/survey/2016#technology-most-loved-dreaded-and-wanted), -[2017](https://insights.stackoverflow.com/survey/2017#most-loved-dreaded-and-wanted), -[2018](https://insights.stackoverflow.com/survey/2018#technology-_-most-loved-dreaded-and-wanted-languages), -[2019](https://insights.stackoverflow.com/survey/2019#technology-_-most-loved-dreaded-and-wanted-languages) -and [2020](https://insights.stackoverflow.com/survey/2020#most-loved-dreaded-and-wanted). - -Rust also helps developers write safer code with its rich type system and ownership model. Say -goodbye to hard to track down race condition bugs in JavaScript! In fact, with Rust, most of your -bugs will be caught by the compiler before your app even runs. And don't worry, when your app does -run into an error, you can still get full stack-traces for your Rust code in the browser console. - -#### Alternatives? - -We love to share ideas with other projects and believe we can all help each other reach the full -potential of this exciting new technology. If you're not into Yew, you might like the following -projects: - -* [Percy](https://github.com/chinedufn/percy) - _"A modular toolkit for building isomorphic web apps -with Rust + WebAssembly"_ -* [Seed](https://github.com/seed-rs/seed) - _"A Rust framework for creating web apps"_ diff --git a/website/docs/tutorial.md b/website/docs/tutorial.md index 9e39339eb8c..e4d88b03487 100644 --- a/website/docs/tutorial.md +++ b/website/docs/tutorial.md @@ -451,10 +451,10 @@ element returned by the `Iterator` with the `{ for ... }` syntax. Remember the `use_state` used earlier? That is a special function, called a "hook". Hooks are used to "hook" into lifecycle of a function component and perform actions. You can learn more about this hook, and others -[here](/next/concepts/function-components/pre-defined-hooks#use_state) +[here](concepts/function-components/pre-defined-hooks#use_state) :::note -Struct components act differently. See [the documentation](/concepts/components) to learn about those. +Struct components act differently. See [the documentation](concepts/components) to learn about those. ::: ## Fetching data (using external REST API) @@ -576,9 +576,9 @@ to learn how to add style sheets. ### More libraries Our app made use of only a few external dependencies. There are lots of crates out there that can be used. -See [external libraries](/next/more/external-libs) for more details. +See [external libraries](more/external-libs) for more details. ### Learning more about Yew -Read our [official documentation](/next). It explains a lot of concepts in much more details. +Read our [official documentation](/docs). It explains a lot of concepts in much more details. To learn more about our the Yew API, see our [API docs](https://docs.rs/yew). diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 28e2580a12c..351cc233c33 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -26,8 +26,10 @@ module.exports = { position: 'left', }, { - to: '/', - label: 'Docs' + type: 'doc', + position: 'left', + docId: 'getting-started/project-setup/introduction', + label: 'Docs', }, { href: 'https://docs.rs/yew', @@ -108,7 +110,7 @@ module.exports = { docs: { sidebarPath: require.resolve('./sidebars.js'), editUrl: 'https://github.com/yewstack/yew/blob/master/website/', - routeBasePath: '/', + routeBasePath: '/docs', }, theme: { customCss: require.resolve('./src/css/custom.css'), @@ -116,4 +118,19 @@ module.exports = { }, ], ], + plugins: [ + [ + '@docusaurus/plugin-client-redirects', + { + redirects: [ + // this handles the redirect from `/next` -> to the (current) first item in the docs sidebar + // note: if the first item is changed, it should be reflected here + { + to: '/docs/next/getting-started/project-setup/introduction', // string + from: ['/docs/next'], // string | string[] + }, + ], + }, + ], + ], }; diff --git a/website/package-lock.json b/website/package-lock.json index 88992634803..a0a22df5741 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@docusaurus/core": "^2.0.0-beta.9", + "@docusaurus/plugin-client-redirects": "^2.0.0-beta.9", "@docusaurus/plugin-google-analytics": "*", "@docusaurus/preset-classic": "^2.0.0-beta.9", "@docusaurus/theme-search-algolia": "*", @@ -2098,6 +2099,31 @@ "integrity": "sha512-FPy/Wa4dH0QvtR92/JJi7t2SB1tnySuSGVNf4e0llArZ4E7+vzTAmd4qVfzBuS+LMj/M6woKHLn+zKKUkfaZOw==", "dev": true }, + "node_modules/@docusaurus/plugin-client-redirects": { + "version": "2.0.0-beta.9", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.0.0-beta.9.tgz", + "integrity": "sha512-rksXkA9keq9jsgxp0ZLX+MY68ViDhCpdLcxNCNmZl2c9XA2v8AQN4HU2e6Dq+OEefk/ltQYLIbfTa2Hj/ZEwzQ==", + "dependencies": { + "@docusaurus/core": "2.0.0-beta.9", + "@docusaurus/types": "2.0.0-beta.9", + "@docusaurus/utils": "2.0.0-beta.9", + "@docusaurus/utils-common": "2.0.0-beta.9", + "@docusaurus/utils-validation": "2.0.0-beta.9", + "chalk": "^4.1.2", + "eta": "^1.12.3", + "fs-extra": "^10.0.0", + "globby": "^11.0.2", + "lodash": "^4.17.20", + "tslib": "^2.3.1" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.8.4 || ^17.0.0", + "react-dom": "^16.8.4 || ^17.0.0" + } + }, "node_modules/@docusaurus/plugin-content-blog": { "version": "2.0.0-beta.9", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.9.tgz", @@ -14287,6 +14313,24 @@ "integrity": "sha512-FPy/Wa4dH0QvtR92/JJi7t2SB1tnySuSGVNf4e0llArZ4E7+vzTAmd4qVfzBuS+LMj/M6woKHLn+zKKUkfaZOw==", "dev": true }, + "@docusaurus/plugin-client-redirects": { + "version": "2.0.0-beta.9", + "resolved": "https://registry.npmjs.org/@docusaurus/plugin-client-redirects/-/plugin-client-redirects-2.0.0-beta.9.tgz", + "integrity": "sha512-rksXkA9keq9jsgxp0ZLX+MY68ViDhCpdLcxNCNmZl2c9XA2v8AQN4HU2e6Dq+OEefk/ltQYLIbfTa2Hj/ZEwzQ==", + "requires": { + "@docusaurus/core": "2.0.0-beta.9", + "@docusaurus/types": "2.0.0-beta.9", + "@docusaurus/utils": "2.0.0-beta.9", + "@docusaurus/utils-common": "2.0.0-beta.9", + "@docusaurus/utils-validation": "2.0.0-beta.9", + "chalk": "^4.1.2", + "eta": "^1.12.3", + "fs-extra": "^10.0.0", + "globby": "^11.0.2", + "lodash": "^4.17.20", + "tslib": "^2.3.1" + } + }, "@docusaurus/plugin-content-blog": { "version": "2.0.0-beta.9", "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-2.0.0-beta.9.tgz", diff --git a/website/package.json b/website/package.json index 06d276f03f6..b12e875fe88 100644 --- a/website/package.json +++ b/website/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@docusaurus/core": "^2.0.0-beta.9", + "@docusaurus/plugin-client-redirects": "^2.0.0-beta.9", "@docusaurus/plugin-google-analytics": "*", "@docusaurus/preset-classic": "^2.0.0-beta.9", "@docusaurus/theme-search-algolia": "*", diff --git a/website/sidebars.js b/website/sidebars.js index d90d6e371d4..17078dd6f22 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -13,122 +13,122 @@ module.exports = { // By default, Docusaurus generates a sidebar from the docs folder structure // conceptsSidebar: [{type: 'autogenerated', dirName: '.'}], - // But you can create a sidebar manually - sidebar: [ - "intro", - { - type: "category", - label: "Getting Started", - items: [ - "getting-started/build-a-sample-app", - "getting-started/examples", - "getting-started/starter-templates", + // But you can create a sidebar manually + sidebar: [ { - type: "category", - label: "Project Setup", - items: [ - "getting-started/project-setup/introduction", - "getting-started/project-setup/using-trunk", - "getting-started/project-setup/using-wasm-pack", - ], + type: 'category', + label: 'Getting Started', + items: [ + { + type: 'category', + label: 'Project Setup', + items: [ + 'getting-started/project-setup/introduction', + 'getting-started/project-setup/using-trunk', + 'getting-started/project-setup/using-wasm-pack', + ] + }, + "getting-started/build-a-sample-app", + "getting-started/examples", + "getting-started/starter-templates", + ], }, - ], - }, - { - type: "category", - label: "Concepts", - items: [ { - type: "category", - label: "wasm-bindgen", - items: [ - "concepts/wasm-bindgen/introduction", - "concepts/wasm-bindgen/web-sys" - ], + type: "category", + label: "Concepts", + items: [ + { + type: "category", + label: "wasm-bindgen", + items: [ + "concepts/wasm-bindgen/introduction", + "concepts/wasm-bindgen/web-sys", + ] + }, + { + type: "category", + label: "Components", + items: [ + "concepts/components/introduction", + "concepts/components/callbacks", + "concepts/components/scope", + "concepts/components/properties", + "concepts/components/children", + "concepts/components/refs" + ], + }, + { + type: "category", + label: "HTML", + items: [ + "concepts/html/introduction", + "concepts/html/components", + "concepts/html/elements", + "concepts/html/events", + "concepts/html/classes", + "concepts/html/fragments", + "concepts/html/lists", + "concepts/html/literals-and-expressions" + ] + }, + { + type: "category", + label: "Function Components", + items: [ + "concepts/function-components/introduction", + "concepts/function-components/attribute", + "concepts/function-components/pre-defined-hooks", + "concepts/function-components/custom-hooks", + ] + }, + "concepts/agents", + "concepts/contexts", + "concepts/router", + ] }, { - type: "category", - label: "Components", - items: [ - "concepts/components/introduction", - "concepts/components/callbacks", - "concepts/components/properties", - "concepts/components/children", - "concepts/components/refs", - ], + type: 'category', + label: 'Advanced topics', + items: [ + "advanced-topics/how-it-works", + "advanced-topics/optimizations", + "advanced-topics/portals", + ] }, { - type: "category", - label: "HTML", - items: [ - "concepts/html/introduction", - "concepts/html/components", - "concepts/html/elements", - "concepts/html/events", - "concepts/html/classes", - "concepts/html/fragments", - "concepts/html/lists", - "concepts/html/literals-and-expressions", - ], + type: 'category', + label: 'More', + items: [ + "more/debugging", + "more/development-tips", + "more/external-libs", + "more/css", + "more/testing", + "more/roadmap", + "more/wasm-build-tools" + ] }, { - type: "category", - label: "Function Components", - items: [ - "concepts/function-components/introduction", - "concepts/function-components/attribute", - "concepts/function-components/pre-defined-hooks", - "concepts/function-components/custom-hooks", - ], + type: "category", + label: "Migration guides", + items: [ + { + type: "category", + label: "yew", + items: ["migration-guides/yew/from-0_18_0-to-0_19_0"], + }, + { + type: "category", + label: "yew-agent", + items: ["migration-guides/yew-agent/from-0_0_0-to-0_1_0"], + }, + { + type: "category", + label: "yew-router", + items: ["migration-guides/yew-router/from-0_15_0-to-0_16_0"], + }, + ], }, - "concepts/agents", - "concepts/contexts", - "concepts/router", - ], - }, - { - type: "category", - label: "Advanced topics", - items: [ - "advanced-topics/how-it-works", - "advanced-topics/optimizations", - "advanced-topics/portals", - ], - }, - { - type: "category", - label: "More", - items: [ - "more/debugging", - "more/development-tips", - "more/external-libs", - "more/css", - "more/testing", - "more/roadmap", - "more/wasm-build-tools", - ], - }, - { - type: "category", - label: "Migration guides", - items: [ - { - type: "category", - label: "yew", - items: ["migration-guides/yew/from-0_18_0-to-0_19_0"], - }, - { - type: "category", - label: "yew-agent", - items: ["migration-guides/yew-agent/from-0_0_0-to-0_1_0"], - }, - { - type: "category", - label: "yew-router", - items: ["migration-guides/yew-router/from-0_15_0-to-0_16_0"], - }, - ], - }, - "tutorial", - ], + "tutorial" + ], }; diff --git a/website/versioned_docs/version-0.18.0/intro.md b/website/src/pages/index.mdx similarity index 66% rename from website/versioned_docs/version-0.18.0/intro.md rename to website/src/pages/index.mdx index 01bc9a4c616..5a564d08087 100644 --- a/website/versioned_docs/version-0.18.0/intro.md +++ b/website/src/pages/index.mdx @@ -1,82 +1,77 @@ ---- -title: "Introduction" -slug: / ---- +# What is Yew? -## What is Yew? - -**Yew** is a modern [Rust](https://www.rust-lang.org/) framework for creating multi-threaded +**Yew** is a modern [Rust](https://www.rust-lang.org/) framework for creating multi-threaded front-end web apps using [WebAssembly](https://webassembly.org/). -* It features a **component-based** framework which makes it easy to create interactive UIs. -Developers who have experience with frameworks like [React](https://reactjs.org/) and -[Elm](https://elm-lang.org/) should feel quite at home when using Yew. -* It achieves **great performance** by minimizing DOM API calls and by helping developers to easily -offload processing to background threads using web workers. -* It supports **JavaScript interoperability**, allowing developers to leverage NPM packages and -integrate with existing JavaScript applications. +* It features a **component-based** framework which makes it easy to create interactive UIs. + Developers who have experience with frameworks like [React](https://reactjs.org/) and + [Elm](https://elm-lang.org/) should feel quite at home when using Yew. +* It achieves **great performance** by minimizing DOM API calls and by helping developers to easily + offload processing to background threads using web workers. +* It supports **JavaScript interoperability**, allowing developers to leverage NPM packages and + integrate with existing JavaScript applications. -### Join Us 😊 +## Join Us 😊 * You can report bugs and discuss features on the [GitHub issues page](https://github.com/yewstack/yew/issues) -* We love pull requests. Check out the [good first issues](https://github.com/yewstack/yew/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) -if you'd like to help out! -* Our [Discord chat](https://discord.gg/VQck8X4) is very active and is a great place to ask -questions +* We love pull requests. Check out the [good first issues](https://github.com/yewstack/yew/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) + if you'd like to help out! +* Our [Discord chat](https://discord.gg/VQck8X4) is very active and is a great place to ask + questions -### Ready to dive in? +## Ready to dive in? -Click the link below to learn how to build your first Yew app and learn from community-built example +Click the link below to learn how to build your first Yew app and learn from community-built example projects -[Getting started](getting-started/project-setup.md) +[Getting started](getting-started/project-setup/introduction) -### Still not convinced? +## Still not convinced? -This project is built on cutting edge technology and is great for developers who like to develop the +This project is built on cutting edge technology and is great for developers who like to develop the foundational projects of tomorrow. We think that the speed and reliability of the technologies on which Yew is built are set to become the standard for fast and resilient web applications of the -future. +future. -#### Wait, why WebAssembly? +### Wait, why WebAssembly? -WebAssembly _\(Wasm\)_ is a portable low-level language that Rust can compile to. It runs at native -speeds in the browser and is interoperable with JavaScript and supported in all major modern -browsers. For ideas on how to get the most out of WebAssembly for your app, check out this list of +WebAssembly _\(Wasm\)_ is a portable low-level language that Rust can compile to. It runs at native +speeds in the browser and is interoperable with JavaScript and supported in all major modern +browsers. For ideas on how to get the most out of WebAssembly for your app, check out this list of [use cases](https://webassembly.org/docs/use-cases/). -It should be noted that using Wasm is not \(yet\) a silver bullet for improving the performance of -web apps. As of the present, using DOM APIs from WebAssembly is still slower than calling them -directly from JavaScript. This is a temporary issue which the -[WebAssembly Interface Types](https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md) proposal aims to resolve. If you would like to learn more, check out this -[excellent article](https://hacks.mozilla.org/2019/08/webassembly-interface-types/) (from Mozilla) +It should be noted that using Wasm is not \(yet\) a silver bullet for improving the performance of +web apps. As of the present, using DOM APIs from WebAssembly is still slower than calling them +directly from JavaScript. This is a temporary issue which the +[WebAssembly Interface Types](https://github.com/WebAssembly/interface-types/blob/master/proposals/interface-types/Explainer.md) proposal aims to resolve. If you would like to learn more, check out this +[excellent article](https://hacks.mozilla.org/2019/08/webassembly-interface-types/) (from Mozilla) which describes the proposal. -#### Ok, but why Rust? +### Ok, but why Rust? -Rust is blazing fast and reliable with its rich type system and ownership model. It has a tough -learning curve but is well worth the effort. Rust has been voted the most loved programming -language in Stack Overflow's Developer Survey six years in a row: -[2016](https://insights.stackoverflow.com/survey/2016#technology-most-loved-dreaded-and-wanted), -[2017](https://insights.stackoverflow.com/survey/2017#most-loved-dreaded-and-wanted), -[2018](https://insights.stackoverflow.com/survey/2018#technology-_-most-loved-dreaded-and-wanted-languages), -[2019](https://insights.stackoverflow.com/survey/2019#technology-_-most-loved-dreaded-and-wanted-languages), -[2020](https://insights.stackoverflow.com/survey/2020#most-loved-dreaded-and-wanted) and +Rust is blazing fast and reliable with its rich type system and ownership model. It has a tough +learning curve but is well worth the effort. Rust has been voted the most loved programming +language in Stack Overflow's Developer Survey six years in a row: +[2016](https://insights.stackoverflow.com/survey/2016#technology-most-loved-dreaded-and-wanted), +[2017](https://insights.stackoverflow.com/survey/2017#most-loved-dreaded-and-wanted), +[2018](https://insights.stackoverflow.com/survey/2018#technology-_-most-loved-dreaded-and-wanted-languages), +[2019](https://insights.stackoverflow.com/survey/2019#technology-_-most-loved-dreaded-and-wanted-languages), +[2020](https://insights.stackoverflow.com/survey/2020#most-loved-dreaded-and-wanted) and [2021](https://insights.stackoverflow.com/survey/2021/#technology-most-loved-dreaded-and-wanted). -Rust also helps developers write safer code with its rich type system and ownership model. Say -goodbye to hard to track down race condition bugs in JavaScript! In fact, with Rust, most of your -bugs will be caught by the compiler before your app even runs. And don't worry, when your app does +Rust also helps developers write safer code with its rich type system and ownership model. Say +goodbye to hard to track down race condition bugs in JavaScript! In fact, with Rust, most of your +bugs will be caught by the compiler before your app even runs. And don't worry, when your app does run into an error, you can still get full stack-traces for your Rust code in the browser console. -#### Alternatives? +### Alternatives? -We love to share ideas with other projects and believe we can all help each other reach the full -potential of this exciting new technology. If you're not into Yew, you might like the following +We love to share ideas with other projects and believe we can all help each other reach the full +potential of this exciting new technology. If you're not into Yew, you might like the following projects: -* [Percy](https://github.com/chinedufn/percy) - _"A modular toolkit for building isomorphic web apps -with Rust + WebAssembly"_ +* [Percy](https://github.com/chinedufn/percy) - _"A modular toolkit for building isomorphic web apps + with Rust + WebAssembly"_ * [Seed](https://github.com/seed-rs/seed) - _"A Rust framework for creating web apps"_ * [Perseus](https://github.com/arctic-hen7/perseus) - _"A high-level web development framework for Rust with full support for server-side rendering and static generation"_ * [Sycamore](https://github.com/sycamore-rs/sycamore) - _"A reactive library for creating web apps in Rust and WebAssembly"_ diff --git a/website/versioned_docs/version-0.18.0/concepts/components.md b/website/versioned_docs/version-0.18.0/concepts/components/introduction.md similarity index 99% rename from website/versioned_docs/version-0.18.0/concepts/components.md rename to website/versioned_docs/version-0.18.0/concepts/components/introduction.md index 9999bd105f2..22fd283ad8c 100644 --- a/website/versioned_docs/version-0.18.0/concepts/components.md +++ b/website/versioned_docs/version-0.18.0/concepts/components/introduction.md @@ -67,7 +67,7 @@ impl Component for MyComponent { } ``` -For usage details, check out [the `html!` guide](html.md). +For usage details, check out [the `html!` guide](../html/introduction.md). ### Rendered diff --git a/website/versioned_docs/version-0.18.0/concepts/html.md b/website/versioned_docs/version-0.18.0/concepts/html/introduction.md similarity index 99% rename from website/versioned_docs/version-0.18.0/concepts/html.md rename to website/versioned_docs/version-0.18.0/concepts/html/introduction.md index c805ab82462..a584410cf58 100644 --- a/website/versioned_docs/version-0.18.0/concepts/html.md +++ b/website/versioned_docs/version-0.18.0/concepts/html/introduction.md @@ -13,7 +13,7 @@ The `html!` macro allows you to write HTML and SVG code declaratively. It is sim **Important notes** 1. The `html!` macro only accepts a single root HTML node \(this obstacle is easily overcome by - [using fragments or iterators](html/lists.md)\) + [using fragments or iterators](../html/lists.md)\) 2. An empty `html! {}` invocation is valid and will not render anything 3. Literals must always be wrapped in quotes as well as braces (i.e. `html! {

{"Hello, World"}

}` is valid, but not `html! {

Hello, World

}` or diff --git a/website/versioned_docs/version-0.18.0/concepts/services.md b/website/versioned_docs/version-0.18.0/concepts/services/introduction.md similarity index 100% rename from website/versioned_docs/version-0.18.0/concepts/services.md rename to website/versioned_docs/version-0.18.0/concepts/services/introduction.md diff --git a/website/versioned_docs/version-0.18.0/concepts/wasm-bindgen.md b/website/versioned_docs/version-0.18.0/concepts/wasm-bindgen/wasm-bindgen.md similarity index 100% rename from website/versioned_docs/version-0.18.0/concepts/wasm-bindgen.md rename to website/versioned_docs/version-0.18.0/concepts/wasm-bindgen/wasm-bindgen.md diff --git a/website/versioned_docs/version-0.18.0/getting-started/project-setup.md b/website/versioned_docs/version-0.18.0/getting-started/project-setup/introduction.md similarity index 93% rename from website/versioned_docs/version-0.18.0/getting-started/project-setup.md rename to website/versioned_docs/version-0.18.0/getting-started/project-setup/introduction.md index 4390b9837e9..5879e5b89a7 100644 --- a/website/versioned_docs/version-0.18.0/getting-started/project-setup.md +++ b/website/versioned_docs/version-0.18.0/getting-started/project-setup/introduction.md @@ -32,7 +32,7 @@ It can bundle assets for your app and even ships with a Sass compiler. All of our examples are built with Trunk. -[Getting started with `trunk`](project-setup/using-trunk.md) +[Getting started with `trunk`](../project-setup/using-trunk.md) ### [**`wasm-pack`**](https://rustwasm.github.io/docs/wasm-pack/) @@ -41,20 +41,20 @@ together with the [`wasm-pack-plugin`](https://github.com/wasm-tool/wasm-pack-pl The primary purpose of `wasm-pack` is building Wasm libraries for use in JavaScript. Because of this, it can only build libraries and doesn't provide useful tools like a development server or automatic rebuilds. -[Get started with `wasm-pack`](project-setup/using-wasm-pack.md) +[Get started with `wasm-pack`](../project-setup/using-wasm-pack.md) ### [**`cargo-web`**](https://github.com/koute/cargo-web) This was the best preferred tool to use before the creation of `wasm-bindgen`. -[Getting started with `cargo web`](project-setup/using-cargo-web.md) +[Getting started with `cargo web`](../project-setup/using-cargo-web.md) ### Comparison | | `trunk` | `wasm-pack` | `cargo-web` | | ----------------------------- | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | Project Status | Actively maintained | Actively maintained by the [Rust / Wasm Working Group](https://rustwasm.github.io) | No Github activity for over 6 months | -| Dev Experience | Just works! Batteries included, no external dependencies needed. | Bare-bones. You'll need to write some scripts to streamline the experience or use the webpack plugin. | Works great for code but needs separate asset pipeline. | +| Dev Experience | Just works! Batteries included, no external dependencies needed. | Bare-bones. You'll need to write some scripts to streamline the experience or use the webpack plugin. | Works great for code but needs separate asset pipeline. | | Local Server | Supported | Only with webpack plugin | Supported | | Auto rebuild on local changes | Supported | Only with webpack plugin | Supported | | Asset handling | Supported | Only with webpack plugin | Static assets only | @@ -62,4 +62,4 @@ This was the best preferred tool to use before the creation of `wasm-bindgen`. | Supported Targets |
  • wasm32-unknown-unknown
|
  • wasm32-unknown-unknown
|
  • wasm32-unknown-unknown
  • wasm32-unknown-emscripten
  • asmjs-unknown-emscripten
| | `web-sys` | Compatible | Compatible | Incompatible | | `stdweb` | Incompatible | Compatible | Compatible | -| Example Usage | [Sample app](./build-a-sample-app.md) | [Starter template](https://github.com/yewstack/yew-wasm-pack-minimal) | [Build script](https://www.github.com/yewstack/yew/tree/master/packages/yew-stdweb/examples) for `yew-stdweb` examples | +| Example Usage | [Sample app](../build-a-sample-app.md) | [Starter template](https://github.com/yewstack/yew-wasm-pack-minimal) | [Build script](https://www.github.com/yewstack/yew/tree/master/packages/yew-stdweb/examples) for `yew-stdweb` examples | diff --git a/website/versioned_docs/version-0.19.0/advanced-topics/how-it-works.md b/website/versioned_docs/version-0.19.0/advanced-topics/how-it-works.md new file mode 100644 index 00000000000..16caea78ff7 --- /dev/null +++ b/website/versioned_docs/version-0.19.0/advanced-topics/how-it-works.md @@ -0,0 +1,71 @@ +--- +title: "How it works" +description: "Low level details about the framework" +--- + +# Low-level library internals + +## Under the hood of the `html!` macro + +The `html!` macro turns code written in a custom HTML-like syntax into valid Rust code. Using this +macro is not necessary for developing Yew applications, but it is recommended. The code generated +by this macro makes use of the public Yew library API which can be used directly if you wish. Note +that some methods used are undocumented intentionally to avoid accidental misuse. With each +update of `yew-macro`, the generated code will be more efficient and handle any breaking changes +without many (if any) modifications to the `html!` syntax. + +Because the `html!` macro allows you to write code in a declarative style, your UI layout code will +closely match the HTML that is generated to the page. This becomes increasingly useful as your +application gets more interactive and your codebase gets larger. Rather than manually writing the +all of the code to manipulate the DOM yourself, the macro will handle it for you. + +Using the `html!` macro can feel pretty magic, but it has nothing to hide. If you're curious about +how it works, try expanding the `html!` macro calls in your program. There's a useful command called +`cargo expand` which allows you to see the expansion of Rust macros. `cargo expand` isn't shipped with +`cargo` by default so you'll need to install it with `cargo install cargo-expand` if you haven't +already. + +Note that when viewing expanded macro code, you're likely to encounter unusually verbose code. The +reason is because generated code can sometimes clash with other code in an application. In order +to prevent issues, `proc_macro` "hygiene" is adhered to. Some examples include: + +1. Instead of using `yew::` the macro generates `::yew::` to make sure that the +Yew package is referenced correctly. This is also why `::alloc::vec::Vec::new()` is called instead +of just `Vec::new()`. +2. Due to potential trait method name collisions, `` is used to make sure that we're using items from the right trait. + +## What is a virtual DOM? + +The DOM ("document object model") is a representation of the HTML content that is managed by the browser +for your web page. A "virtual" DOM is simply a copy of the DOM that is held in application memory. Managing +a virtual DOM results in a higher memory overhead, but allows for batching and faster reads by avoiding +or delaying the use of browser APIs. + +Having a copy of the DOM in memory can be really helpful for libraries which promote the use of +declarative UIs. Rather than needing specific code for describing how the DOM should be modified +in response to a user event, the library can use a generalized approach with DOM "diffing". When a Yew +component is updated and wants to change how it is rendered, the Yew library will build a second copy +of the virtual DOM and directly compare to a virtual DOM which mirrors what is currently on screen. +The "diff" (or difference) between the two can be broken down into incremental updates and applied in +a batch with browser APIs. Once the updates are applied, the old virtual DOM copy is discarded and the +new copy is saved for future diff checks. + +This "diff" algorithm can be optimized over time to improve the performance of complex applications. +Since Yew applications are run with WebAssembly, we believe that Yew has a competitive edge to adopt +more sophisticated algorithms in the future. + +The Yew virtual DOM is not exactly one-to-one with the browser DOM. It also includes "lists" and +"components" for organizing DOM elements. A list can simply be an ordered list of elements but can +also be much more powerful. By annotating each list element with a "key", application developers +can help Yew make additional optimizations to ensure that when a list changes, the least amount +of work is done to calculate the diff update. Similarly, components provide custom logic to +indicate whether a re-render is required to help with performance. + +## Yew scheduler and component-scoped event loop + +*Contribute to the docs – explain how `yew::scheduler` and `yew::html::scope` work in depth* + +## Further reading +* [More information about macros from the Rust Book](https://doc.rust-lang.org/stable/book/ch19-06-macros.html) +* [More information about `cargo-expand`](https://github.com/dtolnay/cargo-expand) +* [The API documentation for `yew::virtual_dom`](https://docs.rs/yew/*/yew/virtual_dom/index.html) diff --git a/website/versioned_docs/version-0.19.0/advanced-topics/optimizations.md b/website/versioned_docs/version-0.19.0/advanced-topics/optimizations.md new file mode 100644 index 00000000000..5be06464a47 --- /dev/null +++ b/website/versioned_docs/version-0.19.0/advanced-topics/optimizations.md @@ -0,0 +1,152 @@ +--- +title: "Optimizations & Best Practices" +sidebar_label: Optimizations +description: "Make your app faster" +--- + +## Using smart pointers effectively + +**Note: if you're unsure about some of the terms used in this section, the Rust Book has a useful +[chapter about smart pointers](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html).** + +In an effort to avoid cloning large amounts of data to create props when re-rendering, we can use +smart pointers to only clone a reference to the data instead of the data itself. If you pass +references to the relevant data in your props and child components instead of the actual data you +can avoid cloning any data until you need to modify it in the child component, where you can +use `Rc::make_mut` to clone and obtain a mutable reference to the data you want to alter. + +This brings further benefits in `Component::changed` when working out whether prop changes require +the component to re-render. This is because instead of comparing the value of the data the +underlying pointer addresses (i.e. the position in a machine's memory where the data is stored) can +instead be compared; if two pointers point to the same data then the value of the data they point to +must be the same. Note that the inverse might not be true! Even if two pointer addresses differ the +underlying data might still be the same - in this case you should compare the underlying data. + +To do this comparison you'll need to use `Rc::ptr_eq` instead of just using `PartialEq` (which is +automatically used when comparing data using the equality operator `==`). The Rust documentation +has [more details about `Rc::ptr_eq`](https://doc.rust-lang.org/stable/std/rc/struct.Rc.html#method.ptr_eq). + +This optimization is most useful for data types that don't implement `Copy`. If you can copy your +data cheaply, then it isn't worth putting it behind a smart pointer. For structures that +can be data-heavy like `Vec`s, `HashMap`s, and `String`s using smart pointers is likely to bring +performance improvements. + +This optimization works best if the values are never updated by the children, and even better, if +they are rarely updated by parents. This makes `Rc<_>s` a good choice for wrapping property values +in for pure components. + +However, it must be noted that unless you need to clone the data yourself in the child component, +this optimization is not only useless, it also adds unnecessary cost of reference counting. Props +in Yew are already reference counted and no data clones occur internally. + +## View functions + +For code readability reasons, it often makes sense to migrate sections of `html!` to their own +functions. Not only does this make your code more readable because it reduces the amount of +indentation present, it also encourages good design patterns – particularly around building +composable applications because these functions can be called in multiple places which reduces the +amount of code that has to be written. + +## Pure Components + +Pure components are components that don't mutate their state, only displaying content and +propagating messages up to normal, mutable components. They differ from view functions in that they +can be used from within the `html!` macro using the component syntax \(``\) +instead of expression syntax \(`{some_view_function()}`\), and that depending on their +implementation, they can be memoized (this means that once a function is called its value is "saved" +so that if it's called with the same arguments more than once it doesn't have to recompute its value +and can just return the saved value from the first function call) - preventing re-renders for +identical props. Yew compares the props internally and so the UI is only re-rendered if the props change. + +## Reducing compile time using workspaces + +Arguably, the largest drawback to using Yew is the long time it takes to compile Yew apps. The time +taken to compile a project seems to be related to the quantity of code passed to the `html!` macro. +This tends to not be much of an issue for smaller projects, but for larger applications it makes +sense to split code across multiple crates to minimize the amount of work the compiler has to do for +each change made to the application. + +One possible approach is to make your main crate handle routing/page selection, and then make a +different crate for each page, where each page could be a different component, or just a big +function that produces `Html`. Code which is shared between the crates containing different parts of +the application could be stored in a separate crate which is depended on throughout the project. +In the best case scenario, you go from rebuilding all of your code on each compile to rebuilding +only the main crate, and one of your page crates. In the worst case, where you edit something in the +"common" crate, you will be right back to where you started: compiling all code that depends on that +commonly shared crate, which is probably everything else. + +If your main crate is too heavyweight, or you want to rapidly iterate on a deeply nested page \(eg. +a page that renders on top of another page\), you can use an example crate to create a simplified +implementation of the main page and render the component you are working on on top of that. + +## Reducing binary sizes + +* optimize Rust code + * `wee_alloc` \( using tiny allocator \) + * `cargo.toml` \( defining release profile \) +* optimize wasm code using `wasm-opt` + +**Note: more information about reducing binary sizes can be found in the +[Rust Wasm Book](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size).** + +### wee\_alloc + +[wee\_alloc](https://github.com/rustwasm/wee_alloc) is a tiny allocator that is much smaller than the allocator that is normally used in Rust binaries. Replacing the default allocator with this one will result in smaller Wasm file sizes, at the expense of speed and memory overhead. + +The slower speed and memory overhead are minor in comparison to the size gains made by not including the default allocator. This smaller file size means that your page will load faster, and so it is generally recommended that you use this allocator over the default, unless your app is doing some allocation-heavy work. + +```rust ,ignore +// Use `wee_alloc` as the global allocator. +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; +``` + +### Cargo.toml + +It is possible to configure release builds to be smaller using the available settings in the +`[profile.release]` section of your `Cargo.toml`. + + +```toml, title=Cargo.toml +[profile.release] +# less code to include into binary +panic = 'abort' +# optimization over all codebase ( better optimization, slower build ) +codegen-units = 1 +# optimization for size ( more aggressive ) +opt-level = 'z' +# optimization for size +# opt-level = 's' +# link time optimization using using whole-program analysis +lto = true +``` + +### wasm-opt + +Further more it is possible to optimize size of `wasm` code. + +The Rust Wasm Book has a section about reducing the size of Wasm binaries: +[Shrinking .wasm size](https://rustwasm.github.io/book/game-of-life/code-size.html) + +* using `wasm-pack` which by default optimizes `wasm` code in release builds +* using `wasm-opt` directly on `wasm` files. + +```text +wasm-opt wasm_bg.wasm -Os -o wasm_bg_opt.wasm +``` + +#### Build size of 'minimal' example in yew/examples/ + +Note: `wasm-pack` combines optimization for Rust and Wasm code. `wasm-bindgen` is used in this example without any Rust size optimization. + +| used tool | size | +| :--- | :--- | +| wasm-bindgen | 158KB | +| wasm-bindgen + wasm-opt -Os | 116KB | +| wasm-pack | 99 KB | + +## Further reading: + * [The Rust Book's chapter on smart pointers](https://doc.rust-lang.org/book/ch15-00-smart-pointers.html) + * [Information from the Rust Wasm Book about reducing binary sizes](https://rustwasm.github.io/book/reference/code-size.html#optimizing-builds-for-code-size) + * [Documentation about Rust profiles](https://doc.rust-lang.org/cargo/reference/profiles.html) + * [binaryen project](https://github.com/WebAssembly/binaryen) diff --git a/website/versioned_docs/version-0.19.0/advanced-topics/portals.md b/website/versioned_docs/version-0.19.0/advanced-topics/portals.md new file mode 100644 index 00000000000..f486c273231 --- /dev/null +++ b/website/versioned_docs/version-0.19.0/advanced-topics/portals.md @@ -0,0 +1,41 @@ +--- +title: "Portals" +description: "Rendering into out-of-tree DOM nodes" +--- + +## How to think about portals? + +Portals provide a first-class way to render children into a DOM node that exists outside the DOM hierarchy of the parent component. +`yew::create_portal(child, host)` returns a `Html` value that renders `child` not hierarchically under its parent component, +but as a child of the `host` element. + +## Usage + +Typical uses of portals can include modal dialogs and hovercards, as well as more technical applications such as controlling the contents of an element's [`shadowRoot`](https://developer.mozilla.org/en-US/docs/Web/API/Element/shadowRoot), appending stylesheets to the surrounding document's `` and collecting referenced elements inside a central `` element of an ``. + +Note that `yew::create_portal` is a rather low-level building block, on which other components should be built that provide the interface for your specific use case. As an example, here is a simple modal dialogue that renders its `children` into an element outside `yew`'s control, identified by the `id="modal_host"`. + +```rust +use yew::{html, create_portal, function_component, Children, Properties}; + +#[derive(Properties, PartialEq)] +pub struct ModalProps { + #[prop_or_default] + pub children: Children, +} + +#[function_component(Modal)] +fn modal(props: &ModalProps) -> Html { + let modal_host = gloo_utils::document() + .get_element_by_id("modal_host") + .expect("a #modal_host element"); + + create_portal( + html!{ {for props.children.iter()} }, + modal_host.into(), + ) +} +``` + +## Further reading +- [Portals example](https://github.com/yewstack/yew/tree/master/examples/portals) diff --git a/website/versioned_docs/version-0.19.0/concepts/agents.md b/website/versioned_docs/version-0.19.0/concepts/agents.md new file mode 100644 index 00000000000..3c2702b4a47 --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/agents.md @@ -0,0 +1,63 @@ +--- +title: "Agents" +description: "Yew's Actor System" +--- + +Agents are similar to Angular's [Services](https://angular.io/guide/architecture-services) +\(but without dependency injection\), and provide Yew with an +[Actor Model](https://en.wikipedia.org/wiki/Actor_model). Agents can be used to route messages +between components independently of where they sit in the component hierarchy, or they can be used +to create shared state between different components. Agents can also be used to offload +computationally expensive tasks from the main thread which renders the UI. There is also planned +support for using agents to allow Yew applications to communicate across tabs \(in the future\). + +In order for agents to run concurrently, Yew uses +[web-workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers). + +## Lifecycle + +![Agent lifecycle](https://user-images.githubusercontent.com/42674621/79125224-b6481d80-7d95-11ea-8e6a-ab9b52d1d8ac.png) + +## Types of Agents + +### Reaches + +* Context - There will exist at most one instance of a Context Agent at any given time. Bridges will + spawn or connect to an already spawned agent on the UI thread. This can be used to coordinate + state between components or other agents. When no bridges are connected to this agent, the agent + will disappear. + +* Job - Spawn a new agent on the UI thread for every new bridge. This is good for moving shared but + independent behavior that communicates with the browser out of components. \(TODO verify\) When + the task is done, the agent will disappear. + +* Public - Same as Context, but runs on its own web worker. + +* Private - Same as Job, but runs on its own web worker. + +* Global \(WIP\) + +## Communication between Agents and Components + +### Bridges + +A bridge allows bi-directional communication between an agent and a component. Bridges also allow agents to communicate with one another. + +A `use_bridge` hook is also provided to create bridges in a function component. + +### Dispatchers + +A dispatcher allows uni-directional communication between a component and an agent. A dispatcher allows a component to send messages to an agent. + +## Overhead + +Agents that use web workers \(i.e. Private and Public\) will incur a serialization overhead on the +messages they send and receive. They use [bincode](https://github.com/servo/bincode) to communicate +with other threads, so the cost is substantially higher than just calling a function. Unless the +cost of computation will outweigh the cost of message passing, you should use agents running on the +UI thread \(i.e. Job or Context\). + +## Further reading + +* The [pub\_sub](https://github.com/yewstack/yew/tree/master/examples/pub_sub) example shows how +components can use agents to communicate with each other. diff --git a/website/versioned_docs/version-0.19.0/concepts/components/callbacks.md b/website/versioned_docs/version-0.19.0/concepts/components/callbacks.md new file mode 100644 index 00000000000..bed13179266 --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/components/callbacks.md @@ -0,0 +1,85 @@ +--- +title: "Callbacks" +--- + +## Callbacks + +Callbacks are used to communicate with services, agents, and parent components within Yew. +Internally their type is just `Fn` wrapped in `Rc` to allow them to be cloned. + +They have an `emit` function that takes their `` type as an argument and converts that to a message expected by its destination. If a callback from a parent is provided in props to a child component, the child can call `emit` on the callback in its `update` lifecycle hook to send a message back to its parent. Closures or Functions provided as props inside the `html!` macro are automatically converted to Callbacks. + +A simple use of a callback might look something like this: + +```rust +use yew::{html, Component, Context, Html}; + +enum Msg { + Clicked, +} + +struct Comp; + +impl Component for Comp { + + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + // highlight-next-line + let onclick = ctx.link().callback(|_| Msg::Clicked); + html! { + // highlight-next-line + + } + } +} +``` + +The function passed to `callback` must always take a parameter. For example, the `onclick` handler requires a function which takes a parameter of type `MouseEvent`. The handler can then decide what kind of message should be sent to the component. This message is scheduled for the next update loop unconditionally. + +If you need a callback that might not need to cause an update, use `batch_callback`. + +```rust +use yew::{events::KeyboardEvent, html, Component, Context, Html}; + +enum Msg { + Submit, +} + +struct Comp; + +impl Component for Comp { + + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + // highlight-start + let onkeypress = ctx.link().batch_callback(|event: KeyboardEvent| { + if event.key() == "Enter" { + Some(Msg::Submit) + } else { + None + } + }); + + html! { + + } + // highlight-end + } +} +``` + +## Relevant examples +- [Counter](https://github.com/yewstack/yew/tree/master/examples/counter) +- [Timer](https://github.com/yewstack/yew/tree/master/examples/timer) diff --git a/website/versioned_docs/version-0.19.0/concepts/components/children.md b/website/versioned_docs/version-0.19.0/concepts/components/children.md new file mode 100644 index 00000000000..4bdb93f202d --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/components/children.md @@ -0,0 +1,252 @@ +--- +title: "Children" +--- + +## General usage + +_Most of the time,_ when allowing a component to have children, you don't care +what type of children the component has. In such cases, the below example will +suffice. + +```rust +use yew::{html, Children, Component, Context, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct ListProps { + #[prop_or_default] + pub children: Children, +} + +pub struct List; + +impl Component for List { + type Message = (); + type Properties = ListProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ { for ctx.props().children.iter() } +
+ } + } +} +``` + +## Advanced usage + +### Typed children +In cases where you want one type of component to be passed as children to your component, +you can use `yew::html::ChildrenWithProps`. + +```rust +use yew::{html, ChildrenWithProps, Component, Context, Html, Properties}; + +pub struct Item; + +impl Component for Item { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "item" } + } + } +} + +#[derive(Properties, PartialEq)] +pub struct ListProps { + #[prop_or_default] + pub children: ChildrenWithProps, +} + +pub struct List; + +impl Component for List { + type Message = (); + type Properties = ListProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ { for ctx.props().children.iter() } +
+ } + } +} +``` + +### Enum typed children +Of course, sometimes you might need to restrict the children to a few different +components. In these cases, you have to get a little more hands-on with Yew. + +The [`derive_more`](https://github.com/JelteF/derive_more) crate is used here +for better ergonomics. If you don't want to use it, you can manually implement +`From` for each variant. + +```rust +use yew::{ + html, html::ChildrenRenderer, virtual_dom::VChild, Component, + Context, Html, Properties, +}; + +pub struct Primary; + +impl Component for Primary { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "Primary" } + } + } +} + +pub struct Secondary; + +impl Component for Secondary { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "Secondary" } + } + } +} + +#[derive(Clone, derive_more::From, PartialEq)] +pub enum Item { + Primary(VChild), + Secondary(VChild), +} + +// Now, we implement `Into` so that yew knows how to render `Item`. +#[allow(clippy::from_over_into)] +impl Into for Item { + fn into(self) -> Html { + match self { + Self::Primary(child) => child.into(), + Self::Secondary(child) => child.into(), + } + } +} + +#[derive(Properties, PartialEq)] +pub struct ListProps { + #[prop_or_default] + pub children: ChildrenRenderer, +} + +pub struct List; + +impl Component for List { + type Message = (); + type Properties = ListProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ { for ctx.props().children.iter() } +
+ } + } +} +``` + +### Optional typed child +You can also have a single optional child component of a specific type too: + +```rust +use yew::{ + html, html_nested, virtual_dom::VChild, Component, + Context, Html, Properties +}; + +pub struct PageSideBar; + +impl Component for PageSideBar { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "sidebar" } + } + } +} + +#[derive(Properties, PartialEq)] +pub struct PageProps { + #[prop_or_default] + pub sidebar: Option>, +} + +struct Page; + +impl Component for Page { + type Message = (); + type Properties = PageProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ { ctx.props().sidebar.clone().map(Html::from).unwrap_or_default() } + // ... page content +
+ } + } +} + +// The page component can be called either with the sidebar or without: + +pub fn render_page(with_sidebar: bool) -> Html { + if with_sidebar { + // Page with sidebar + html! { + + }}} /> + } + } else { + // Page without sidebar + html! { + + } + } +} +``` diff --git a/website/versioned_docs/version-0.19.0/concepts/components/introduction.md b/website/versioned_docs/version-0.19.0/concepts/components/introduction.md new file mode 100644 index 00000000000..3c11cab0e43 --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/components/introduction.md @@ -0,0 +1,305 @@ +--- +title: "Introduction" +description: "Components and their lifecycle hooks" +--- + +## What are Components? + +Components are the building blocks of Yew. They manage their own state and can render themselves to the DOM. Components are created by implementing the `Component` trait for a type. The `Component` +trait has a number of methods which need to be implemented; Yew will call these at different stages +in the lifecycle of a component. + +## Lifecycle + +:::important contribute +`Contribute to our docs:` [Add a diagram of the component lifecycle](https://github.com/yewstack/yew/issues/1915) +::: + +## Lifecycle Methods + +### Create + +When a component is created, it receives properties from its parent component and is stored within +the `Context` thats passed down to the `create` method. The properties can be used to +initialize the component's state and the "link" can be used to register callbacks or send messages to the component. + +```rust +use yew::{Component, Context, html, Html, Properties}; + +#[derive(PartialEq, Properties)] +pub struct Props; + +pub struct MyComponent; + +impl Component for MyComponent { + type Message = (); + type Properties = Props; + + // highlight-start + fn create(ctx: &Context) -> Self { + MyComponent + } + // highlight-end + + fn view(&self, _ctx: &Context) -> Html { + html! { + // impl + } + } +} +``` + +### View + +The `view` method allows you to describe how a component should be rendered to the DOM. Writing +HTML-like code using Rust functions can become quite messy, so Yew provides a macro called `html!` +for declaring HTML and SVG nodes (as well as attaching attributes and event listeners to them) and a +convenient way to render child components. The macro is somewhat similar to React's JSX (the +differences in programming language aside). +One difference is that Yew provides a shorthand syntax for properties, similar to Svelte, where instead of writing `onclick={onclick}`, you can just write `{onclick}`. + +```rust +use yew::{Component, Context, html, Html, Properties}; + +enum Msg { + Click, +} + +#[derive(PartialEq, Properties)] +struct Props { + button_text: String, +} + +struct MyComponent; + +impl Component for MyComponent { + type Message = Msg; + type Properties = Props; + + fn create(_ctx: &Context) -> Self { + Self + } + + // highlight-start + fn view(&self, ctx: &Context) -> Html { + let onclick = ctx.link().callback(|_| Msg::Click); + html! { + + } + } + // highlight-end +} +``` + +For usage details, check out [the `html!` guide](../html/introduction). + +### Rendered + +The `rendered` component lifecycle method is called once `view` has been called and Yew has rendered +the results to the DOM, but before the browser refreshes the page. This method is useful when you +want to perform actions that can only be completed after the component has rendered elements. There +is also a parameter called `first_render` which can be used to determine whether this function is +being called on the first render, or instead a subsequent one. + +```rust +use web_sys::HtmlInputElement; +use yew::{ + Component, Context, html, Html, NodeRef, +}; + +pub struct MyComponent { + node_ref: NodeRef, +} + +impl Component for MyComponent { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self { + node_ref: NodeRef::default(), + } + } + + fn view(&self, ctx: &Context) -> Html { + html! { + + } + } + + // highlight-start + fn rendered(&mut self, _ctx: &Context, first_render: bool) { + if first_render { + if let Some(input) = self.node_ref.cast::() { + input.focus(); + } + } + } + // highlight-end +} +``` + +:::tip note +Note that this lifecycle method does not require an implementation and will do nothing by default. +::: + +### Update + +Communication with components happens primarily through messages which are handled by the +`update` lifecycle method. This allows the component to update itself +based on what the message was, and determine if it needs to re-render itself. Messages can be sent +by event listeners, child components, Agents, Services, or Futures. + +Here's an example of what an implementation of `update` could look like: + +```rust +use yew::{Component, Context, html, Html}; + +// highlight-start +pub enum Msg { + SetInputEnabled(bool) +} +// highlight-end + +struct MyComponent { + input_enabled: bool, +} + +impl Component for MyComponent { + // highlight-next-line + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self { + input_enabled: false, + } + } + + // highlight-start + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::SetInputEnabled(enabled) => { + if self.input_enabled != enabled { + self.input_enabled = enabled; + true // Re-render + } else { + false + } + } + } + } + // highlight-end + + fn view(&self, _ctx: &Context) -> Html { + html! { + // impl + } + } + +} +``` + +### Changed + +Components may be re-rendered by their parents. When this happens, they could receive new properties +and need to re-render. This design facilitates parent to child component communication by just +changing the values of a property. There is a default implementation which re-renders the component +when props are changed. + +### Destroy + +After Components are unmounted from the DOM, Yew calls the `destroy` lifecycle method; this is +necessary if you need to undertake operations to clean up after earlier actions of a component +before it is destroyed. This method is optional and does nothing by default. + +### Infinite loops + +Infinite loops are possible with Yew's lifecycle methods, but are only caused when trying to update +the same component after every render when that update also requests the component to be rendered. + +A simple example can be seen below: + +```rust +use yew::{Context, Component, Html}; + +struct Comp; + +impl Component for Comp { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn update(&mut self, _ctx: &Context, _msg: Self::Message) -> bool { + // We are going to always request to re-render on any msg + true + } + + fn view(&self, _ctx: &Context) -> Html { + // For this example it doesn't matter what is rendered + Html::default() + } + + fn rendered(&mut self, ctx: &Context, _first_render: bool) { + // Request that the component is updated with this new msg + ctx.link().send_message(()); + } +} +``` + +Let's run through what happens here: +1. Component is created using the `create` function. +2. The `view` method is called so Yew knows what to render to the browser DOM. +3. The `rendered` method is called, which schedules an update message using the `Context` link. +4. Yew finishes the post-render phase. +5. Yew checks for scheduled events and sees the update message queue is not empty so works through +the messages. +6. The `update` method is called which returns `true` to indicate something has changed and the +component needs to re-render. +7. Jump back to 2. + +You can still schedule updates in the `rendered` method and it's often useful to do so, but +consider how your component will terminate this loop when you do. + + +## Associated Types + +The `Component` trait has two associated types: `Message` and `Properties`. + +```rust ,ignore +impl Component for MyComponent { + type Message = Msg; + type Properties = Props; + + // ... +} +``` + +The `Message` type is used to send messages to a component after an event has taken place; for +example you might want to undertake some action when a user clicks a button or scrolls down the +page. Because components tend to have to respond to more than one event, the `Message` type will +normally be an enum, where each variant is an event to be handled. + +When organizing your codebase, it is sensible to include the definition of the `Message` type in the +same module in which your component is defined. You may find it helpful to adopt a consistent naming +convention for message types. One option (though not the only one) is to name the types +`ComponentNameMsg`, e.g. if your component was called `Homepage` then you might call the type +`HomepageMsg`. + +```rust +enum Msg { + Click, + FormInput(String) +} +``` + +`Properties` represents the information passed to a component from its parent. This type must implement the `Properties` trait \(usually by deriving it\) and can specify whether certain properties are required or optional. This type is used when creating and updating a component. It is common practice to create a struct called `Props` in your component's module and use that as the component's `Properties` type. It is common to shorten "properties" to "props". Since props are handed down from parent components, the root component of your application typically has a `Properties` type of `()`. If you wish to specify properties for your root component, use the `App::mount_with_props` method. + +## Context + +All component lifecycle methods take a context object. This object provides a reference to component's scope, which +allows sending messages to a component and the props passed to the component. + diff --git a/website/versioned_docs/version-0.19.0/concepts/components/properties.md b/website/versioned_docs/version-0.19.0/concepts/components/properties.md new file mode 100644 index 00000000000..693e86321da --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/components/properties.md @@ -0,0 +1,137 @@ +--- +title: "Properties" +description: "Parent to child communication" +--- + +Properties enable child and parent components to communicate with each other. +Every component has an associated properties type which describes what is passed down from the parent. +In theory this can be any type that implements the `Properties` trait, but in practice there's no +reason for it to be anything but a struct where each field represents a property. + +## Derive macro + +Instead of implementing the `Properties` trait yourself, you should use `#[derive(Properties)]` to +automatically generate the implementation instead. +Types for which you derive `Properties` must also implement `PartialEq`. + +### Field attributes + +When deriving `Properties`, all fields are required by default. +The following attributes allow you to give your props initial values which will be used unless they're set to another value. + +:::tip +Attributes aren't visible in Rustdoc generated documentation. +The docstrings of your properties should mention whether a prop is optional and if it has a special default value. +::: + +#### `#[prop_or_default]` + +Initialize the prop value with the default value of the field's type using the `Default` trait. + +#### `#[prop_or(value)]` + +Use `value` to initialize the prop value. `value` can be any expression that returns the field's type. +For example, to default a boolean prop to `true`, use the attribute `#[prop_or(true)]`. + +#### `#[prop_or_else(function)]` + +Call `function` to initialize the prop value. `function` should have the signature `FnMut() -> T` where `T` is the field type. + +## `PartialEq` + +`Properties` require `PartialEq` to be implemented. This is so that they can be compared by Yew to call the `changed` method +only when they change. + +## Memory/speed overhead of using Properties + +Internally properties are reference counted. This means that only a pointer is passed down the component tree for props. +It saves us from the cost of having to clone the entire props, which might be expensive. + +## Example + +```rust +use std::rc::Rc; +use yew::Properties; + +#[derive(Clone, PartialEq)] +pub enum LinkColor { + Blue, + Red, + Green, + Black, + Purple, +} + +fn create_default_link_color() -> LinkColor { + LinkColor::Blue +} + +#[derive(Properties, PartialEq)] +pub struct LinkProps { + /// The link must have a target. + href: String, + text: String, + /// Color of the link. Defaults to `Blue`. + #[prop_or_else(create_default_link_color)] + color: LinkColor, + /// The view function will not specify a size if this is None. + #[prop_or_default] + size: Option, + /// When the view function doesn't specify active, it defaults to true. + #[prop_or(true)] + active: bool, +} +``` + +## Props macro + +The `yew::props!` macro allows you to build properties the same way the `html!` macro does it. + +The macro uses the same syntax as a struct expression except that you can't use attributes or a base expression (`Foo { ..base }`). +The type path can either point to the props directly (`path::to::Props`) or the associated properties of a component (`MyComp::Properties`). + +```rust +use std::rc::Rc; +use yew::{props, Properties}; + +#[derive(Clone, PartialEq)] +pub enum LinkColor { + Blue, + Red, + Green, + Black, + Purple, +} + +fn create_default_link_color() -> LinkColor { + LinkColor::Blue +} + +#[derive(Properties, PartialEq)] +pub struct LinkProps { + /// The link must have a target. + href: String, + text: Rc, + /// Color of the link. Defaults to `Blue`. + #[prop_or_else(create_default_link_color)] + color: LinkColor, + /// The view function will not specify a size if this is None. + #[prop_or_default] + size: Option, + /// When the view function doesn't specify active, it defaults to true. + #[prop_or(true)] + active: bool, +} + +impl LinkProps { + pub fn new_link_with_size(href: String, text: String, size: u32) -> Self { + // highlight-start + props! {LinkProps { + href, + text: Rc::from(text), + size, + }} + // highlight-end + } +} +``` diff --git a/website/versioned_docs/version-0.19.0/concepts/components/refs.md b/website/versioned_docs/version-0.19.0/concepts/components/refs.md new file mode 100644 index 00000000000..a7ffa03a8d4 --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/components/refs.md @@ -0,0 +1,54 @@ +--- +title: "Refs" +description: "Out-of-band DOM access" +--- + +The `ref` keyword can be used inside of any HTML element or component to get the DOM `Element` that +the item is attached to. This can be used to make changes to the DOM outside of the `view` lifecycle +method. + +This is useful for getting ahold of canvas elements, or scrolling to different sections of a page. +For example, using a `NodeRef` in a component's `rendered` method allows you to make draw calls to +a canvas element after it has been rendered from `view`. + +The syntax is: + +```rust +use web_sys::Element; +use yew::{html, Component, Context, Html, NodeRef}; + +struct Comp { + node_ref: NodeRef, +} + +impl Component for Comp { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self { + // highlight-next-line + node_ref: NodeRef::default(), + } + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + // highlight-next-line +
+ } + } + + fn rendered(&mut self, _ctx: &Context, _first_render: bool) { + // highlight-start + let has_attributes = self.node_ref + .cast::() + .unwrap() + .has_attributes(); + // highlight-end + } +} +``` + +## Relevant examples +- [Node Refs](https://github.com/yewstack/yew/tree/master/examples/node_refs) diff --git a/website/versioned_docs/version-0.19.0/concepts/components/scope.md b/website/versioned_docs/version-0.19.0/concepts/components/scope.md new file mode 100644 index 00000000000..0b070cca9e1 --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/components/scope.md @@ -0,0 +1,87 @@ +--- +title: "Scope" +description: "Component's Scope" +--- + +## Component's `Scope<_>` API + +The component "`Scope`" is the mechanism through which components are able to create callbacks and update themselves +using messages. We obtain a reference to this by calling `link()` on the context object passed to the component. + +### `send_message` + +Sends a message to the component. +Messages are handled by the `update` method which determines whether the component should re-render. + +### `send_message_batch` + +Sends multiple messages to the component at the same time. +This is similar to `send_message` but if any of the messages cause the `update` method to return `true`, +the component will re-render after all messages in the batch have been processed. + +If the given vector is empty, this function doesn't do anything. + +### `callback` + +Create a callback that will send a message to the component when it is executed. +Under the hood, it will call `send_message` with the message returned by the provided closure. + +There is a different method called `callback_once` which accepts a `FnOnce` instead of a `Fn`. +You should use this with care though, as the resulting callback will panic if executed more than once. + +```rust +use yew::{html, Component, Context, Html}; + +enum Msg { + Text(String), +} + +struct Comp; + +impl Component for Comp { + + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + // Create a callback that accepts some text and sends it + // to the component as the `Msg::Text` message variant. + // highlight-next-line + let cb = ctx.link().callback(|text: String| Msg::Text(text)); + + // The previous line is needlessly verbose to make it clearer. + // It can be simplified it to this: + // highlight-next-line + let cb = ctx.link().callback(Msg::Text); + + // Will send `Msg::Text("Hello World!")` to the component. + // highlight-next-line + cb.emit("Hello World!".to_owned()); + + html! { + // html here + } + } +} +``` + +### `batch_callback` + +Create a callback that will send a batch of messages to the component when it is executed. +The difference to `callback` is that the closure passed to this method doesn't have to return a message. +Instead, the closure can return either `Vec` or `Option` where `Msg` is the component's message type. + +`Vec` is treated as a batch of messages and uses `send_message_batch` under the hood. + +`Option` calls `send_message` if it is `Some`. If the value is `None`, nothing happens. +This can be used in cases where, depending on the situation, an update isn't required. + +This is achieved using the `SendAsMessage` trait which is only implemented for these types. +You can implement `SendAsMessage` for your own types which allows you to use them in `batch_callback`. + +Like `callback`, this method also has a `FnOnce` counterpart, `batch_callback_once`. +The same restrictions apply as for `callback_once`. diff --git a/website/versioned_docs/version-0.19.0/concepts/contexts.md b/website/versioned_docs/version-0.19.0/concepts/contexts.md new file mode 100644 index 00000000000..03d440f182a --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/contexts.md @@ -0,0 +1,149 @@ +--- +title: "Contexts" +sidebar_label: Contexts +description: "Using contexts to pass data within application" +--- + +Generally data is passed down the component tree using props but that becomes tedious for values such as +user preferences, authentication information etc. Consider the following example which passes down the +theme using props: +```rust +use yew::{html, Children, Component, Context, Html, Properties}; + +#[derive(Clone, PartialEq)] +pub struct Theme { + foreground: String, + background: String, +} + +#[derive(PartialEq, Properties)] +pub struct NavbarProps { + theme: Theme, +} + +pub struct Navbar; + +impl Component for Navbar { + type Message = (); + type Properties = NavbarProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ + { "App title" } + + + { "Somewhere" } + +
+ } + } +} + +#[derive(PartialEq, Properties)] +pub struct ThemeProps { + theme: Theme, + children: Children, +} + +#[yew::function_component(Title)] +fn title(_props: &ThemeProps) -> Html { + html! { + // impl + } +} +#[yew::function_component(NavButton)] +fn nav_button(_props: &ThemeProps) -> Html { + html! { + // impl + } +} + +// root +let theme = Theme { + foreground: "yellow".to_owned(), + background: "pink".to_owned(), +}; + +html! { + +}; +``` + +Passing down data like this isn't ideal for something like a theme which needs to be available everywhere. +This is where contexts come in. + +Contexts provide a way to share data between components without passing them down explicitly as props. +They make data available to all components in the tree. + +## Using Contexts + +In order to use contexts, we need a struct which defines what data is to be passed. +For the above use-case, consider the following struct: +```rust +#[derive(Clone, Debug, PartialEq)] +struct Theme { + foreground: String, + background: String, +} +``` + +A context provider is required to consume the context. `ContextProvider`, where `T` is the context struct is used as the provider. +`T` must implement `Clone` and `PartialEq`. `ContextProvider` is the component whose children will have the context available to them. +The children are re-rendered when the context changes. + +### Consuming context + +#### Struct components + +The `Scope::context` method is used to consume contexts in struct components. + +##### Example + +```rust +use yew::{Callback, html, Component, Context, Html}; + +#[derive(Clone, Debug, PartialEq)] +struct Theme { + foreground: String, + background: String, +} + +struct ContextDemo; + +impl Component for ContextDemo { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + let (theme, _) = ctx + .link() + .context::(Callback::noop()) + .expect("context to be set"); + html! { + + } + } +} +``` + +#### Function components + +`use_context` hook is used to consume contexts in function components. +See [docs for use_context](function-components/pre-defined-hooks.md#use_context) to learn more. diff --git a/website/versioned_docs/version-0.19.0/concepts/function-components/attribute.md b/website/versioned_docs/version-0.19.0/concepts/function-components/attribute.md new file mode 100644 index 00000000000..96e87c313ee --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/function-components/attribute.md @@ -0,0 +1,123 @@ +--- +title: "#[function_component]" +description: "The #[function_component] attribute" +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +`#[function_component(_)]` turns a normal Rust function into a function component. +Functions with the attribute have to return `Html` and may take a single parameter for the type of props the component should accept. +The parameter type needs to be a reference to a `Properties` type (ex. `props: &MyProps`). +If the function doesn't have any parameters the resulting component doesn't accept any props. + +The attribute doesn't replace your original function with a component. You need to provide a name as an input to the attribute which will be the identifier of the component. +Assuming you have a function called `chat_container` and you add the attribute `#[function_component(ChatContainer)]` you can use the component like this: + +```rust +use yew::{function_component, html, Html}; + +#[function_component(ChatContainer)] +pub fn chat_container() -> Html { + html! { + // chat container impl + } +} + +html! { + +}; +``` + +## Example + + + + +```rust +use yew::{function_component, html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct RenderedAtProps { + pub time: String, +} + +#[function_component(RenderedAt)] +pub fn rendered_at(props: &RenderedAtProps) -> Html { + html! { +

+ { "Rendered at: " } + { &props.time } +

+ } +} +``` + +
+ + +```rust +use yew::{function_component, html, use_state, Callback}; + +#[function_component(App)] +fn app() -> Html { + let counter = use_state(|| 0); + + let onclick = { + let counter = counter.clone(); + Callback::from(move |_| counter.set(*counter + 1)) + }; + + html! { +
+ +

+ { "Current value: " } + { *counter } +

+
+ } +} +``` + +
+
+ +## Generic function components + +The `#[function_component(_)]` attribute also works with generic functions for creating generic components. + +```rust title=my_generic_component.rs +use std::fmt::Display; +use yew::{function_component, html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct Props +where + T: PartialEq, +{ + data: T, +} + +#[function_component(MyGenericComponent)] +pub fn my_generic_component(props: &Props) -> Html +where + T: PartialEq + Display, +{ + html! { +

+ { &props.data } +

+ } +} + +// used like this +html! { + data=123 /> +}; + +// or +html! { + data={"foo".to_string()} /> +}; +``` diff --git a/website/versioned_docs/version-0.19.0/concepts/function-components/custom-hooks.md b/website/versioned_docs/version-0.19.0/concepts/function-components/custom-hooks.md new file mode 100644 index 00000000000..2e38c61716a --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/function-components/custom-hooks.md @@ -0,0 +1,88 @@ +--- +title: "Custom Hooks" +description: "Defining your own Hooks " +--- + +## Defining custom Hooks + +Component's stateful logic can be extracted into usable function by creating custom Hooks. + +Consider that we have a component which subscribes to an agent and displays the messages sent to it. +```rust +use yew::{function_component, html, use_effect, use_state, Callback}; +use yew_agent::Bridged; +// EventBus is an implementation yew_agent::Agent +use website_test::agents::EventBus; + + +#[function_component(ShowMessages)] +pub fn show_messages() -> Html { + let state = use_state(Vec::new); + + { + let state = state.clone(); + use_effect(move || { + let producer = EventBus::bridge(Callback::from(move |msg| { + let mut messages = (*state).clone(); + messages.push(msg); + state.set(messages) + })); + + || drop(producer) + }); + } + + let output = state.iter().map(|it| html! {

{ it }

}); + html! {
{ for output }
} +} +``` + +There's one problem with this code: the logic can't be reused by another component. +If we build another component which keeps track of the messages, instead of copying the code we can move the logic into a custom hook. + +We'll start by creating a new function called `use_subscribe`. +The `use_` prefix conventionally denotes that a function is a hook. +This function will take no arguments and return `Rc>>`. +```rust +use std::{cell::RefCell, rc::Rc}; + +fn use_subscribe() -> Rc>> { + todo!() +} +``` + +This is a simple hook which can be created by combining other hooks. For this example, we'll two pre-defined hooks. +We'll use `use_state` hook to store the `Vec` for messages, so they persist between component re-renders. +We'll also use `use_effect` to subscribe to the `EventBus` `Agent` so the subscription can be tied to component's lifecycle. + +```rust +use std::collections::HashSet; +use yew::{use_effect, use_state, Callback}; +use yew_agent::Bridged; +// EventBus is an implementation yew_agent::Agent +use website_test::agents::EventBus; + +fn use_subscribe() -> Vec { + let state = use_state(Vec::new); + + let effect_state = state.clone(); + + use_effect(move || { + let producer = EventBus::bridge(Callback::from(move |msg| { + let mut messages = (*effect_state).clone(); + messages.push(msg); + effect_state.set(messages) + })); + || drop(producer) + }); + + (*state).clone() +} +``` + +Although this approach works in almost all cases, it can't be used to write primitive hooks like the pre-defined hooks we've been using already + +### Writing primitive hooks + +`use_hook` function is used to write such hooks. View the docs on [docs.rs](https://docs.rs/yew/0.18.0/yew-functional/use_hook.html) for the documentation +and `hooks` directory to see implementations of pre-defined hooks. diff --git a/website/versioned_docs/version-0.19.0/concepts/function-components/introduction.md b/website/versioned_docs/version-0.19.0/concepts/function-components/introduction.md new file mode 100644 index 00000000000..4e737c8d08d --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/function-components/introduction.md @@ -0,0 +1,61 @@ +--- +title: "Function Components" +sidebar_label: Introduction +description: "Introduction to function components " +--- + +Function components are a simplified version of normal components. They consist of a single function +that receives props and determines what should be rendered by returning `Html`. Basically, it's a +component that's been reduced to just the `view` method. On its own that would be quite limiting +because you can only create pure components, but that's where Hooks come in. Hooks allow function +components to maintain their own internal state and use other Yew features without needing to manually +implement the `Component` trait. + +## Creating function components + +The easiest way to create a function component is to add the [`#[function_component]`](./../function-components/attribute.md) attribute to a function. + +```rust +use yew::{function_component, html}; + +#[function_component(HelloWorld)] +fn hello_world() -> Html { + html! { "Hello world" } +} +``` + +### Under the hood + +There are two parts to how Yew implements function components. + +The first part is the `FunctionProvider` trait which is analogous to the `Component` trait, except +that it only has a single method (called `run`). The second part is the `FunctionComponent` struct +which wraps types implementing `FunctionProvider` and implements `Component`. + +The `#[function_component]` attribute is a procedural macro which automatically implements +`FunctionProvider` for you and exposes it wrapped in `FunctionComponent`. + +### Hooks + +Hooks are functions that let you "hook into" components' state and/or lifecycle and perform +actions. Yew comes with a few pre-defined Hooks. You can also create your own. + +#### Pre-defined Hooks + +Yew comes with the following predefined Hooks: +- [`use_state`](./../function-components/pre-defined-hooks.md#use_state) +- [`use_ref`](./../function-components/pre-defined-hooks.md#use_ref) +- [`use_reducer`](./../function-components/pre-defined-hooks.md#use_reducer) +- [`use_reducer_with_init`](./../function-components/pre-defined-hooks.md#use_reducer_with_init) +- [`use_effect`](./../function-components/pre-defined-hooks.md#use_effect) +- [`use_effect_with_deps`](./../function-components/pre-defined-hooks.md#use_effect_with_deps) + +#### Custom Hooks + +There are cases where you want to define your own Hooks for reasons. Yew allows you to define your own Hooks which lets you extract your potentially stateful logic from the component into reusable functions. +See the [Defining custom hooks](./../function-components/custom-hooks.md#defining-custom-hooks) section for more information. + +## Further reading + +* The React documentation has a section on [React hooks](https://reactjs.org/docs/hooks-intro.html). +These are not exactly the same as Yew's hooks, but the underlying concept is similar. diff --git a/website/versioned_docs/version-0.19.0/concepts/function-components/pre-defined-hooks.md b/website/versioned_docs/version-0.19.0/concepts/function-components/pre-defined-hooks.md new file mode 100644 index 00000000000..102111d46b7 --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/function-components/pre-defined-hooks.md @@ -0,0 +1,413 @@ +--- +title: "Pre-defined Hooks" +description: "The pre-defined Hooks that Yew comes with " +--- + +## `use_state` + +`use_state` is used to manage state in a function component. +It returns a `UseStateHandle` object which `Deref`s to the current value +and provides a `set` method to update the value. + +The hook takes a function as input which determines the initial state. +This value remains up-to-date on subsequent renders. + +The setter function is guaranteed to be the same across the entire +component lifecycle. You can safely omit the `UseStateHandle` from the +dependents of `use_effect_with_deps` if you only intend to set +values from within the hook. + +This hook will always trigger a re-render upon receiving a new state. See +[`use_state_eq`](#use_state_eq) if you want the component to only +re-render when the state changes. + +### Example + +```rust +use yew::{Callback, function_component, html, use_state}; + +#[function_component(UseState)] +fn state() -> Html { + let counter = use_state(|| 0); + let onclick = { + let counter = counter.clone(); + Callback::from(move |_| counter.set(*counter + 1)) + }; + + + html! { +
+ +

+ { "Current value: " } + { *counter } +

+
+ } +} +``` + +:::caution + +The value held in the handle will reflect the value at the time the +handle is returned by the `use_state`. It is possible that the handle +does not dereference to an up to date value if you are moving it into a +`use_effect_with_deps` hook. You can register the +state to the dependents so the hook can be updated when the value changes. + +::: + +## `use_state_eq` + +This hook has the same effect as `use_state` but will only trigger a +re-render when the setter receives a value that `prev_state != next_state`. + +This hook requires the state object to implement `PartialEq`. + +## `use_ref` +`use_ref` is used for obtaining an immutable reference to a value. +Its state persists across renders. + +`use_ref` can be useful for keeping things in scope for the lifetime of the component, so long as +you don't store a clone of the resulting `Rc` anywhere that outlives the component. + +If you need a mutable reference, consider using [`use_mut_ref`](#use_mut_ref). +If you need the component to be re-rendered on state change, consider using [`use_state`](#use_state). + +```rust +// EventBus is an implementation of yew_agent::Agent +use website_test::agents::EventBus; +use yew::{function_component, html, use_ref, use_state, Callback}; +use yew_agent::Bridged; + +#[function_component(UseRef)] +fn ref_hook() -> Html { + let greeting = use_state(|| "No one has greeted me yet!".to_owned()); + + { + let greeting = greeting.clone(); + use_ref(|| EventBus::bridge(Callback::from(move |msg| { + greeting.set(msg); + }))); + } + + html! { +
+ { (*greeting).clone() } +
+ } +} +``` + +## `use_mut_ref` +`use_mut_ref` is used for obtaining a mutable reference to a value. +Its state persists across renders. + +It is important to note that you do not get notified of state changes. +If you need the component to be re-rendered on state change, consider using [`use_state`](#use_state). + +### Example + +```rust +use web_sys::HtmlInputElement; +use yew::{ + events::Event, + function_component, html, use_mut_ref, use_state, + Callback, TargetCast, +}; + +#[function_component(UseMutRef)] +fn mut_ref_hook() -> Html { + let message = use_state(|| "".to_string()); + let message_count = use_mut_ref(|| 0); + + let onclick = Callback::from(move |_| { + let window = gloo_utils::window(); + + if *message_count.borrow_mut() > 3 { + window.alert_with_message("Message limit reached").unwrap(); + } else { + *message_count.borrow_mut() += 1; + window.alert_with_message("Message sent").unwrap(); + } + }); + + let onchange = { + let message = message.clone(); + Callback::from(move |e: Event| { + let input: HtmlInputElement = e.target_unchecked_into(); + message.set(input.value()); + }) + }; + + html! { +
+ + +
+ } +} +``` + +## `use_node_ref` +`use_node_ref` is used for obtaining a `NodeRef` that persists across renders. + +When conditionally rendering elements you can use `NodeRef` in conjunction with `use_effect_with_deps` +to perform actions each time an element is rendered and just before its going to be removed from the +DOM. + +### Example + +```rust +use web_sys::HtmlInputElement; +use yew::{ + function_component, functional::*, html, + NodeRef +}; + +#[function_component(UseRef)] +pub fn ref_hook() -> Html { + let input_ref = use_node_ref(); + let value = use_state(|| 25f64); + + let onclick = { + let input_ref = input_ref.clone(); + let value = value.clone(); + move |_| { + if let Some(input) = input_ref.cast::() { + value.set(*value + input.value_as_number()); + } + } + }; + + html! { +
+ + +
+ } +} +``` + +## `use_reducer` + +`use_reducer` is an alternative to [`use_state`](#use_state). It is used to handle component's state and is used +when complex actions needs to be performed on said state. + +It accepts an initial state function and returns a `UseReducerHandle` that dereferences to the state, +and a dispatch function. +The dispatch function takes one argument of type `Action`. When called, the action and current value +are passed to the reducer function which computes a new state which is returned, +and the component is re-rendered. + +The dispatch function is guaranteed to be the same across the entire +component lifecycle. You can safely omit the `UseReducerHandle` from the +dependents of `use_effect_with_deps` if you only intend to dispatch +values from within the hooks. + +The state object returned by the initial state function is required to +implement a `Reducible` trait which provides an `Action` type and a +reducer function. + +This hook will always trigger a re-render upon receiving an action. See +[`use_reducer_eq`](#use_reducer_eq) if you want the component to only +re-render when the state changes. + +### Example + +```rust +use yew::prelude::*; +use std::rc::Rc; + +/// reducer's Action +enum CounterAction { + Double, + Square, +} + +/// reducer's State +struct CounterState { + counter: i32, +} + +impl Default for CounterState { + fn default() -> Self { + Self { counter: 1 } + } +} + +impl Reducible for CounterState { + /// Reducer Action Type + type Action = CounterAction; + + /// Reducer Function + fn reduce(self: Rc, action: Self::Action) -> Rc { + let next_ctr = match action { + CounterAction::Double => self.counter * 2, + CounterAction::Square => self.counter.pow(2) + }; + + Self { counter: next_ctr }.into() + } +} + +#[function_component(UseReducer)] +fn reducer() -> Html { + // The use_reducer hook takes an initialization function which will be called only once. + let counter = use_reducer(CounterState::default); + + let double_onclick = { + let counter = counter.clone(); + Callback::from(move |_| counter.dispatch(CounterAction::Double)) + }; + let square_onclick = { + let counter = counter.clone(); + Callback::from(move |_| counter.dispatch(CounterAction::Square)) + }; + + html! { + <> +
{ counter.counter }
+ + + + + } +} +``` + +:::caution + +The value held in the handle will reflect the value of at the time the +handle is returned by the `use_reducer`. It is possible that the handle does +not dereference to an up to date value if you are moving it into a +`use_effect_with_deps` hook. You can register the +state to the dependents so the hook can be updated when the value changes. + +::: + +## `use_reducer_eq` + +This hook has the same effect as `use_reducer` but will only trigger a +re-render when the reducer function produces a value that `prev_state != next_state`. + +This hook requires the state object to implement `PartialEq` in addition +to the `Reducible` trait required by `use_reducer`. + +## `use_effect` + +`use_effect` is used for hooking into the component's lifecycle. +Similar to `rendered` from the `Component` trait, +`use_effect` takes a function which is called after the render finishes. + +The input function has to return a closure, the destructor, which is called when the component is destroyed. +The destructor can be used to clean up the effects introduced and it can take ownership of values to delay dropping them until the component is destroyed. + +### Example + +```rust +use yew::{Callback, function_component, html, use_effect, use_state}; + +#[function_component(UseEffect)] +fn effect() -> Html { + let counter = use_state(|| 0); + + { + let counter = counter.clone(); + use_effect(move || { + // Make a call to DOM API after component is rendered + gloo_utils::document().set_title(&format!("You clicked {} times", *counter)); + + // Perform the cleanup + || gloo_utils::document().set_title("You clicked 0 times") + }); + } + let onclick = { + let counter = counter.clone(); + Callback::from(move |_| counter.set(*counter + 1)) + }; + + html! { + + } +} +``` + +### `use_effect_with_deps` + +Sometimes, it's needed to manually define dependencies for [`use_effect`](#use_effect). In such cases, we use `use_effect_with_deps`. +```rust ,no_run +use yew::use_effect_with_deps; + +use_effect_with_deps( + move |_| { + // ... + || () + }, + (), // dependents +); +``` + +**Note**: `dependents` must implement `PartialEq`. + +## `use_context` + +`use_context` is used for consuming [contexts](../contexts.md) in function components. + + +### Example + +```rust +use yew::{ContextProvider, function_component, html, use_context, use_state}; + + +/// App theme +#[derive(Clone, Debug, PartialEq)] +struct Theme { + foreground: String, + background: String, +} + +/// Main component +#[function_component(App)] +pub fn app() -> Html { + let ctx = use_state(|| Theme { + foreground: "#000000".to_owned(), + background: "#eeeeee".to_owned(), + }); + + html! { + // `ctx` is type `Rc>` while we need `Theme` + // so we deref it. + // It derefs to `&Theme`, hence the clone + context={(*ctx).clone()}> + // Every child here and their children will have access to this context. + + > + } +} + +/// The toolbar. +/// This component has access to the context +#[function_component(Toolbar)] +pub fn toolbar() -> Html { + html! { +
+ +
+ } +} + +/// Button placed in `Toolbar`. +/// As this component is a child of `ThemeContextProvider` in the component tree, it also has access to the context. +#[function_component(ThemedButton)] +pub fn themed_button() -> Html { + let theme = use_context::().expect("no ctx found"); + + html! { + + } +} +``` diff --git a/website/versioned_docs/version-0.19.0/concepts/html/classes.md b/website/versioned_docs/version-0.19.0/concepts/html/classes.md new file mode 100644 index 00000000000..16a1087c79b --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/html/classes.md @@ -0,0 +1,143 @@ +--- +title: "Classes" +description: "A handy macro to handle classes" +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +## Classes + +The struct `Classes` can be used to deal with HTML classes. + +When pushing a string to the set, `Classes` ensures that there is one element +for every class even if a single string might contain multiple classes. + +`Classes` can also be merged by using `Extend` (i.e. +`classes1.extend(classes2)`) or `push()` (i.e. `classes1.push(classes2)`). In +fact, anything that implements `Into` can be used to push new classes +to the set. + +The macro `classes!` is a convenient macro that creates one single `Classes`. +Its input accepts a comma separated list of expressions. The only requirement +is that every expression implements `Into`. + + + + +```rust +use yew::{classes, html}; + +html! { +
+}; +``` + +
+ + +```rust +use yew::{classes, html}; + +html! { +
+}; +``` + +
+ + +```rust +use yew::{classes, html}; + +let my_classes = String::from("class-1 class-2"); + +html! { +
+}; +``` + +
+ + +```rust +use yew::{classes, html}; + +html! { +
+}; +``` + + + + +```rust +use yew::{classes, html}; + +html! { +
+}; +``` + +
+ + +```rust +use yew::{classes, html}; + +let my_classes = ["class-1", "class-2"]; + +html! { +
+}; +``` + +
+ + +## Components that accept classes + +```rust +use yew::{ + classes, html, Children, Classes, Component, + Context, Html, Properties +}; + +#[derive(PartialEq, Properties)] +struct Props { + #[prop_or_default] + class: Classes, + fill: bool, + children: Children, +} + +struct MyComponent; + +impl Component for MyComponent { + type Message = (); + type Properties = Props; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + let Props { + class, + fill, + children, + } = &ctx.props(); + html! { +
+ { children.clone() } +
+ } + } +} +``` diff --git a/website/versioned_docs/version-0.19.0/concepts/html/components.md b/website/versioned_docs/version-0.19.0/concepts/html/components.md new file mode 100644 index 00000000000..9cda522d189 --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/html/components.md @@ -0,0 +1,236 @@ +--- +title: "Components" +description: "Create complex layouts with component hierarchies" +--- + +## Basic + +Any type that implements `Component` can be used in the `html!` macro: + +```rust +use yew::{Component, Html, html, Context, Properties}; + +struct MyComponent; + +impl Component for MyComponent { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + html! { + { "This component has no properties!" } + } + } +} + +#[derive(Clone, PartialEq, Properties)] +struct Props { + prop1: String, + prop2: String, +} + +struct MyComponentWithProps; + +impl Component for MyComponentWithProps { + type Message = (); + type Properties = Props; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { + { + format!( + "prop1: {} and prop2: {}", + ctx.props().prop1, + ctx.props().prop2 + ) + } + } + } +} + +let props = Props { + prop1: "Hello".to_owned(), + prop2: "World".to_owned(), +}; + + +html!{ + <> + // No properties + + + // With Properties + + + // With the whole set of props provided at once + + + // With Properties from a variable and specific values overridden + + +}; +``` + +## Nested + +Components can be passed children if they have a `children` field in their `Properties`. + +```rust title="parent.rs" +use yew::{Children, Component, Context, html, Html, Properties}; + +#[derive(PartialEq, Properties)] +struct Props { + id: String, + children: Children, +} + +struct Container; + +impl Component for Container { + type Message = (); + type Properties = Props; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ { ctx.props().children.clone() } +
+ } + } +} + +html! { + +

{ "Hi" }

+
{ "Hello" }
+
+}; +``` + +The `html!` macro allows you to pass a base expression with the `..props` syntax instead of specifying each property individually, +similar to Rust's [Functional Update Syntax](https://doc.rust-lang.org/stable/reference/expressions/struct-expr.html#functional-update-syntax). +This base expression must occur after any individual props are passed. +When passing a base props expression with a `children` field, the children passed in the `html!` macro overwrite the ones already present in the props. + +```rust +use yew::{Children, Component, Context, html, Html, props, Properties}; + +#[derive(PartialEq, Properties)] +struct Props { + id: String, + children: Children, +} + +struct Container; + +impl Component for Container { + type Message = (); + type Properties = Props; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { +
+ { ctx.props().children.clone() } +
+ } + } +} + +let props = yew::props!(Container::Properties { + id: "container-2", + children: Children::default(), +}); + +html! { + + // props.children will be overwritten with this + { "I am a child, as you can see" } + +}; +``` + +## Nested Children with Props + +Nested component properties can be accessed and mutated if the containing component types its children. In the following example, the `List` component can wrap `ListItem` components. For a real world example of this pattern, check out the `yew-router` source code. For a more advanced example, check out the `nested-list` example in the main yew repository. + +```rust +use std::rc::Rc; +use yew::{html, ChildrenWithProps, Component, Context, Html, Properties}; + +#[derive(Clone, PartialEq, Properties)] +pub struct ListItemProps { + value: String, +} + +pub struct ListItem; + +impl Component for ListItem { + type Message = (); + type Properties = ListItemProps; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! { + + { ctx.props().value.clone() } + + } + } +} + +#[derive(PartialEq, Properties)] +pub struct Props { + pub children: ChildrenWithProps, +} + +pub struct List; +impl Component for List { + type Message = (); + type Properties = Props; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + html! {{ + for ctx.props().children.iter().map(|mut item| { + let mut props = Rc::make_mut(&mut item.props); + props.value = format!("item-{}", props.value); + item + }) + }} + } +} +html! { + + + + + +}; +``` + +## Relevant examples +- [Boids](https://github.com/yewstack/yew/tree/master/examples/boids) +- [Router](https://github.com/yewstack/yew/tree/master/examples/router) +- [Store](https://github.com/yewstack/yew/tree/master/examples/store) diff --git a/website/versioned_docs/version-0.19.0/concepts/html/elements.md b/website/versioned_docs/version-0.19.0/concepts/html/elements.md new file mode 100644 index 00000000000..f75279c732f --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/html/elements.md @@ -0,0 +1,242 @@ +--- +title: "Elements" +description: "Both HTML and SVG elements are supported" +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +## DOM nodes + +There are many reasons why you might want to create or manage DOM nodes manually in Yew, such as +when integrating with JS libraries that can cause conflicts with managed components. + +Using `web-sys`, you can create DOM elements and convert them into a `Node` - which can then be +used as a `Html` value using `VRef`: + +```rust +use web_sys::{Element, Node}; +use yew::{Component, Context, html, Html}; +use gloo_utils::document; + +struct Comp; + +impl Component for Comp { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, _ctx: &Context) -> Html { + // Create a div element from the document + let div: Element = document().create_element("div").unwrap(); + // Add content, classes etc. + div.set_inner_html("Hello, World!"); + // Convert Element into a Node + let node: Node = div.into(); + // Return that Node as a Html value + Html::VRef(node) + } +} +``` + +## Dynamic tag names + +When building a higher-order component you might find yourself in a situation where the element's tag name isn't static. +For example, you might have a `Title` component which can render anything from `h1` to `h6` depending on a level prop. +Instead of having to use a big match expression, Yew allows you to set the tag name dynamically +using `@{name}` where `name` can be any expression that returns a string. + +```rust +use yew::html; + +let level = 5; +let text = "Hello World!".to_owned(); + +html! { + <@{format!("h{}", level)} class="title">{ text } +}; +``` + +## Boolean Attributes + +Some content attributes (e.g checked, hidden, required) are called boolean attributes. In Yew, +boolean attributes need to be set to a bool value: + +```rust +use yew::html; + +html! { + +}; +``` + +This will result in **HTML** that's functionally equivalent to this: + +```html + +``` + +Setting a boolean attribute to false is equivalent to not using the attribute at all; values from +boolean expressions can be used: + +```rust +use yew::html; + +let no = 1 + 1 != 2; + +html! { + +}; +``` + +This will result in the following **HTML**: + +```html +
This div is NOT hidden.
+``` + +## Optional attributes for HTML elements + +Most HTML attributes can use optional values (Some(x) or None). This allows us to omit the attribute if the attribute is marked as optional. + +```rust +use yew::html; + +let maybe_id = Some("foobar"); + +html! { +
+}; +``` + +If the attribute is set to `None`, the attribute won't be set in the DOM. + +## Listeners + +Listener attributes need to be passed a `Callback` which is a wrapper around a closure. How you create your callback depends on how you wish your app to react to a listener event: + + + + +```rust +use yew::{Component, Context, html, Html}; + +struct MyComponent; + +enum Msg { + Click, +} + +impl Component for MyComponent { + type Message = Msg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + Self + } + + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + match msg { + Msg::Click => { + // Handle Click + } + }; + true + } + + fn view(&self, ctx: &Context) -> Html { + // Create a callback from a component link to handle it in a component + let click_callback = ctx.link().callback(|_| Msg::Click); + html! { + + } + } +} +``` + + + + +```rust +use yew::{html, Component, Context, Html}; +use yew_agent::{Dispatcher, Dispatched}; +use website_test::agents::{MyWorker, WorkerMsg}; + +struct MyComponent { + worker: Dispatcher, +} + +impl Component for MyComponent { + type Message = WorkerMsg; + type Properties = (); + + fn create(_ctx: &Context) -> Self { + MyComponent { + worker: MyWorker::dispatcher(), + } + } + + fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + self.worker.send(msg); + false + } + + fn view(&self, ctx: &Context) -> Html { + // Create a callback from a worker to handle it in another context + let click_callback = ctx.link().callback(|_| WorkerMsg::Process); + html! { + + } + } +} +``` + + + + +```rust +use yew::{Callback, Context, Component, html, Html}; +use weblog::console_log; + +struct MyComponent; + +impl Component for MyComponent { + type Message = (); + type Properties = (); + + fn create(_ctx: &Context) -> Self { + MyComponent + } + + fn view(&self, _ctx: &Context) -> Html { + // Create an ephemeral callback + let click_callback = Callback::from(|_| { + console_log!("clicked!"); + }); + + html! { + + } + } +} +``` + + + + +## Relevant examples + +- [Inner HTML](https://github.com/yewstack/yew/tree/master/examples/inner_html) diff --git a/website/versioned_docs/version-0.19.0/concepts/html/events.md b/website/versioned_docs/version-0.19.0/concepts/html/events.md new file mode 100644 index 00000000000..2d769802c47 --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/html/events.md @@ -0,0 +1,696 @@ +--- +title: "Events" +--- + +## Introduction + +Yew integrates with the [`web-sys`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/) crate and +uses the events from that crate. The [table below](#event-types) lists all of the `web-sys` +events that are accepted in the `html!` macro. + +You can still add a [`Callback`](../components/callbacks.md) for an event that is not listed in the table +below, see [Manual event listener](#manual-event-listener). + +## Event Types + +:::tip +All the event types mentioned in the following table are re-exported under `yew::events`. +Using the types from `yew::events` makes it easier to ensure version compatibility than +if you were to manually include `web-sys` as a dependency in your crate because you won't +end up using a version which conflicts with the version that Yew specifies. +::: + +The event listener name is the expected name when adding an event `Callback` in the `html` macro: + +```rust +use yew::{html, Callback}; + +html! { + +}; +``` + +The event name is the listener without the "on" prefix, therefore, the `onclick` event listener +listens for `click` events. + +| Event listener name | `web_sys` Event Type | +| --------------------------- | ------------------------------------------------------------------------------------- | +| `onabort` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onauxclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | +| `onblur` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | +| `oncancel` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `oncanplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `oncanplaythrough` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | +| `onclose` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `oncontextmenu` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | +| `oncuechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `ondblclick` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | +| `ondrag` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | +| `ondragend` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | +| `ondragenter` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | +| `ondragexit` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | +| `ondragleave` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.htmk) | +| `ondragover` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | +| `ondragstart` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | +| `ondrop` | [DragEvent](https://docs.rs/web-sys/latest/web_sys/struct.DragEvent.html) | +| `ondurationchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onemptied` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onended` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onfocus` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | +| `onfocusin` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | +| `onfocusout` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | +| `onformdata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `oninput` | [InputEvent](https://docs.rs/web-sys/latest/web_sys/struct.InputEvent.html) | +| `oninvalid` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onkeydown` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) | +| `onkeypress` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) | +| `onkeyup` | [KeyboardEvent](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html) | +| `onload` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onloadeddata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onloadedmetadata` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onloadstart` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) | +| `onmousedown` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | +| `onmouseenter` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | +| `onmouseleave` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | +| `onmousemove` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | +| `onmouseout` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | +| `onmouseover` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | +| `onmouseup` | [MouseEvent](https://docs.rs/web-sys/latest/web_sys/struct.MouseEvent.html) | +| `onpause` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onplay` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onplaying` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onprogress` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) | +| `onratechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onreset` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onresize` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onscroll` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onsecuritypolicyviolation` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onseeked` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onseeking` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onselect` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onslotchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onstalled` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onsubmit` | [FocusEvent](https://docs.rs/web-sys/latest/web_sys/struct.FocusEvent.html) | +| `onsuspend` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `ontimeupdate` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `ontoggle` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onvolumechange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onwaiting` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onwheel` | [WheelEvent](https://docs.rs/web-sys/latest/web_sys/struct.WheelEvent.html) | +| `oncopy` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `oncut` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onpaste` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onanimationcancel` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | +| `onanimationend` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | +| `onanimationiteration` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | +| `onanimationstart` | [AnimationEvent](https://docs.rs/web-sys/latest/web_sys/struct.AnimationEvent.html) | +| `ongotpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | +| `onloadend` | [ProgressEvent](https://docs.rs/web-sys/latest/web_sys/struct.ProgressEvent.html) | +| `onlostpointercapture` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | +| `onpointercancel` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | +| `onpointerdown` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | +| `onpointerenter` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | +| `onpointerleave` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | +| `onpointerlockchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onpointerlockerror` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onpointermove` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | +| `onpointerout` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | +| `onpointerover` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | +| `onpointerup` | [PointerEvent](https://docs.rs/web-sys/latest/web_sys/struct.PointerEvent.html) | +| `onselectionchange` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onselectstart` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `onshow` | [Event](https://docs.rs/web-sys/latest/web_sys/struct.Event.html) | +| `ontouchcancel` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | +| `ontouchend` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | +| `ontouchmove` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | +| `ontouchstart` | [TouchEvent](https://docs.rs/web-sys/latest/web_sys/struct.TouchEvent.html) | +| `ontransitioncancel` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | +| `ontransitionend` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | +| `ontransitionrun` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | +| `ontransitionstart` | [TransitionEvent](https://docs.rs/web-sys/latest/web_sys/struct.TransitionEvent.html) | + +## Typed event target + +:::caution +In this section **target ([Event.target](https://developer.mozilla.org/en-US/docs/Web/API/Event/target))** +is always referring to the element at which the event was dispatched from. + + +This will **not** always be the element at which the `Callback` is placed. +::: + +In event `Callback`s you may want to get the target of that event. For example, the +`change` event gives no information but is used to notify that something has changed. + +In Yew getting the target element in the correct type can be done in a few ways and we will go through +them here. Calling [`web_sys::Event::target`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Event.html#method.target) +on an event returns an optional [`web_sys::EventTarget`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.EventTarget.html) +type, which might not seem very useful when you want to know the value of your input element. + +In all the approaches below we are going to tackle the same problem, so it's clear where the approach +differs opposed to the problem at hand. + +**The Problem:** + +We have an `onchange` `Callback` on my `` element and each time it is invoked we want to send +an [update](../components#update) `Msg` to our component. + +Our `Msg` enum looks like this: + +```rust +pub enum Msg { + InputValue(String), +} +``` + +### Using `JsCast` + +The [`wasm-bindgen`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/index.html) crate has +a useful trait; [`JsCast`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html) +which allows us to hop and skip our way to the type we want, as long as it implements `JsCast`. We can +do this cautiously, which involves some runtime checks and failure types like `Option` and `Result`, +or we can do it dangerously. + +Enough talk, more code: + +```toml title="Cargo.toml" +[dependencies] +# need wasm-bindgen for JsCast +wasm-bindgen = "0.2" +``` + +```rust +//highlight-next-line +use wasm_bindgen::JsCast; +use web_sys::{EventTarget, HtmlInputElement}; +use yew::{ + events::Event, + html, + Component, Context, Html, +}; + +pub struct Comp; + +pub enum Msg { + InputValue(String), +} + +impl Component for Comp { + type Message = Msg; + type Properties = (); + + fn create(_: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + let link = ctx.link(); + + // Use batch_callback so if something unexpected happens we can return + // None and do nothing + let on_cautious_change = link.batch_callback(|e: Event| { + // When events are created the target is undefined, it's only + // when dispatched does the target get added. + let target: Option = e.target(); + // Events can bubble so this listener might catch events from child + // elements which are not of type HtmlInputElement + //highlight-next-line + let input = target.and_then(|t| t.dyn_into::().ok()); + + input.map(|input| Msg::InputValue(input.value())) + }); + + let on_dangerous_change = link.callback(|e: Event| { + let target: EventTarget = e + .target() + .expect("Event should have a target when dispatched"); + // You must KNOW target is a HtmlInputElement, otherwise + // the call to value would be Undefined Behaviour (UB). + //highlight-next-line + Msg::InputValue(target.unchecked_into::().value()) + }); + + html! { + <> + + + + } + } +} +``` +:::tip +Use `batch_callback` when you want to conditionally return a value from a `Callback`. +::: + +The methods from `JsCast` are [`dyn_into`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html#method.dyn_into) +and [`unchecked_into`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html#method.unchecked_into) +and you can probably see, they allowed +us to go from `EventTarget` to [`HtmlInputElement`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.HtmlInputElement.html). +The `dyn_into` method is cautious because at +runtime it will check whether the type is actually a `HtmlInputElement` and if not return an +`Err(JsValue)`, the [`JsValue`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/struct.JsValue.html) +is a catch-all type and is essentially giving you back the object to try again. + +At this point you might be thinking... when is the dangerous version ok to use? In the case above it +is safe1 as we've set the `Callback` on to an element with no children so the target can +only be that same element. + + +_1 As safe as anything can be when JS land is involved._ + +### Using `TargetCast` + +**It is highly recommended to read [Using JsCast](#using-jscast) first!** + +:::note +`TargetCast` was designed to feel very similar to `JsCast` - this is to allow new users to get a feel +for the behaviour of `JsCast` but with the smaller scope of events and their targets. + +`TargetCast` vs `JsCast` is purely preference, you will find that `TargetCast` implements something +similar to what you would using `JsCast`. +::: + +The `TargetCast` trait builds off of `JsCast` and is specialized towards getting typed event targets +from events. + +`TargetCast` comes with Yew so no need to add a dependency in order to use the trait methods on events +but it works in a very similar way to `JsCast`. + +```rust +use web_sys::HtmlInputElement; +use yew::{ + events::Event, + html, + // Need to import TargetCast + //highlight-next-line + Component, Context, Html, TargetCast, +}; + +pub struct Comp; + +pub enum Msg { + InputValue(String), +} + +impl Component for Comp { + type Message = Msg; + type Properties = (); + + fn create(_: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + let link = ctx.link(); + + let on_cautious_change = link.batch_callback(|e: Event| { + //highlight-next-line + let input = e.target_dyn_into::(); + + input.map(|input| Msg::InputValue(input.value())) + }); + + let on_dangerous_change = link.callback(|e: Event| { + // You must KNOW target is a HtmlInputElement, otherwise + // the call to value would be Undefined Behaviour (UB). + //highlight-next-line + Msg::InputValue(e.target_unchecked_into::().value()) + }); + + html! { + <> + + + + } + } +} +``` +If you followed the advice above and read about `JsCast`, or know the trait, you can probably +see that `TargetCast::target_dyn_into` feels similar to `JsCast::dyn_into` but specifically +does the cast on the target of the event. `TargetCast::target_unchecked_into` is similar to +`JsCast::unchecked_into`, and as such all the same warnings above `JsCast` apply to `TargetCast`. + + +### Using `NodeRef` + +[`NodeRef`](../components/refs.md) can be used instead of querying the event given to a `Callback`. + +```rust +//highlight-start +use web_sys::HtmlInputElement; +use yew::{html, Component, Context, Html, NodeRef}; +//highlight-end + +pub struct Comp { + //highlight-next-line + my_input: NodeRef, +} + +pub enum Msg { + InputValue(String), +} + +impl Component for Comp { + type Message = Msg; + type Properties = (); + + fn create(_: &Context) -> Self { + Self { + //highlight-next-line + my_input: NodeRef::default(), + } + } + + fn view(&self, ctx: &Context) -> Html { + let link = ctx.link(); + + let my_input_ref = self.my_input.clone(); + + let onchange = link.batch_callback(move |_| { + //highlight-next-line + let input = my_input_ref.cast::(); + + input.map(|input| Msg::InputValue(input.value())) + }); + + html! { + <> + + + } + } +} +``` +Using `NodeRef`, you can ignore the event and use the `NodeRef::cast` method to get an +`Option` - this is optional as calling `cast` before the `NodeRef` has been +set, or when the type doesn't match will return `None`. + +You might also see by using `NodeRef` we don't have to send the `String` back in the +`Msg::InputValue` as we always have `my_input` in the component state - so we could do the following: + +```rust +use web_sys::HtmlInputElement; +use yew::{html, Component, Context, Html, NodeRef}; + +pub struct Comp { + my_input: NodeRef, +} + +pub enum Msg { + // Signal the input element has changed + //highlight-next-line + InputChanged, +} + +impl Component for Comp { + type Message = Msg; + type Properties = (); + + fn create(_: &Context) -> Self { + Self { + my_input: NodeRef::default(), + } + } + + //highlight-start + fn update(&mut self, _: &Context, msg: Self::Message) -> bool { + match msg { + Msg::InputChanged => { + if let Some(input) = self.my_input.cast::() { + let value = input.value(); + // do something with value + + true + } else { + false + } + } + } + } + //highlight-end + + fn view(&self, ctx: &Context) -> Html { + let link = ctx.link(); + //highlight-next-line + let onchange = link.callback(|_| Msg::InputChanged); + + html! { + + } + } +} +``` + +Which approach you take depends on your component and your preferences, there is no _blessed_ way +per se. + +## Manual event listener + +You may want to listen to an event that is not supported by Yew's `html` macro, see the +[supported events listed here](#event-types). + +In order to add an event listener to one of elements manually we need the help of +[`NodeRef`](../components/refs) so that in the `rendered` method we can add a listener using the +[`web-sys`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/index.html) and +[wasm-bindgen](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/index.html) API. +We do this in `rendered` as this is the only time we can guarantee that the element exists in +the browser, Yew needs some time to create them after `view` is called. + +The examples below are going to show adding listeners for the made-up `custard` event. All events +either unsupported by yew or custom can be represented as a +[`web_sys::Event`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Event.html). If you +need to access a specific method or field on a custom / unsupported event then you can use the +methods of [`JsCast`](https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/trait.JsCast.html) +in order to convert to the type required. + +### Using `Closure` (verbose) + +Using the `web-sys` and `wasm-bindgen` API's directly for this can be a bit painful.. so brace +yourself ([there is a more concise way thanks to `gloo`](#using-gloo-concise)). + +```rust +use wasm_bindgen::{prelude::Closure, JsCast}; +use web_sys::HtmlElement; +use yew::{ + events::Event, + html, + Component, Context, Html, NodeRef, +}; + +pub struct Comp { + my_div: NodeRef, + custard_listener: Option>, +} + +pub enum Msg { + Custard, +} + +impl Component for Comp { + type Message = Msg; + type Properties = (); + + fn create(_: &Context) -> Self { + Self { + my_div: NodeRef::default(), + custard_listener: None, + } + } + + fn update(&mut self, _: &Context, msg: Self::Message) -> bool { + match msg { + Msg::Custard => { + // do something about custard.. + true + } + } + } + + fn view(&self, _: &Context) -> Html { + html! { +
+ } + } + + fn rendered(&mut self, ctx: &Context, first_render: bool) { + if !first_render { + return; + } + + if let Some(element) = self.my_div.cast::() { + // Create your Callback as you normally would + let oncustard = ctx.link().callback(|_: Event| Msg::Custard); + + // Create a Closure from a Box - this has to be 'static + let listener = + Closure::::wrap( + Box::new(move |e: Event| oncustard.emit(e)) + ); + element + .add_event_listener_with_callback( + "custard", + listener.as_ref().unchecked_ref() + ) + .unwrap(); + + // Need to save listener in the component otherwise when the + // event is fired it will try and call the listener that no longer + // exists in memory! + self.custard_listener = Some(listener); + } + } + + fn destroy(&mut self, _: &Context) { + // All done with the component but need to remove + // the event listener before the custard_listener memory + // goes out of scope. + if let (Some(element), Some(listener)) = ( + self.my_div.cast::(), + self.custard_listener.take(), + ) { + element + .remove_event_listener_with_callback( + "custard", + listener.as_ref().unchecked_ref() + ) + .unwrap(); + } + } +} +``` + +For more information on `Closures`, see +[The `wasm-bindgen` Guide](https://rustwasm.github.io/wasm-bindgen/examples/closures.html). + +### Using `gloo` (concise) + +The easier way is with `gloo`, more specifically [`gloo_events`](https://docs.rs/gloo-events/0.1.1/gloo_events/index.html) +which is an abstraction for `web-sys`, `wasm-bindgen`. + +`gloo_events` has the `EventListener` type which can be used to create and store the +event listener. + +```toml title="Cargo.toml" +[dependencies] +gloo-events = "0.1" +``` + +```rust +use web_sys::HtmlElement; +use yew::{ + events::Event, + html, + Component, Context, Html, NodeRef, +}; + +use gloo_events::EventListener; + +pub struct Comp { + my_div: NodeRef, + custard_listener: Option, +} + +pub enum Msg { + Custard, +} + +impl Component for Comp { + type Message = Msg; + type Properties = (); + + fn create(_: &Context) -> Self { + Self { + my_div: NodeRef::default(), + custard_listener: None, + } + } + + fn update(&mut self, _: &Context, msg: Self::Message) -> bool { + match msg { + Msg::Custard => { + // do something about custard.. + true + } + } + } + + fn view(&self, _: &Context) -> Html { + html! { +
+ } + } + + fn rendered(&mut self, ctx: &Context, first_render: bool) { + if !first_render { + return; + } + + if let Some(element) = self.my_div.cast::() { + // Create your Callback as you normally would + let oncustard = ctx.link().callback(|_: Event| Msg::Custard); + + let listener = EventListener::new( + &element, + "custard", + move |e| oncustard.emit(e.clone()) + ); + + self.custard_listener = Some(listener); + } + } +} +``` + +Notice that when using an `EventListener` you don't need to do anything when the +component is about to be destroyed as the `EventListener` has a `drop` implementation +which will remove the event listener from the element. + +For more information on `EventListener`, see the +[gloo_events docs.rs](https://docs.rs/gloo-events/0.1.1/gloo_events/struct.EventListener.html). \ No newline at end of file diff --git a/website/versioned_docs/version-0.19.0/concepts/html/fragments.md b/website/versioned_docs/version-0.19.0/concepts/html/fragments.md new file mode 100644 index 00000000000..9347643f831 --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/html/fragments.md @@ -0,0 +1,43 @@ +--- +title: "Fragments" +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The `html!` macro always requires a single root node. In order to get around this restriction, you +can use an "empty tag" (these are also called "fragments"). + + + + +```rust +use yew::html; + +html! { + <> +
+

+ +}; + +``` + +
+ + + +```rust, compile_fail +use yew::html; + +// error: only one root html element allowed + +html! { +
+

+}; + +``` + +
+
diff --git a/website/versioned_docs/version-0.19.0/concepts/html/introduction.md b/website/versioned_docs/version-0.19.0/concepts/html/introduction.md new file mode 100644 index 00000000000..8226e1ef306 --- /dev/null +++ b/website/versioned_docs/version-0.19.0/concepts/html/introduction.md @@ -0,0 +1,230 @@ +--- +title: "HTML" +sidebar_label: Introduction +description: "The procedural macro for generating HTML and SVG" +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +The `html!` macro allows you to write HTML and SVG code declaratively. It is similar to JSX +\(an extension to JavaScript which allows you to write HTML-like code inside of JavaScript\). + +**Important notes** + +1. The `html!` macro only accepts one root html node \(you can counteract this by + [using fragments or iterators](./../html/lists.md)\) +2. An empty `html! {}` invocation is valid and will not render anything +3. Literals must always be quoted and wrapped in braces: `html! { "Hello, World" }` + +:::note +The `html!` macro can reach the default recursion limit of the compiler. If you encounter compilation errors, add an attribute like `#![recursion_limit="1024"]` in the crate root to overcome the problem. +::: + +## Tag Structure + +Tags are based on HTML tags. Components, Elements, and Lists are all based on this tag syntax. + +Tags must either self-close `<... />` or have a corresponding end tag for each start tag. + + + + +```rust +use yew::html; + +html! { +
+}; +``` + +
+ + +```rust ,compile_fail +use yew::html; + +html! { +
// <- MISSING CLOSE TAG +}; +``` + + + + + + + +```rust +use yew::html; + +html! { + +}; +``` + + + + +```rust ,compile_fail +use yew::html; + +html! { + // <- MISSING SELF-CLOSE +}; +``` + + + + +:::tip +For convenience, elements which _usually_ require a closing tag are **allowed** to self-close. For example, writing `html! {
}` is valid. +::: + +## Children + +Create complex nested HTML and SVG layouts with ease: + + + + +```rust +use yew::html; + +html! { +
+
+
+ + + + +