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: |
cd packages/yew
cargo test --doc --features "doc_test wasm_test"
- name: Run website code snippet tests
run: |
cd packages/website-test
cargo test
integration_tests:
name: Integration Tests on ${{ matrix.toolchain }}

View File

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

View File

@ -37,7 +37,7 @@ category = "Testing"
description = "Run all tests"
dependencies = ["tests-setup"]
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]
category = "Testing"
@ -86,6 +86,14 @@ private = true
command = "cargo"
args = ["test", "--doc"]
[tasks.website-test]
script = [
"""
cd packages/website-test
cargo test
"""
]
[tasks.bench-flow]
private = 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.
```rust
```rust ,ignore
// Use `wee_alloc` as the global allocator.
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;

View File

@ -19,23 +19,33 @@ in the lifecycle of a component.
### 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.
It is common to store the props (data which can be passed from parent to child components) and the
`ComponentLink` in your component struct, like so:
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
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>) -> Self {
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}`.
```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 {
let onclick = ctx.link().callback(|_| Msg::Click);
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.
```rust
use web_sys::HtmlInputElement;
use yew::prelude::*;
use yew::{
Component, Context, html, Html, NodeRef,
web_sys::HtmlInputElement
};
pub struct MyComponent {
node_ref: NodeRef,
}
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 {
html! {
@ -88,6 +127,7 @@ impl Component for MyComponent {
}
}
// highlight-start
fn rendered(&mut self, _ctx: &Context<Self>, first_render: bool) {
if first_render {
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:
```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 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
}
}
}
fn create(_ctx: &Context<Self>) -> Self {
Self {
input_enabled: 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`.
```rust
```rust ,ignore
impl Component for MyComponent {
type Message = Msg;
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.
```rust
// Create a callback that accepts some text and sends it to the component as the `Msg::Text` message variant.
let cb = link.callback(|text: String| Msg::Text(text));
use yew::{html, Component, Context, Html};
// The previous line is needlessly verbose to make it clearer.
// It can be simplified it to this:
let cb = link.callback(Msg::Text);
enum Msg {
Text(String),
}
// Will send `Msg::Text("Hello World!")` to the component.
cb.emit("Hello World!".to_owned());
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 {
// 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`
@ -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:
```rust
let onclick = link.callback(|_| Msg::Clicked);
html! {
<button {onclick}>{ "Click" }</button>
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 {
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`.
```rust
let onkeypress = link.batch_callback(|event| {
if event.key() == "Enter" {
Some(Msg::Submit)
} else {
None
}
});
use yew::{html, Component, Context, Html, KeyboardEvent};
html! {
<input type="text" {onkeypress} />
enum Msg {
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.
```rust
use yew::prelude::*;
use yew::{html, Children, Component, Context, Html, Properties};
#[derive(Properties, PartialEq)]
pub struct ListProps {
@ -20,8 +20,12 @@ pub struct ListProps {
pub struct List;
impl Component for List {
type Message = ();
type Properties = ListProps;
// ...
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> 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>`.
```rust
use yew::html::ChildrenWithProps;
use yew::prelude::*;
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 {
Self
}
fn view(&self, _ctx: &Context<Self>) -> Html {
html! {
{ "item" }
}
}
}
#[derive(Properties, PartialEq)]
pub struct ListProps {
@ -53,9 +71,13 @@ pub struct ListProps {
pub struct List;
impl Component for ListProps {
impl Component for List {
type Message = ();
type Properties = ListProps;
// ...
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> 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.
```rust
use yew::prelude::*;
use yew::html::ChildrenRenderer;
use yew::virtual_dom::{ VChild, VComp };
use yew::{
html, html::ChildrenRenderer, virtual_dom::VChild, Component,
Context, Html, Properties,
};
// `derive_more::From` implements `From<VChild<Primary>>` and
// `From<VChild<Secondary>>` for `Item` automatically!
#[derive(Clone, derive_more::From)]
pub struct Primary;
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 {
Primary(VChild<Primary>),
Secondary(VChild<Secondary>),
}
// Now, we implement `Into<Html>` so that yew knows how to render `Item`.
#[allow(clippy::from_over_into)]
impl Into<Html> for Item {
fn into(self) -> Html {
match self {
@ -107,8 +163,12 @@ pub struct ListProps {
pub struct List;
impl Component for List {
type Message = ();
type Properties = ListProps;
// ...
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
@ -124,9 +184,27 @@ impl Component for List {
You can also have a single optional child component of a specific type too:
```rust
use yew::prelude::*;
use yew::virtual_dom::VChild;
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 {
Self
}
fn view(&self, _ctx: &Context<Self>) -> Html {
html! {
{ "sidebar" }
}
}
}
#[derive(Properties, PartialEq)]
pub struct PageProps {
@ -137,8 +215,12 @@ pub struct PageProps {
struct Page;
impl Component for Page {
type Message = ();
type Properties = PageProps;
// ...
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> 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
// Page without sidebar
html! {
<Page />
}
// Page with sidebar
html! {
<Page sidebar={html_nested! {
<PageSideBar />
}} />
pub fn render_page(with_sidebar: bool) -> Html {
if with_sidebar {
// Page with sidebar
html! {
<Page sidebar={{html_nested! {
<PageSideBar />
}}} />
}
} else {
// 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`).
```rust
let props = yew::props!(LinkProps {
href: "/",
text: Rc::from("imagine this text being really long"),
size: 64,
});
use std::rc::Rc;
use yew::{props, Properties};
// build the associated properties of a component
let props = yew::props!(Model::Properties {
href: "/book",
text: Rc::from("my bestselling novel"),
});
#[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<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:
```rust
// In create
self.node_ref = NodeRef::default();
use yew::{html, web_sys::Element, Component, Context, Html, NodeRef};
// In view
html! {
<div ref={self.node_ref.clone()}></div>
struct Comp {
node_ref: NodeRef,
}
// In rendered
let has_attributes = self.node_ref.cast::<Element>().unwrap().has_attributes();
impl Component for Comp {
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

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
theme using props:
```rust
// root
let theme = // ...
html! {
<Navbar {theme} />
use yew::{html, Children, Component, Context, Html, Properties};
#[derive(Clone, PartialEq)]
pub struct Theme {
foreground: String,
background: String,
}
// Navbar component
html! {
<div>
<Title {theme}>{ "App title" }<Title>
<NavButton {theme}>{ "Somewhere" }</NavButton>
</div>
#[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 {
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.
@ -54,14 +106,36 @@ 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 {
Self
}
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! {
<button style={format!("background: {}; color: {};", theme.background, theme.foreground)}>
<button style={format!(
"background: {}; color: {};",
theme.background,
theme.foreground
)}
>
{ "Click me!" }
</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.
```rust
use yew::{function_component, html};
#[function_component(HelloWorld)]
fn hello_world() -> Html {
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:
```rust
html! { <ChatContainer /> }
use yew::{function_component, html, Html};
#[function_component(ChatContainer)]
pub fn chat_container() -> Html {
html! {
// chat container impl
}
}
html! {
<ChatContainer />
};
```
## Example
@ -21,6 +32,8 @@ html! { <ChatContainer /> }
<!--With props-->
```rust
use yew::{function_component, html, Properties};
#[derive(Properties, PartialEq)]
pub struct RenderedAtProps {
pub time: String,
@ -40,13 +53,15 @@ pub fn rendered_at(props: &RenderedAtProps) -> Html {
<!--Without props-->
```rust
use yew::{function_component, html, use_state, Callback};
#[function_component(App)]
fn app() -> Html {
let (counter, set_counter) = use_state(|| 0);
let counter = use_state(|| 0);
let onclick = {
let counter = Rc::clone(&counter);
Callback::from(move |_| set_counter(*counter + 1))
let counter = counter.clone();
Callback::from(move |_| counter.set(*counter + 1))
};
html! {
@ -54,7 +69,7 @@ fn app() -> Html {
<button {onclick}>{ "Increment value" }</button>
<p>
<b>{ "Current value: " }</b>
{ counter }
{ *counter }
</p>
</div>
}
@ -68,30 +83,47 @@ fn app() -> Html {
The `#[function_component(_)]` attribute also works with generic functions for creating generic components.
```rust
use std::fmt::Display;
use yew::{function_component, html, Properties};
#[derive(Properties, PartialEq)]
pub struct Props<T>
where T: PartialEq
where
T: PartialEq,
{
data: T,
}
#[function_component(MyGenericComponent)]
pub fn my_generic_component<T>(props: &Props<T>) -> Html
where T: PartialEq + Display
where
T: PartialEq + Display,
{
html! {
<p>
{ props.data }
{ &props.data }
</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
html! {
<MyGenericComponent<i32> data=123 />
}
};
// or
let foo = Foo;
html! {
<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.
```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, set_state) = use_state(|| vec![]);
let state = use_state(Vec::new);
{
let mut state = Rc::clone(&state);
let state = state.clone();
use_effect(move || {
let producer = EventBus::bridge(Callback::from(move |msg| {
let mut messages = (*state).clone();
messages.push(msg);
set_state(messages)
state.set(messages)
}));
|| 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.
This function will take no arguments and return `Rc<RefCell<Vec<String>>>`.
```rust
use std::{cell::RefCell, rc::Rc};
fn use_subscribe() -> Rc<RefCell<Vec<String>>> {
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.
```rust
fn use_subscribe() -> Rc<Vec<String>> {
let (state, set_state) = use_state(Vec::new);
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<String> {
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 = (*state).clone();
let mut messages = (*effect_state).clone();
messages.push(msg);
set_state(messages)
effect_state.set(messages)
}));
|| drop(producer)
});
state
(*state).clone()
}
```

View File

@ -15,6 +15,8 @@ This value remains up-to-date on subsequent renders.
### Example
```rust
use yew::{Callback, function_component, html, use_state};
#[function_component(UseState)]
fn state() -> Html {
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
```rust
use yew::{
function_component, html, use_ref, use_state,
web_sys::{Event, HtmlInputElement},
Callback, TargetCast,
};
#[function_component(UseRef)]
fn ref_hook() -> Html {
let message = use_state(|| "".to_string());
let message_count = use_ref(|| 0);
let onclick = Callback::from(move |e| {
let onclick = Callback::from(move |_| {
let window = yew::utils::window();
if *message_count.borrow_mut() > 3 {
window.alert_with_message("Message limit reached");
window.alert_with_message("Message limit reached").unwrap();
} else {
*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! {
<div>
<input {onchange} value={message} />
<input {onchange} value={(*message).clone()} />
<button {onclick}>{ "Send" }</button>
</div>
}
}
```
## `use_reducer`
`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
```rust
use std::rc::Rc;
use yew::{function_component, html, use_reducer, Callback};
#[function_component(UseReducer)]
fn reducer() -> Html {
/// reducer's Action
@ -114,13 +124,13 @@ fn reducer() -> Html {
counter: match action {
Action::Double => prev.counter * 2,
Action::Square => prev.counter * prev.counter,
}
},
},
// initial state
CounterState { counter: 1 },
);
let double_onclick = {
let double_onclick = {
let counter = counter.clone();
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.
```rust
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,
},
);
use std::rc::Rc;
use yew::{function_component, use_reducer_with_init, html};
#[function_component(ReducerWithInit)]
fn reducer_with_init() -> Html {
/// reducer's State
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`
@ -173,27 +201,29 @@ The destructor can be used to clean up the effects introduced and it can take ow
### Example
```rust
use yew::{Callback, function_component, html, use_effect, use_state};
#[function_component(UseEffect)]
fn effect() -> Html {
let (counter, set_counter) = use_state(|| 0);
let counter = use_state(|| 0);
{
let counter = counter.clone();
use_effect(move || {
// 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
|| yew::utils::document().set_title("You clicked 0 times")
});
}
let onclick = {
let counter = Rc::clone(&counter);
Callback::from(move |_| set_counter(*counter + 1))
let counter = counter.clone();
Callback::from(move |_| counter.set(*counter + 1))
};
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`
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(
move |_| {
// ...
@ -221,6 +253,9 @@ use_effect_with_deps(
### Example
```rust
use yew::{ContextProvider, function_component, html, use_context, use_state};
/// App theme
#[derive(Clone, Debug, PartialEq)]
struct Theme {
@ -237,7 +272,8 @@ pub fn app() -> 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
<ContextProvider<Theme> context={(*ctx).clone()}>
// 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-->
```rust
use yew::html;
html! {
<div id="my_div"></div>
}
};
```
<!--Invalid-->
```rust
```rust ,compile_fail
use yew::html;
html! {
<div id="my_div"> // <- MISSING CLOSE TAG
}
};
```
<!--Self-closing-->
```rust
use yew::html;
html! {
<input id="my_input" />
}
};
```
<!--Invalid-->
```rust
```rust ,compile_fail
use yew::html;
html! {
<input id="my_input"> // <- MISSING SELF-CLOSE
}
};
```
<!--END_DOCUSAURUS_CODE_TABS-->
@ -71,6 +79,8 @@ Create complex nested HTML and SVG layouts with ease:
<!--HTML-->
```rust
use yew::html;
html! {
<div>
<div data-key="abc"></div>
@ -86,12 +96,14 @@ html! {
</select>
</div>
</div>
}
};
```
<!--SVG-->
```rust
use yew::html;
html! {
<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"/>
@ -108,7 +120,7 @@ html! {
</filter>
</defs>
</svg>
}
};
```
<!--END_DOCUSAURUS_CODE_TABS-->

View File

@ -23,53 +23,65 @@ is that every expression implements `Into<Classes>`.
<!--Literal-->
```rust
use yew::{classes, html};
html! {
<div class={classes!("container")}></div>
}
<div class={classes!("container")}></div>
};
```
<!--Multiple-->
```rust
use yew::{classes, html};
html! {
<div class={classes!("class-1", "class-2")}></div>
}
};
```
<!--String-->
```rust
use yew::{classes, html};
let my_classes = String::from("class-1 class-2");
html! {
<div class={classes!(my_classes)}></div>
}
};
```
<!--Optional-->
```rust
use yew::{classes, html};
html! {
<div class={classes!(Some("class"))} />
}
};
```
<!--Vector-->
```rust
use yew::{classes, html};
html! {
<div class={classes!(vec!["class-1", "class-2"])}></div>
}
};
```
<!--Array-->
```rust
use yew::{classes, html};
let my_classes = ["class-1", "class-2"];
html! {
<div class={classes!(&my_classes)}></div>
}
<div class={classes!(my_classes.as_ref())}></div>
};
```
<!--END_DOCUSAURUS_CODE_TABS-->
@ -77,9 +89,13 @@ html! {
## Components that accept classes
```rust
use yew::{
classes, html, Children, Classes, Component,
Context, Html, Properties
};
use boolinator::Boolinator;
#[derive(Properties)]
#[derive(PartialEq, Properties)]
struct Props {
#[prop_or_default]
class: Classes,
@ -90,9 +106,12 @@ struct Props {
struct MyComponent;
impl Component for MyComponent {
type Message = ();
type Properties = Props;
// ...
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html {
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:
```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!{
<>
// No properties
<MyComponent />
// With Properties
<MyComponent prop1="lorem" prop2="ipsum" />
<MyComponentWithProps prop1="lorem" prop2="ipsum" />
// With the whole set of props provided at once
<MyComponent with props />
<MyComponentWithProps with props />
</>
}
};
```
## Nested
@ -27,52 +81,82 @@ html!{
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 {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<div id={ctx.props().id.clone()}>
{ ctx.props().children.clone() }
</div>
}
}
}
html! {
<Container id="container">
<h4>{ "Hi" }</h4>
<div>{ "Hello" }</div>
</Container>
}
};
```
When using the `with props` syntax, 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 {
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 {
id: "container-2",
children: Children::default(),
});
html! {
<Container with props>
// props.children will be overwritten with this
<span>{ "I am a child, as you can see" }</span>
</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
@ -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.
```rust
html! {
<List>
<ListItem value="a" />
<ListItem value="b" />
<ListItem value="c" />
</List>
}
```
use std::rc::Rc;
use yew::{html, ChildrenWithProps, Component, Context, Html, Properties};
```rust
#[derive(Properties, Clone)]
#[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 {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<span>
{ ctx.props().value.clone() }
</span>
}
}
}
#[derive(PartialEq, Properties)]
pub struct Props {
pub children: ChildrenWithProps<ListItem>,
}
pub struct List;
impl Component for List {
type Message = ();
type Properties = Props;
// ...
fn create(_ctx: &Context<Self>) -> Self {
Self
}
fn view(&self, ctx: &Context<Self>) -> Html {
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);
@ -111,6 +215,13 @@ impl Component for List {
}}
}
}
html! {
<List>
<ListItem value="a" />
<ListItem value="b" />
<ListItem value="c" />
</List>
};
```
## 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`:
```rust
// ...
fn view(&self, _ctx: &Context<Self>) -> Html {
use yew::{utils::document, web_sys::{Element, Node}};
use yew::{
Component, Context, html, Html, utils::document,
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
let div: Element = document().create_element("div").unwrap();
// Add content, classes etc.
@ -25,6 +37,7 @@ used as a `Html` value using `VRef`:
// Return that Node as a Html value
Html::VRef(node)
}
}
```
## 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.
```rust
use yew::html;
let level = 5;
let text = "Hello World!".to_owned()
let text = "Hello World!".to_owned();
html! {
<@{format!("h{}", level)} class="title">{ content }</@>
}
<@{format!("h{}", level)} class="title">{ text }</@>
};
```
## 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:
```rust
html! {
<div hidden=true>
{ "This div is hidden." }
</div>
}
use yew::html;
html! {
<div hidden=true>
{ "This div is hidden." }
</div>
};
```
This will result in **HTML** that's functionally equivalent to this:
```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
boolean expressions can be used:
```rust
let no = 1 + 1 != 2;
use yew::html;
html! {
<div hidden={no}>
{ "This div is NOT hidden." }
</div>
}
let no = 1 + 1 != 2;
html! {
<div hidden={no}>
{ "This div is NOT hidden." }
</div>
};
```
This will result in the following **HTML**:
```html
<div>This div is NOT hidden.</div>
<div>This div is NOT hidden.</div>
```
## 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.
```rust
use yew::html;
let maybe_id = Some("foobar");
html! {
<div id={maybe_id}></div>
}
};
```
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-->
```rust
use yew::{Component, Context, html, Html};
struct MyComponent;
enum Msg {
@ -113,20 +136,21 @@ impl Component for MyComponent {
type Properties = ();
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 {
Msg::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
let click_callback = ctx.link().callback(|_: ClickEvent| Msg::Click);
let click_callback = ctx.link().callback(|_| Msg::Click);
html! {
<button onclick={click_callback}>
{ "Click me!" }
@ -139,23 +163,32 @@ impl Component for MyComponent {
<!--Agent Handler-->
```rust
use yew::{html, Component, Context, Html};
use yew_agent::{Dispatcher, Dispatched};
use website_test::agents::{MyWorker, WorkerMsg};
struct MyComponent {
worker: Dispatcher<MyWorker>,
}
impl Component for MyComponent {
type Message = ();
type Message = WorkerMsg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
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
let click_callback = self.worker.callback(|_: ClickEvent| WorkerMsg::Process);
let click_callback = ctx.link().callback(|_| WorkerMsg::Process);
html! {
<button onclick={click_callback}>
{ "Click me!" }
@ -168,6 +201,9 @@ impl Component for MyComponent {
<!--Other Cases-->
```rust
use yew::{Callback, Context, Component, html, Html};
use weblog::console_log;
struct MyComponent;
impl Component for MyComponent {
@ -180,7 +216,7 @@ impl Component for MyComponent {
fn view(&self, _ctx: &Context<Self>) -> Html {
// Create an ephemeral callback
let click_callback = Callback::from(|| {
let click_callback = Callback::from(|_| {
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-->
<!--Valid-->
```rust
use yew::html;
html! {
<>
<div></div>
<p></p>
</>
}
};
```
<!--Invalid-->
```rust
```rust ,compile_fail
use yew::html;
/* error: only one root html element allowed */
html! {
<div></div>
<p></p>
}
};
```
<!--END_DOCUSAURUS_CODE_TABS-->
@ -36,20 +40,28 @@ Yew supports two different syntaxes for building html from an iterator:
<!--DOCUSAURUS_CODE_TABS-->
<!--Syntax Type 1-->
```rust
use yew::{html, Html};
let items = (1..=10).collect::<Vec<_>>();
html! {
<ul class="item-list">
{ props.items.iter().map(renderItem).collect::<Html>() }
{ items.iter().collect::<Html>() }
</ul>
}
};
```
<!--Syntax Type 2-->
```rust
use yew::{html};
let items = (1..=10).collect::<Vec<_>>();
html! {
<ul class="item-list">
{ for props.items.iter().map(renderItem) }
{ for items.iter() }
</ul>
}
};
```
<!--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.
```rust
use yew::html;
let text = "lorem ipsum";
html!{
<>
@ -16,7 +18,7 @@ html!{
<div>{"dolor sit"}</div>
<span>{42}</span>
</>
}
};
```
## Expressions
@ -24,6 +26,10 @@ html!{
You can insert expressions in your HTML using `{}` blocks, as long as they resolve to `Html`
```rust
use yew::html;
let show_link = true;
html! {
<div>
{
@ -36,12 +42,14 @@ html! {
}
}
</div>
}
};
```
It often makes sense to extract these expressions into functions or closures to optimize for readability:
```rust
use yew::{html, Html};
let show_link = true;
let maybe_display_link = move || -> Html {
if show_link {
@ -55,5 +63,5 @@ let maybe_display_link = move || -> Html {
html! {
<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.
```rust
#[derive(Routable)]
use yew_router::Routable;
#[derive(Clone, Routable)]
enum Route {
#[at("/")]
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.
```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)]
fn app() -> Html {
html! {
@ -49,7 +65,7 @@ fn switch(route: &Route) -> Html {
match route {
Route::Home => html! { <h1>{ "Home" }</h1> },
Route::Secure => {
let callback = Callback::from(|_| yew_router::push_route(Routes::Home));
let callback = Callback::from(|_| yew_router::push_route(Route::Home));
html! {
<div>
<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::*;
enum Msg {

View File

@ -17,7 +17,7 @@ exception. There are a few options available:
This crate integrates with the familiar Rust `log` crate:
```rust
```rust ,ignore
// setup
fn main() {
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.
4. Paste the following snippet in Template Text section:
```rust
```rust ,ignore
use yew::prelude::*;
struct $NAME$;