Add testing for website code blocks (#2014)

* Add doc-test to test website code snippets

Heavily inspired by tokio-rs/website repo.

* Fix code snippets to pass doc tests

Some code snippets are explicitly ignored and some are not run
to avoid having to include dependencies for one liners.

* Add website code snippet tests to CI

* Fix CI

* Remove doc-test from workspace

* Exclude doc-test from workspace

* Refactor code snippets and tests

Code snippets can import types from doc_test crate i.e.:
```rust
use doc_test::agents::EventBus;
```
This allows for moving some boilerplate away from the example and still
checks that the code compiles correctly.

Also some slight changes to some of the examples and the information
about `ComponentLink` which is deprecated.

* Move doc-test to packages

* Rename doc-test crate to website-test

The new name makes it more clear the purpose of this crate.

* fix ci
This commit is contained in:
mc1098 2021-08-28 12:17:28 +01:00 committed by GitHub
parent bd8ecf5d5e
commit ed2e1ea00e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1098 additions and 255 deletions

View File

@ -105,6 +105,11 @@ jobs:
run: | run: |
cd packages/yew cd packages/yew
cargo test --doc --features "doc_test wasm_test" cargo test --doc --features "doc_test wasm_test"
- name: Run website code snippet tests
run: |
cd packages/website-test
cargo test
integration_tests: integration_tests:
name: Integration Tests on ${{ matrix.toolchain }} name: Integration Tests on ${{ matrix.toolchain }}

View File

@ -35,3 +35,6 @@ members = [
# Release tools # Release tools
"packages/changelog", "packages/changelog",
] ]
exclude = [
"packages/website-test",
]

View File

@ -37,7 +37,7 @@ category = "Testing"
description = "Run all tests" description = "Run all tests"
dependencies = ["tests-setup"] dependencies = ["tests-setup"]
env = { CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*", "**/packages/changelog"] } env = { CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*", "**/packages/changelog"] }
run_task = { name = ["test-flow", "doc-test-flow"], fork = true } run_task = { name = ["test-flow", "doc-test-flow", "website-test"], fork = true }
[tasks.benchmarks] [tasks.benchmarks]
category = "Testing" category = "Testing"
@ -86,6 +86,14 @@ private = true
command = "cargo" command = "cargo"
args = ["test", "--doc"] args = ["test", "--doc"]
[tasks.website-test]
script = [
"""
cd packages/website-test
cargo test
"""
]
[tasks.bench-flow] [tasks.bench-flow]
private = true private = true
workspace = true workspace = true

View File

@ -0,0 +1,20 @@
[package]
name = "website-test"
version = "0.1.0"
edition = "2018"
build = "build.rs"
publish = false
[dependencies]
yew-agent = { path = "../../packages/yew-agent/" }
[dev-dependencies]
yew = { path = "../../packages/yew/" }
yew-router = { path = "../../packages/yew-router/" }
derive_more = "0.99"
boolinator = "2.4"
weblog = "0.3.0"
wasm-bindgen = "0.2"
[build-dependencies]
glob = "0.3"

View File

@ -0,0 +1,5 @@
[tasks.doc-test]
command = "cargo"
args = [
"test"
]

View File

@ -0,0 +1,100 @@
use glob::glob;
use std::collections::HashMap;
use std::env;
use std::fmt::{self, Write};
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Default)]
struct Level {
nested: HashMap<String, Level>,
files: Vec<PathBuf>,
}
fn main() {
let home = env::var("CARGO_MANIFEST_DIR").unwrap();
let pattern = format!("{}/../../website/docs/**/*.md", home);
let base = format!("{}/../../website", home);
let base = Path::new(&base).canonicalize().unwrap();
let mut level = Level::default();
for entry in glob(&pattern).unwrap() {
let path = entry.unwrap();
let path = Path::new(&path).canonicalize().unwrap();
let rel = path.strip_prefix(&base).unwrap();
let mut parts = vec![];
for part in rel {
parts.push(part.to_str().unwrap());
}
level.insert(path.clone(), &parts[..]);
}
let out = format!("{}/website_tests.rs", env::var("OUT_DIR").unwrap());
fs::write(&out, level.to_contents()).unwrap();
}
impl Level {
fn insert(&mut self, path: PathBuf, rel: &[&str]) {
if rel.len() == 1 {
self.files.push(path);
} else {
let nested = self.nested.entry(rel[0].to_string()).or_default();
nested.insert(path, &rel[1..]);
}
}
fn to_contents(&self) -> String {
let mut dst = String::new();
self.write_inner(&mut dst, 0).unwrap();
dst
}
fn write_into(&self, dst: &mut String, name: &str, level: usize) -> fmt::Result {
self.write_space(dst, level);
let name = name.replace(|c| c == '-' || c == '.', "_");
writeln!(dst, "pub mod {} {{", name)?;
self.write_inner(dst, level + 1)?;
self.write_space(dst, level);
writeln!(dst, "}}")?;
Ok(())
}
fn write_inner(&self, dst: &mut String, level: usize) -> fmt::Result {
for (name, nested) in &self.nested {
nested.write_into(dst, name, level)?;
}
self.write_space(dst, level);
for file in &self.files {
let stem = Path::new(file)
.file_stem()
.unwrap()
.to_str()
.unwrap()
.replace("-", "_");
self.write_space(dst, level);
writeln!(dst, "#[doc = include_str!(\"{}\")]", file.display())?;
self.write_space(dst, level);
writeln!(dst, "pub fn {}_md() {{}}", stem)?;
}
Ok(())
}
fn write_space(&self, dst: &mut String, level: usize) {
for _ in 0..level {
dst.push_str(" ");
}
}
}

View File

@ -0,0 +1,50 @@
//! Agent types that compile to be used by website code snippets
use yew_agent::{Agent, AgentLink, Context, HandlerId};
pub struct EventBus;
impl Agent for EventBus {
type Reach = Context<Self>;
type Message = ();
type Input = ();
type Output = String;
fn create(_link: AgentLink<Self>) -> Self {
Self
}
fn update(&mut self, _msg: Self::Message) {
// impl
}
fn handle_input(&mut self, _msg: Self::Input, _id: HandlerId) {
// impl
}
}
pub enum WorkerMsg {
Process,
}
pub struct MyWorker;
impl Agent for MyWorker {
type Reach = Context<Self>;
type Message = ();
type Input = WorkerMsg;
type Output = ();
fn create(_link: AgentLink<Self>) -> Self {
Self
}
fn update(&mut self, _msg: Self::Message) {
// impl
}
fn handle_input(&mut self, _msg: Self::Input, _id: HandlerId) {
// impl
}
}

View File

@ -0,0 +1,3 @@
pub mod agents;
include!(concat!(env!("OUT_DIR"), "/website_tests.rs"));

View File

@ -95,7 +95,7 @@ implementation of the main page and render the component you are working on on t
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. 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 ```rust ,ignore
// Use `wee_alloc` as the global allocator. // Use `wee_alloc` as the global allocator.
#[global_allocator] #[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

View File

@ -19,23 +19,33 @@ in the lifecycle of a component.
### Create ### Create
When a component is created, it receives properties from its parent component as well as a `ComponentLink`. 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. When a component is created, it receives properties from its parent component and is stored within
the `Context<Self>` thats passed down to the `create` method. The properties can be used to
It is common to store the props (data which can be passed from parent to child components) and the initialize the component's state and the "link" can be used to register callbacks or send messages to the component.
`ComponentLink` in your component struct, like so:
```rust ```rust
use yew::{Component, Context, html, Html, Properties};
#[derive(PartialEq, Properties)]
pub struct Props;
pub struct MyComponent; pub struct MyComponent;
impl Component for MyComponent { impl Component for MyComponent {
type Message = ();
type Properties = Props; type Properties = Props;
// ...
// highlight-start
fn create(ctx: &Context<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
MyComponent MyComponent
} }
// highlight-end
// ... fn view(&self, _ctx: &Context<Self>) -> Html {
html! {
// impl
}
}
} }
``` ```
@ -49,15 +59,35 @@ 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}`. 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 ```rust
impl Component for MyComponent { 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 {
Self
}
// highlight-start
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
let onclick = ctx.link().callback(|_| Msg::Click); let onclick = ctx.link().callback(|_| Msg::Click);
html! { html! {
<button {onclick}>{ ctx.props().button_text }</button> <button {onclick}>{ &ctx.props().button_text }</button>
} }
} }
// highlight-end
} }
``` ```
@ -72,15 +102,24 @@ is also a parameter called `first_render` which can be used to determine whether
being called on the first render, or instead a subsequent one. being called on the first render, or instead a subsequent one.
```rust ```rust
use web_sys::HtmlInputElement; use yew::{
use yew::prelude::*; Component, Context, html, Html, NodeRef,
web_sys::HtmlInputElement
};
pub struct MyComponent { pub struct MyComponent {
node_ref: NodeRef, node_ref: NodeRef,
} }
impl Component for MyComponent { impl Component for MyComponent {
// ... type Message = ();
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self {
node_ref: NodeRef::default(),
}
}
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
@ -88,6 +127,7 @@ impl Component for MyComponent {
} }
} }
// highlight-start
fn rendered(&mut self, _ctx: &Context<Self>, first_render: bool) { fn rendered(&mut self, _ctx: &Context<Self>, first_render: bool) {
if first_render { if first_render {
if let Some(input) = self.node_ref.cast::<HtmlInputElement>() { if let Some(input) = self.node_ref.cast::<HtmlInputElement>() {
@ -95,6 +135,7 @@ impl Component for MyComponent {
} }
} }
} }
// highlight-end
} }
``` ```
@ -112,27 +153,50 @@ by event listeners, child components, Agents, Services, or Futures.
Here's an example of what an implementation of `update` could look like: Here's an example of what an implementation of `update` could look like:
```rust ```rust
use yew::{Component, Context, html, Html};
// highlight-start
pub enum Msg { pub enum Msg {
SetInputEnabled(bool) SetInputEnabled(bool)
} }
// highlight-end
struct MyComponent {
input_enabled: bool,
}
impl Component for MyComponent { impl Component for MyComponent {
// highlight-next-line
type Message = Msg; type Message = Msg;
type Properties = ();
// ... fn create(_ctx: &Context<Self>) -> Self {
Self {
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool { input_enabled: false,
match msg { }
Msg::SetInputEnabled(enabled) => {
if self.input_enabled != enabled {
self.input_enabled = enabled;
true // Re-render
} else {
false
}
}
}
} }
// highlight-start
fn update(&mut self, _ctx: &Context<Self>, 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<Self>) -> Html {
html! {
// impl
}
}
} }
``` ```
@ -153,7 +217,7 @@ before it is destroyed. This method is optional and does nothing by default.
The `Component` trait has two associated types: `Message` and `Properties`. The `Component` trait has two associated types: `Message` and `Properties`.
```rust ```rust ,ignore
impl Component for MyComponent { impl Component for MyComponent {
type Message = Msg; type Message = Msg;
type Properties = Props; type Properties = Props;

View File

@ -30,15 +30,43 @@ There is a different method called `callback_once` which accepts a `FnOnce` inst
You should use this with care though, as the resulting callback will panic if executed more than once. You should use this with care though, as the resulting callback will panic if executed more than once.
```rust ```rust
// Create a callback that accepts some text and sends it to the component as the `Msg::Text` message variant. use yew::{html, Component, Context, Html};
let cb = link.callback(|text: String| Msg::Text(text));
// The previous line is needlessly verbose to make it clearer. enum Msg {
// It can be simplified it to this: Text(String),
let cb = link.callback(Msg::Text); }
// Will send `Msg::Text("Hello World!")` to the component. struct Comp;
cb.emit("Hello World!".to_owned());
impl Component for Comp {
type Message = Msg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> 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` ### `batch_callback`
@ -70,9 +98,31 @@ They have an `emit` function that takes their `<IN>` type as an argument and con
A simple use of a callback might look something like this: A simple use of a callback might look something like this:
```rust ```rust
let onclick = link.callback(|_| Msg::Clicked); use yew::{html, Component, Context, Html};
html! {
<button {onclick}>{ "Click" }</button> enum Msg {
Clicked,
}
struct Comp;
impl Component for Comp {
type Message = Msg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html {
// highlight-next-line
let onclick = ctx.link().callback(|_| Msg::Clicked);
html! {
// highlight-next-line
<button {onclick}>{ "Click" }</button>
}
}
} }
``` ```
@ -81,16 +131,38 @@ The function passed to `callback` must always take a parameter. For example, the
If you need a callback that might not need to cause an update, use `batch_callback`. If you need a callback that might not need to cause an update, use `batch_callback`.
```rust ```rust
let onkeypress = link.batch_callback(|event| { use yew::{html, Component, Context, Html, KeyboardEvent};
if event.key() == "Enter" {
Some(Msg::Submit)
} else {
None
}
});
html! { enum Msg {
<input type="text" {onkeypress} /> Submit,
}
struct Comp;
impl Component for Comp {
type Message = Msg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html {
// highlight-start
let onkeypress = ctx.link().batch_callback(|event: KeyboardEvent| {
if event.key() == "Enter" {
Some(Msg::Submit)
} else {
None
}
});
html! {
<input type="text" {onkeypress} />
}
// highlight-end
}
} }
``` ```

View File

@ -9,7 +9,7 @@ what type of children the component has. In such cases, the below example will
suffice. suffice.
```rust ```rust
use yew::prelude::*; use yew::{html, Children, Component, Context, Html, Properties};
#[derive(Properties, PartialEq)] #[derive(Properties, PartialEq)]
pub struct ListProps { pub struct ListProps {
@ -20,8 +20,12 @@ pub struct ListProps {
pub struct List; pub struct List;
impl Component for List { impl Component for List {
type Message = ();
type Properties = ListProps; type Properties = ListProps;
// ...
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
@ -40,10 +44,24 @@ In cases where you want one type of component to be passed as children to your c
you can use `yew::html::ChildrenWithProps<T>`. you can use `yew::html::ChildrenWithProps<T>`.
```rust ```rust
use yew::html::ChildrenWithProps; use yew::{html, ChildrenWithProps, Component, Context, Html, Properties};
use yew::prelude::*;
// ... pub struct Item;
impl Component for Item {
type Message = ();
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, _ctx: &Context<Self>) -> Html {
html! {
{ "item" }
}
}
}
#[derive(Properties, PartialEq)] #[derive(Properties, PartialEq)]
pub struct ListProps { pub struct ListProps {
@ -53,9 +71,13 @@ pub struct ListProps {
pub struct List; pub struct List;
impl Component for ListProps { impl Component for List {
type Message = ();
type Properties = ListProps; type Properties = ListProps;
// ...
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
@ -76,19 +98,53 @@ for better ergonomics. If you don't want to use it, you can manually implement
`From` for each variant. `From` for each variant.
```rust ```rust
use yew::prelude::*; use yew::{
use yew::html::ChildrenRenderer; html, html::ChildrenRenderer, virtual_dom::VChild, Component,
use yew::virtual_dom::{ VChild, VComp }; Context, Html, Properties,
};
// `derive_more::From` implements `From<VChild<Primary>>` and pub struct Primary;
// `From<VChild<Secondary>>` for `Item` automatically!
#[derive(Clone, derive_more::From)] impl Component for Primary {
type Message = ();
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, _ctx: &Context<Self>) -> Html {
html! {
{ "Primary" }
}
}
}
pub struct Secondary;
impl Component for Secondary {
type Message = ();
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, _ctx: &Context<Self>) -> Html {
html! {
{ "Secondary" }
}
}
}
#[derive(Clone, derive_more::From, PartialEq)]
pub enum Item { pub enum Item {
Primary(VChild<Primary>), Primary(VChild<Primary>),
Secondary(VChild<Secondary>), Secondary(VChild<Secondary>),
} }
// Now, we implement `Into<Html>` so that yew knows how to render `Item`. // Now, we implement `Into<Html>` so that yew knows how to render `Item`.
#[allow(clippy::from_over_into)]
impl Into<Html> for Item { impl Into<Html> for Item {
fn into(self) -> Html { fn into(self) -> Html {
match self { match self {
@ -107,8 +163,12 @@ pub struct ListProps {
pub struct List; pub struct List;
impl Component for List { impl Component for List {
type Message = ();
type Properties = ListProps; type Properties = ListProps;
// ...
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
@ -124,9 +184,27 @@ impl Component for List {
You can also have a single optional child component of a specific type too: You can also have a single optional child component of a specific type too:
```rust ```rust
use yew::prelude::*; use yew::{
use yew::virtual_dom::VChild; 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 {
Self
}
fn view(&self, _ctx: &Context<Self>) -> Html {
html! {
{ "sidebar" }
}
}
}
#[derive(Properties, PartialEq)] #[derive(Properties, PartialEq)]
pub struct PageProps { pub struct PageProps {
@ -137,8 +215,12 @@ pub struct PageProps {
struct Page; struct Page;
impl Component for Page { impl Component for Page {
type Message = ();
type Properties = PageProps; type Properties = PageProps;
// ...
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html! { html! {
@ -149,20 +231,22 @@ impl Component for Page {
} }
} }
} }
```
The page component can be called either with the sidebar or without: // The page component can be called either with the sidebar or without:
```rust pub fn render_page(with_sidebar: bool) -> Html {
// Page without sidebar if with_sidebar {
html! { // Page with sidebar
<Page /> html! {
} <Page sidebar={{html_nested! {
<PageSideBar />
// Page with sidebar }}} />
html! { }
<Page sidebar={html_nested! { } else {
<PageSideBar /> // Page without sidebar
}} /> html! {
<Page />
}
} }
}
``` ```

View File

@ -91,15 +91,47 @@ The macro uses the same syntax as a struct expression except that you can't use
The type path can either point to the props directly (`path::to::Props`) or the associated properties of a component (`MyComp::Properties`). The type path can either point to the props directly (`path::to::Props`) or the associated properties of a component (`MyComp::Properties`).
```rust ```rust
let props = yew::props!(LinkProps { use std::rc::Rc;
href: "/", use yew::{props, Properties};
text: Rc::from("imagine this text being really long"),
size: 64,
});
// build the associated properties of a component #[derive(Clone, PartialEq)]
let props = yew::props!(Model::Properties { pub enum LinkColor {
href: "/book", Blue,
text: Rc::from("my bestselling novel"), 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<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<u32>,
/// 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
}
}
``` ```

View File

@ -14,16 +14,39 @@ a canvas element after it has been rendered from `view`.
The syntax is: The syntax is:
```rust ```rust
// In create use yew::{html, web_sys::Element, Component, Context, Html, NodeRef};
self.node_ref = NodeRef::default();
// In view struct Comp {
html! { node_ref: NodeRef,
<div ref={self.node_ref.clone()}></div>
} }
// In rendered impl Component for Comp {
let has_attributes = self.node_ref.cast::<Element>().unwrap().has_attributes(); type Message = ();
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self {
// highlight-next-line
node_ref: NodeRef::default(),
}
}
fn view(&self, _ctx: &Context<Self>) -> Html {
html! {
// highlight-next-line
<div ref={self.node_ref.clone()}></div>
}
}
fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
// highlight-start
let has_attributes = self.node_ref
.cast::<Element>()
.unwrap()
.has_attributes();
// highlight-end
}
}
``` ```
## Relevant examples ## Relevant examples

View File

@ -8,19 +8,71 @@ Generally data is passed down the component tree using props but that becomes te
user preferences, authentication information etc. Consider the following example which passes down the user preferences, authentication information etc. Consider the following example which passes down the
theme using props: theme using props:
```rust ```rust
// root use yew::{html, Children, Component, Context, Html, Properties};
let theme = // ...
html! { #[derive(Clone, PartialEq)]
<Navbar {theme} /> pub struct Theme {
foreground: String,
background: String,
} }
// Navbar component #[derive(PartialEq, Properties)]
html! { pub struct NavbarProps {
<div> theme: Theme,
<Title {theme}>{ "App title" }<Title>
<NavButton {theme}>{ "Somewhere" }</NavButton>
</div>
} }
pub struct Navbar;
impl Component for Navbar {
type Message = ();
type Properties = NavbarProps;
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<div>
<Title theme={ctx.props().theme.clone()}>
{ "App title" }
</Title>
<NavButton theme={ctx.props().theme.clone()}>
{ "Somewhere" }
</NavButton>
</div>
}
}
}
#[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! {
<Navbar {theme} />
};
``` ```
Passing down data like this isn't ideal for something like a theme which needs to be available everywhere. Passing down data like this isn't ideal for something like a theme which needs to be available everywhere.
@ -54,14 +106,36 @@ The `Scope::context` method is used to consume contexts in struct components.
##### Example ##### Example
```rust ```rust
use yew::{Callback, html, Component, Context, Html};
#[derive(Clone, Debug, PartialEq)]
struct Theme {
foreground: String,
background: String,
}
struct ContextDemo; struct ContextDemo;
impl Component for ContextDemo { impl Component for ContextDemo {
/// ... type Message = ();
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
let theme = ctx.link().context::<Theme>(); let (theme, _) = ctx
.link()
.context::<Theme>(Callback::noop())
.expect("context to be set");
html! { html! {
<button style={format!("background: {}; color: {};", theme.background, theme.foreground)}> <button style={format!(
"background: {}; color: {};",
theme.background,
theme.foreground
)}
>
{ "Click me!" } { "Click me!" }
</button> </button>
} }

View File

@ -15,6 +15,8 @@ Hooks allow function components to use state and other Yew features without impl
The easiest way to create a function component is to add the [`#[function_component]`](function-components/attribute.md) attribute to a function. The easiest way to create a function component is to add the [`#[function_component]`](function-components/attribute.md) attribute to a function.
```rust ```rust
use yew::{function_component, html};
#[function_component(HelloWorld)] #[function_component(HelloWorld)]
fn hello_world() -> Html { fn hello_world() -> Html {
html! { "Hello world" } html! { "Hello world" }

View File

@ -12,7 +12,18 @@ The attribute doesn't replace your original function with a component. You need
Assuming you have a function called `chat_container` and you add the attribute `#[function_component(ChatContainer)]` you can use the component like this: Assuming you have a function called `chat_container` and you add the attribute `#[function_component(ChatContainer)]` you can use the component like this:
```rust ```rust
html! { <ChatContainer /> } use yew::{function_component, html, Html};
#[function_component(ChatContainer)]
pub fn chat_container() -> Html {
html! {
// chat container impl
}
}
html! {
<ChatContainer />
};
``` ```
## Example ## Example
@ -21,6 +32,8 @@ html! { <ChatContainer /> }
<!--With props--> <!--With props-->
```rust ```rust
use yew::{function_component, html, Properties};
#[derive(Properties, PartialEq)] #[derive(Properties, PartialEq)]
pub struct RenderedAtProps { pub struct RenderedAtProps {
pub time: String, pub time: String,
@ -40,13 +53,15 @@ pub fn rendered_at(props: &RenderedAtProps) -> Html {
<!--Without props--> <!--Without props-->
```rust ```rust
use yew::{function_component, html, use_state, Callback};
#[function_component(App)] #[function_component(App)]
fn app() -> Html { fn app() -> Html {
let (counter, set_counter) = use_state(|| 0); let counter = use_state(|| 0);
let onclick = { let onclick = {
let counter = Rc::clone(&counter); let counter = counter.clone();
Callback::from(move |_| set_counter(*counter + 1)) Callback::from(move |_| counter.set(*counter + 1))
}; };
html! { html! {
@ -54,7 +69,7 @@ fn app() -> Html {
<button {onclick}>{ "Increment value" }</button> <button {onclick}>{ "Increment value" }</button>
<p> <p>
<b>{ "Current value: " }</b> <b>{ "Current value: " }</b>
{ counter } { *counter }
</p> </p>
</div> </div>
} }
@ -68,30 +83,47 @@ fn app() -> Html {
The `#[function_component(_)]` attribute also works with generic functions for creating generic components. The `#[function_component(_)]` attribute also works with generic functions for creating generic components.
```rust ```rust
use std::fmt::Display;
use yew::{function_component, html, Properties};
#[derive(Properties, PartialEq)] #[derive(Properties, PartialEq)]
pub struct Props<T> pub struct Props<T>
where T: PartialEq where
T: PartialEq,
{ {
data: T, data: T,
} }
#[function_component(MyGenericComponent)] #[function_component(MyGenericComponent)]
pub fn my_generic_component<T>(props: &Props<T>) -> Html pub fn my_generic_component<T>(props: &Props<T>) -> Html
where T: PartialEq + Display where
T: PartialEq + Display,
{ {
html! { html! {
<p> <p>
{ props.data } { &props.data }
</p> </p>
} }
} }
#[derive(PartialEq)]
struct Foo;
impl Display for Foo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("foo")
}
}
// used like this // used like this
html! { html! {
<MyGenericComponent<i32> data=123 /> <MyGenericComponent<i32> data=123 />
} };
// or // or
let foo = Foo;
html! { html! {
<MyGenericComponent<Foo> data={foo} /> <MyGenericComponent<Foo> data={foo} />
} };
``` ```

View File

@ -9,17 +9,23 @@ Component's stateful logic can be extracted into usable function by creating cus
Consider that we have a component which subscribes to an agent and displays the messages sent to it. Consider that we have a component which subscribes to an agent and displays the messages sent to it.
```rust ```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)] #[function_component(ShowMessages)]
pub fn show_messages() -> Html { pub fn show_messages() -> Html {
let (state, set_state) = use_state(|| vec![]); let state = use_state(Vec::new);
{ {
let mut state = Rc::clone(&state); let state = state.clone();
use_effect(move || { use_effect(move || {
let producer = EventBus::bridge(Callback::from(move |msg| { let producer = EventBus::bridge(Callback::from(move |msg| {
let mut messages = (*state).clone(); let mut messages = (*state).clone();
messages.push(msg); messages.push(msg);
set_state(messages) state.set(messages)
})); }));
|| drop(producer) || drop(producer)
@ -38,6 +44,8 @@ We'll start by creating a new function called `use_subscribe`.
The `use_` prefix conventionally denotes that a function is a hook. The `use_` prefix conventionally denotes that a function is a hook.
This function will take no arguments and return `Rc<RefCell<Vec<String>>>`. This function will take no arguments and return `Rc<RefCell<Vec<String>>>`.
```rust ```rust
use std::{cell::RefCell, rc::Rc};
fn use_subscribe() -> Rc<RefCell<Vec<String>>> { fn use_subscribe() -> Rc<RefCell<Vec<String>>> {
todo!() todo!()
} }
@ -48,19 +56,27 @@ We'll use `use_state` hook to store the `Vec` for messages, so they persist betw
We'll also use `use_effect` to subscribe to the `EventBus` `Agent` so the subscription can be tied to component's lifecycle. We'll also use `use_effect` to subscribe to the `EventBus` `Agent` so the subscription can be tied to component's lifecycle.
```rust ```rust
fn use_subscribe() -> Rc<Vec<String>> { use std::collections::HashSet;
let (state, set_state) = use_state(Vec::new); 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<String> {
let state = use_state(Vec::new);
let effect_state = state.clone();
use_effect(move || { use_effect(move || {
let producer = EventBus::bridge(Callback::from(move |msg| { let producer = EventBus::bridge(Callback::from(move |msg| {
let mut messages = (*state).clone(); let mut messages = (*effect_state).clone();
messages.push(msg); messages.push(msg);
set_state(messages) effect_state.set(messages)
})); }));
|| drop(producer) || drop(producer)
}); });
state (*state).clone()
} }
``` ```

View File

@ -15,6 +15,8 @@ This value remains up-to-date on subsequent renders.
### Example ### Example
```rust ```rust
use yew::{Callback, function_component, html, use_state};
#[function_component(UseState)] #[function_component(UseState)]
fn state() -> Html { fn state() -> Html {
let counter = use_state(|| 0); let counter = use_state(|| 0);
@ -46,19 +48,25 @@ If you need the component to be re-rendered on state change, consider using [`us
### Example ### Example
```rust ```rust
use yew::{
function_component, html, use_ref, use_state,
web_sys::{Event, HtmlInputElement},
Callback, TargetCast,
};
#[function_component(UseRef)] #[function_component(UseRef)]
fn ref_hook() -> Html { fn ref_hook() -> Html {
let message = use_state(|| "".to_string()); let message = use_state(|| "".to_string());
let message_count = use_ref(|| 0); let message_count = use_ref(|| 0);
let onclick = Callback::from(move |e| { let onclick = Callback::from(move |_| {
let window = yew::utils::window(); let window = yew::utils::window();
if *message_count.borrow_mut() > 3 { if *message_count.borrow_mut() > 3 {
window.alert_with_message("Message limit reached"); window.alert_with_message("Message limit reached").unwrap();
} else { } else {
*message_count.borrow_mut() += 1; *message_count.borrow_mut() += 1;
window.alert_with_message("Message sent"); window.alert_with_message("Message sent").unwrap();
} }
}); });
@ -72,14 +80,13 @@ fn ref_hook() -> Html {
html! { html! {
<div> <div>
<input {onchange} value={message} /> <input {onchange} value={(*message).clone()} />
<button {onclick}>{ "Send" }</button> <button {onclick}>{ "Send" }</button>
</div> </div>
} }
} }
``` ```
## `use_reducer` ## `use_reducer`
`use_reducer` is an alternative to [`use_state`](#use_state). It is used to handle component's state and is used `use_reducer` is an alternative to [`use_state`](#use_state). It is used to handle component's state and is used
@ -95,6 +102,9 @@ For lazy initialization, consider using [`use_reducer_with_init`](#use_reducer_w
### Example ### Example
```rust ```rust
use std::rc::Rc;
use yew::{function_component, html, use_reducer, Callback};
#[function_component(UseReducer)] #[function_component(UseReducer)]
fn reducer() -> Html { fn reducer() -> Html {
/// reducer's Action /// reducer's Action
@ -114,13 +124,13 @@ fn reducer() -> Html {
counter: match action { counter: match action {
Action::Double => prev.counter * 2, Action::Double => prev.counter * 2,
Action::Square => prev.counter * prev.counter, Action::Square => prev.counter * prev.counter,
} },
}, },
// initial state // initial state
CounterState { counter: 1 }, CounterState { counter: 1 },
); );
let double_onclick = { let double_onclick = {
let counter = counter.clone(); let counter = counter.clone();
Callback::from(move |_| counter.dispatch(Action::Double)) Callback::from(move |_| counter.dispatch(Action::Double))
}; };
@ -149,16 +159,34 @@ This is useful for lazy initialization where it is beneficial not to perform exp
computation up-front. computation up-front.
```rust ```rust
let counter = use_reducer_with_init( use std::rc::Rc;
// reducer function use yew::{function_component, use_reducer_with_init, html};
|prev: Rc<CounterState>, action: i32| CounterState {
counter: prev.counter + action, #[function_component(ReducerWithInit)]
}, fn reducer_with_init() -> Html {
0, // initial value
|initial: i32| CounterState { // init method /// reducer's State
counter: initial + 10, struct CounterState {
}, counter: i32,
); }
let counter = use_reducer_with_init(
// reducer function
|prev: Rc<CounterState>, action: i32| CounterState {
counter: prev.counter + action,
},
0, // initial value
|initial: i32| CounterState { // init method
counter: initial + 10,
},
);
html! {
<>
<div id="result">{ counter.counter }</div>
</>
}
}
``` ```
## `use_effect` ## `use_effect`
@ -173,27 +201,29 @@ The destructor can be used to clean up the effects introduced and it can take ow
### Example ### Example
```rust ```rust
use yew::{Callback, function_component, html, use_effect, use_state};
#[function_component(UseEffect)] #[function_component(UseEffect)]
fn effect() -> Html { fn effect() -> Html {
let (counter, set_counter) = use_state(|| 0); let counter = use_state(|| 0);
{ {
let counter = counter.clone(); let counter = counter.clone();
use_effect(move || { use_effect(move || {
// Make a call to DOM API after component is rendered // Make a call to DOM API after component is rendered
yew::utils::document().set_title(&format!("You clicked {} times", counter)); yew::utils::document().set_title(&format!("You clicked {} times", *counter));
// Perform the cleanup // Perform the cleanup
|| yew::utils::document().set_title("You clicked 0 times") || yew::utils::document().set_title("You clicked 0 times")
}); });
} }
let onclick = { let onclick = {
let counter = Rc::clone(&counter); let counter = counter.clone();
Callback::from(move |_| set_counter(*counter + 1)) Callback::from(move |_| counter.set(*counter + 1))
}; };
html! { html! {
<button {onclick}>{ format!("Increment to {}", counter) }</button> <button {onclick}>{ format!("Increment to {}", *counter) }</button>
} }
} }
``` ```
@ -201,7 +231,9 @@ fn effect() -> Html {
### `use_effect_with_deps` ### `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`. Sometimes, it's needed to manually define dependencies for [`use_effect`](#use_effect). In such cases, we use `use_effect_with_deps`.
```rust ```rust ,no_run
use yew::use_effect_with_deps;
use_effect_with_deps( use_effect_with_deps(
move |_| { move |_| {
// ... // ...
@ -221,6 +253,9 @@ use_effect_with_deps(
### Example ### Example
```rust ```rust
use yew::{ContextProvider, function_component, html, use_context, use_state};
/// App theme /// App theme
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
struct Theme { struct Theme {
@ -237,7 +272,8 @@ pub fn app() -> Html {
}); });
html! { html! {
// `ctx` is type `Rc<UseStateHandle<Theme>>` while we need `Theme` so we deref it // `ctx` is type `Rc<UseStateHandle<Theme>>` while we need `Theme`
// so we deref it.
// It derefs to `&Theme`, hence the clone // It derefs to `&Theme`, hence the clone
<ContextProvider<Theme> context={(*ctx).clone()}> <ContextProvider<Theme> context={(*ctx).clone()}>
// Every child here and their children will have access to this context. // Every child here and their children will have access to this context.

View File

@ -28,33 +28,41 @@ Tags must either self-close `<... />` or have a corresponding end tag for each s
<!--Open - Close--> <!--Open - Close-->
```rust ```rust
use yew::html;
html! { html! {
<div id="my_div"></div> <div id="my_div"></div>
} };
``` ```
<!--Invalid--> <!--Invalid-->
```rust ```rust ,compile_fail
use yew::html;
html! { html! {
<div id="my_div"> // <- MISSING CLOSE TAG <div id="my_div"> // <- MISSING CLOSE TAG
} };
``` ```
<!--Self-closing--> <!--Self-closing-->
```rust ```rust
use yew::html;
html! { html! {
<input id="my_input" /> <input id="my_input" />
} };
``` ```
<!--Invalid--> <!--Invalid-->
```rust ```rust ,compile_fail
use yew::html;
html! { html! {
<input id="my_input"> // <- MISSING SELF-CLOSE <input id="my_input"> // <- MISSING SELF-CLOSE
} };
``` ```
<!--END_DOCUSAURUS_CODE_TABS--> <!--END_DOCUSAURUS_CODE_TABS-->
@ -71,6 +79,8 @@ Create complex nested HTML and SVG layouts with ease:
<!--HTML--> <!--HTML-->
```rust ```rust
use yew::html;
html! { html! {
<div> <div>
<div data-key="abc"></div> <div data-key="abc"></div>
@ -86,12 +96,14 @@ html! {
</select> </select>
</div> </div>
</div> </div>
} };
``` ```
<!--SVG--> <!--SVG-->
```rust ```rust
use yew::html;
html! { html! {
<svg width="149" height="147" viewBox="0 0 149 147" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="149" height="147" viewBox="0 0 149 147" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M60.5776 13.8268L51.8673 42.6431L77.7475 37.331L60.5776 13.8268Z" fill="#DEB819"/> <path d="M60.5776 13.8268L51.8673 42.6431L77.7475 37.331L60.5776 13.8268Z" fill="#DEB819"/>
@ -108,7 +120,7 @@ html! {
</filter> </filter>
</defs> </defs>
</svg> </svg>
} };
``` ```
<!--END_DOCUSAURUS_CODE_TABS--> <!--END_DOCUSAURUS_CODE_TABS-->

View File

@ -23,53 +23,65 @@ is that every expression implements `Into<Classes>`.
<!--Literal--> <!--Literal-->
```rust ```rust
use yew::{classes, html};
html! { html! {
<div class={classes!("container")}></div> <div class={classes!("container")}></div>
} };
``` ```
<!--Multiple--> <!--Multiple-->
```rust ```rust
use yew::{classes, html};
html! { html! {
<div class={classes!("class-1", "class-2")}></div> <div class={classes!("class-1", "class-2")}></div>
} };
``` ```
<!--String--> <!--String-->
```rust ```rust
use yew::{classes, html};
let my_classes = String::from("class-1 class-2"); let my_classes = String::from("class-1 class-2");
html! { html! {
<div class={classes!(my_classes)}></div> <div class={classes!(my_classes)}></div>
} };
``` ```
<!--Optional--> <!--Optional-->
```rust ```rust
use yew::{classes, html};
html! { html! {
<div class={classes!(Some("class"))} /> <div class={classes!(Some("class"))} />
} };
``` ```
<!--Vector--> <!--Vector-->
```rust ```rust
use yew::{classes, html};
html! { html! {
<div class={classes!(vec!["class-1", "class-2"])}></div> <div class={classes!(vec!["class-1", "class-2"])}></div>
} };
``` ```
<!--Array--> <!--Array-->
```rust ```rust
use yew::{classes, html};
let my_classes = ["class-1", "class-2"]; let my_classes = ["class-1", "class-2"];
html! { html! {
<div class={classes!(&my_classes)}></div> <div class={classes!(my_classes.as_ref())}></div>
} };
``` ```
<!--END_DOCUSAURUS_CODE_TABS--> <!--END_DOCUSAURUS_CODE_TABS-->
@ -77,9 +89,13 @@ html! {
## Components that accept classes ## Components that accept classes
```rust ```rust
use yew::{
classes, html, Children, Classes, Component,
Context, Html, Properties
};
use boolinator::Boolinator; use boolinator::Boolinator;
#[derive(Properties)] #[derive(PartialEq, Properties)]
struct Props { struct Props {
#[prop_or_default] #[prop_or_default]
class: Classes, class: Classes,
@ -90,9 +106,12 @@ struct Props {
struct MyComponent; struct MyComponent;
impl Component for MyComponent { impl Component for MyComponent {
type Message = ();
type Properties = Props; type Properties = Props;
// ... fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
let Props { let Props {

View File

@ -8,18 +8,72 @@ description: "Create complex layouts with component hierarchies"
Any type that implements `Component` can be used in the `html!` macro: Any type that implements `Component` can be used in the `html!` macro:
```rust ```rust
use yew::{Component, Html, html, Context, Properties};
struct MyComponent;
impl Component for MyComponent {
type Message = ();
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, _ctx: &Context<Self>) -> Html {
html! {
{ "This component has no properties!" }
}
}
}
#[derive(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 {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
{
format!(
"prop1: {} and prop2: {}",
ctx.props().prop1,
ctx.props().prop2
)
}
}
}
}
let props = Props {
prop1: "Hello".to_owned(),
prop2: "World".to_owned(),
};
html!{ html!{
<> <>
// No properties // No properties
<MyComponent /> <MyComponent />
// With Properties // With Properties
<MyComponent prop1="lorem" prop2="ipsum" /> <MyComponentWithProps prop1="lorem" prop2="ipsum" />
// With the whole set of props provided at once // With the whole set of props provided at once
<MyComponent with props /> <MyComponentWithProps with props />
</> </>
} };
``` ```
## Nested ## Nested
@ -27,52 +81,82 @@ html!{
Components can be passed children if they have a `children` field in their `Properties`. Components can be passed children if they have a `children` field in their `Properties`.
```rust title="parent.rs" ```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 {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<div id={ctx.props().id.clone()}>
{ ctx.props().children.clone() }
</div>
}
}
}
html! { html! {
<Container id="container"> <Container id="container">
<h4>{ "Hi" }</h4> <h4>{ "Hi" }</h4>
<div>{ "Hello" }</div> <div>{ "Hello" }</div>
</Container> </Container>
} };
``` ```
When using the `with props` syntax, the children passed in the `html!` macro overwrite the ones already present in the props. When using the `with props` syntax, the children passed in the `html!` macro overwrite the ones already present in the props.
```rust ```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 {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<div id={ctx.props().id.clone()}>
{ ctx.props().children.clone() }
</div>
}
}
}
let props = yew::props!(Container::Properties { let props = yew::props!(Container::Properties {
id: "container-2", id: "container-2",
children: Children::default(), children: Children::default(),
}); });
html! { html! {
<Container with props> <Container with props>
// props.children will be overwritten with this // props.children will be overwritten with this
<span>{ "I am a child, as you can see" }</span> <span>{ "I am a child, as you can see" }</span>
</Container> </Container>
} };
```
Here's the implementation of `Container`:
```rust
#[derive(Properties, Clone)]
pub struct Props {
pub id: String,
pub children: Children,
}
pub struct Container;
impl Component for Container {
type Properties = Props;
// ...
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<div id={ctx.props().id.clone()}>
{ ctx.props().children.clone() }
</div>
}
}
}
``` ```
## Nested Children with Props ## Nested Children with Props
@ -80,29 +164,49 @@ impl Component for Container {
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. 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 ```rust
html! { use std::rc::Rc;
<List> use yew::{html, ChildrenWithProps, Component, Context, Html, Properties};
<ListItem value="a" />
<ListItem value="b" />
<ListItem value="c" />
</List>
}
```
```rust #[derive(Clone, PartialEq, Properties)]
#[derive(Properties, Clone)] pub struct ListItemProps {
value: String,
}
pub struct ListItem;
impl Component for ListItem {
type Message = ();
type Properties = ListItemProps;
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<span>
{ ctx.props().value.clone() }
</span>
}
}
}
#[derive(PartialEq, Properties)]
pub struct Props { pub struct Props {
pub children: ChildrenWithProps<ListItem>, pub children: ChildrenWithProps<ListItem>,
} }
pub struct List; pub struct List;
impl Component for List { impl Component for List {
type Message = ();
type Properties = Props; type Properties = Props;
// ... fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
html!{{ html! {{
for ctx.props().children.iter().map(|mut item| { for ctx.props().children.iter().map(|mut item| {
let mut props = Rc::make_mut(&mut item.props); let mut props = Rc::make_mut(&mut item.props);
props.value = format!("item-{}", props.value); props.value = format!("item-{}", props.value);
@ -111,6 +215,13 @@ impl Component for List {
}} }}
} }
} }
html! {
<List>
<ListItem value="a" />
<ListItem value="b" />
<ListItem value="c" />
</List>
};
``` ```
## Relevant examples ## Relevant examples

View File

@ -12,10 +12,22 @@ Using `web-sys`, you can create DOM elements and convert them into a `Node` - wh
used as a `Html` value using `VRef`: used as a `Html` value using `VRef`:
```rust ```rust
// ... use yew::{
fn view(&self, _ctx: &Context<Self>) -> Html { Component, Context, html, Html, utils::document,
use yew::{utils::document, web_sys::{Element, Node}}; web_sys::{Element, Node}
};
struct Comp;
impl Component for Comp {
type Message = ();
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, _ctx: &Context<Self>) -> Html {
// Create a div element from the document // Create a div element from the document
let div: Element = document().create_element("div").unwrap(); let div: Element = document().create_element("div").unwrap();
// Add content, classes etc. // Add content, classes etc.
@ -25,6 +37,7 @@ used as a `Html` value using `VRef`:
// Return that Node as a Html value // Return that Node as a Html value
Html::VRef(node) Html::VRef(node)
} }
}
``` ```
## Dynamic tag names ## Dynamic tag names
@ -35,12 +48,14 @@ Instead of having to use a big match expression, Yew allows you to set the tag n
using `@{name}` where `name` can be any expression that returns a string. using `@{name}` where `name` can be any expression that returns a string.
```rust ```rust
use yew::html;
let level = 5; let level = 5;
let text = "Hello World!".to_owned() let text = "Hello World!".to_owned();
html! { html! {
<@{format!("h{}", level)} class="title">{ content }</@> <@{format!("h{}", level)} class="title">{ text }</@>
} };
``` ```
## Boolean Attributes ## Boolean Attributes
@ -49,35 +64,39 @@ Some content attributes (e.g checked, hidden, required) are called boolean attri
boolean attributes need to be set to a bool value: boolean attributes need to be set to a bool value:
```rust ```rust
html! { use yew::html;
<div hidden=true>
{ "This div is hidden." } html! {
</div> <div hidden=true>
} { "This div is hidden." }
</div>
};
``` ```
This will result in **HTML** that's functionally equivalent to this: This will result in **HTML** that's functionally equivalent to this:
```html ```html
<div hidden>This div is hidden.</div> <div hidden>This div is hidden.</div>
``` ```
Setting a boolean attribute to false is equivalent to not using the attribute at all; values from Setting a boolean attribute to false is equivalent to not using the attribute at all; values from
boolean expressions can be used: boolean expressions can be used:
```rust ```rust
let no = 1 + 1 != 2; use yew::html;
html! { let no = 1 + 1 != 2;
<div hidden={no}>
{ "This div is NOT hidden." } html! {
</div> <div hidden={no}>
} { "This div is NOT hidden." }
</div>
};
``` ```
This will result in the following **HTML**: This will result in the following **HTML**:
```html ```html
<div>This div is NOT hidden.</div> <div>This div is NOT hidden.</div>
``` ```
## Optional attributes for HTML elements ## Optional attributes for HTML elements
@ -85,11 +104,13 @@ This will result in the following **HTML**:
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. 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 ```rust
use yew::html;
let maybe_id = Some("foobar"); let maybe_id = Some("foobar");
html! { html! {
<div id={maybe_id}></div> <div id={maybe_id}></div>
} };
``` ```
If the attribute is set to `None`, the attribute won't be set in the DOM. If the attribute is set to `None`, the attribute won't be set in the DOM.
@ -102,6 +123,8 @@ Listener attributes need to be passed a `Callback` which is a wrapper around a c
<!--Component handler--> <!--Component handler-->
```rust ```rust
use yew::{Component, Context, html, Html};
struct MyComponent; struct MyComponent;
enum Msg { enum Msg {
@ -113,20 +136,21 @@ impl Component for MyComponent {
type Properties = (); type Properties = ();
fn create(_ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
MyComponent; Self
} }
fn update(&mut self, msg: Self::Message) -> bool { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { match msg {
Msg::Click => { Msg::Click => {
// Handle Click // Handle Click
} }
} };
true
} }
fn view(&self, ctx: Context<Self>) -> Html { fn view(&self, ctx: &Context<Self>) -> Html {
// Create a callback from a component link to handle it in a component // Create a callback from a component link to handle it in a component
let click_callback = ctx.link().callback(|_: ClickEvent| Msg::Click); let click_callback = ctx.link().callback(|_| Msg::Click);
html! { html! {
<button onclick={click_callback}> <button onclick={click_callback}>
{ "Click me!" } { "Click me!" }
@ -139,23 +163,32 @@ impl Component for MyComponent {
<!--Agent Handler--> <!--Agent Handler-->
```rust ```rust
use yew::{html, Component, Context, Html};
use yew_agent::{Dispatcher, Dispatched};
use website_test::agents::{MyWorker, WorkerMsg};
struct MyComponent { struct MyComponent {
worker: Dispatcher<MyWorker>, worker: Dispatcher<MyWorker>,
} }
impl Component for MyComponent { impl Component for MyComponent {
type Message = (); type Message = WorkerMsg;
type Properties = (); type Properties = ();
fn create(_ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
MyComponent { MyComponent {
worker: MyWorker::dispatcher() worker: MyWorker::dispatcher(),
} }
} }
fn view(&self, _ctx: &Context<Self>) -> Html { fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
self.worker.send(msg);
false
}
fn view(&self, ctx: &Context<Self>) -> Html {
// Create a callback from a worker to handle it in another context // Create a callback from a worker to handle it in another context
let click_callback = self.worker.callback(|_: ClickEvent| WorkerMsg::Process); let click_callback = ctx.link().callback(|_| WorkerMsg::Process);
html! { html! {
<button onclick={click_callback}> <button onclick={click_callback}>
{ "Click me!" } { "Click me!" }
@ -168,6 +201,9 @@ impl Component for MyComponent {
<!--Other Cases--> <!--Other Cases-->
```rust ```rust
use yew::{Callback, Context, Component, html, Html};
use weblog::console_log;
struct MyComponent; struct MyComponent;
impl Component for MyComponent { impl Component for MyComponent {
@ -180,7 +216,7 @@ impl Component for MyComponent {
fn view(&self, _ctx: &Context<Self>) -> Html { fn view(&self, _ctx: &Context<Self>) -> Html {
// Create an ephemeral callback // Create an ephemeral callback
let click_callback = Callback::from(|| { let click_callback = Callback::from(|_| {
console_log!("clicked!"); console_log!("clicked!");
}); });

View File

@ -9,22 +9,26 @@ The `html!` macro always requires a single root node. In order to get around thi
<!--DOCUSAURUS_CODE_TABS--> <!--DOCUSAURUS_CODE_TABS-->
<!--Valid--> <!--Valid-->
```rust ```rust
use yew::html;
html! { html! {
<> <>
<div></div> <div></div>
<p></p> <p></p>
</> </>
} };
``` ```
<!--Invalid--> <!--Invalid-->
```rust ```rust ,compile_fail
use yew::html;
/* error: only one root html element allowed */ /* error: only one root html element allowed */
html! { html! {
<div></div> <div></div>
<p></p> <p></p>
} };
``` ```
<!--END_DOCUSAURUS_CODE_TABS--> <!--END_DOCUSAURUS_CODE_TABS-->
@ -36,20 +40,28 @@ Yew supports two different syntaxes for building html from an iterator:
<!--DOCUSAURUS_CODE_TABS--> <!--DOCUSAURUS_CODE_TABS-->
<!--Syntax Type 1--> <!--Syntax Type 1-->
```rust ```rust
use yew::{html, Html};
let items = (1..=10).collect::<Vec<_>>();
html! { html! {
<ul class="item-list"> <ul class="item-list">
{ props.items.iter().map(renderItem).collect::<Html>() } { items.iter().collect::<Html>() }
</ul> </ul>
} };
``` ```
<!--Syntax Type 2--> <!--Syntax Type 2-->
```rust ```rust
use yew::{html};
let items = (1..=10).collect::<Vec<_>>();
html! { html! {
<ul class="item-list"> <ul class="item-list">
{ for props.items.iter().map(renderItem) } { for items.iter() }
</ul> </ul>
} };
``` ```
<!--END_DOCUSAURUS_CODE_TABS--> <!--END_DOCUSAURUS_CODE_TABS-->

View File

@ -9,6 +9,8 @@ All display text must be enclosed by `{}` blocks because text is handled as an e
the largest deviation from normal HTML syntax that Yew makes. the largest deviation from normal HTML syntax that Yew makes.
```rust ```rust
use yew::html;
let text = "lorem ipsum"; let text = "lorem ipsum";
html!{ html!{
<> <>
@ -16,7 +18,7 @@ html!{
<div>{"dolor sit"}</div> <div>{"dolor sit"}</div>
<span>{42}</span> <span>{42}</span>
</> </>
} };
``` ```
## Expressions ## Expressions
@ -24,6 +26,10 @@ html!{
You can insert expressions in your HTML using `{}` blocks, as long as they resolve to `Html` You can insert expressions in your HTML using `{}` blocks, as long as they resolve to `Html`
```rust ```rust
use yew::html;
let show_link = true;
html! { html! {
<div> <div>
{ {
@ -36,12 +42,14 @@ html! {
} }
} }
</div> </div>
} };
``` ```
It often makes sense to extract these expressions into functions or closures to optimize for readability: It often makes sense to extract these expressions into functions or closures to optimize for readability:
```rust ```rust
use yew::{html, Html};
let show_link = true; let show_link = true;
let maybe_display_link = move || -> Html { let maybe_display_link = move || -> Html {
if show_link { if show_link {
@ -55,5 +63,5 @@ let maybe_display_link = move || -> Html {
html! { html! {
<div>{maybe_display_link()}</div> <div>{maybe_display_link()}</div>
} };
``` ```

View File

@ -17,7 +17,9 @@ at the top of the application.
Routes are defined by an `enum` which derives `Routable`. This enum must be `Clone + Sized. Routes are defined by an `enum` which derives `Routable`. This enum must be `Clone + Sized.
```rust ```rust
#[derive(Routable)] use yew_router::Routable;
#[derive(Clone, Routable)]
enum Route { enum Route {
#[at("/")] #[at("/")]
Home, Home,
@ -38,6 +40,20 @@ nothing is rendered, and a message is logged to console stating that no route wa
`yew_router::attach_route_listener` is used to attach a listener which is called every time route is changed. `yew_router::attach_route_listener` is used to attach a listener which is called every time route is changed.
```rust ```rust
use yew_router::{Router, Routable};
use yew::{Callback, function_component, html, Html};
#[derive(Clone, Routable)]
enum Route {
#[at("/")]
Home,
#[at("/secure")]
Secure,
#[not_found]
#[at("/404")]
NotFound,
}
#[function_component(Main)] #[function_component(Main)]
fn app() -> Html { fn app() -> Html {
html! { html! {
@ -49,7 +65,7 @@ fn switch(route: &Route) -> Html {
match route { match route {
Route::Home => html! { <h1>{ "Home" }</h1> }, Route::Home => html! { <h1>{ "Home" }</h1> },
Route::Secure => { Route::Secure => {
let callback = Callback::from(|_| yew_router::push_route(Routes::Home)); let callback = Callback::from(|_| yew_router::push_route(Route::Home));
html! { html! {
<div> <div>
<h1>{ "Secure" }</h1> <h1>{ "Secure" }</h1>

View File

@ -55,7 +55,7 @@ If you would like to start your application with any dynamic properties, you can
::: :::
```rust ```rust ,no_run
use yew::prelude::*; use yew::prelude::*;
enum Msg { enum Msg {

View File

@ -17,7 +17,7 @@ exception. There are a few options available:
This crate integrates with the familiar Rust `log` crate: This crate integrates with the familiar Rust `log` crate:
```rust ```rust ,ignore
// setup // setup
fn main() { fn main() {
wasm_logger::init(wasm_logger::Config::default()); wasm_logger::init(wasm_logger::Config::default());

View File

@ -16,7 +16,7 @@ Feel free to contribute to add instructions for your editor of choice.
3. Give it a name and description of your preference. 3. Give it a name and description of your preference.
4. Paste the following snippet in Template Text section: 4. Paste the following snippet in Template Text section:
```rust ```rust ,ignore
use yew::prelude::*; use yew::prelude::*;
struct $NAME$; struct $NAME$;