mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
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:
parent
bd8ecf5d5e
commit
ed2e1ea00e
5
.github/workflows/pull-request.yml
vendored
5
.github/workflows/pull-request.yml
vendored
@ -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 }}
|
||||
|
||||
@ -35,3 +35,6 @@ members = [
|
||||
# Release tools
|
||||
"packages/changelog",
|
||||
]
|
||||
exclude = [
|
||||
"packages/website-test",
|
||||
]
|
||||
|
||||
@ -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
|
||||
|
||||
20
packages/website-test/Cargo.toml
Normal file
20
packages/website-test/Cargo.toml
Normal 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"
|
||||
5
packages/website-test/Makefile.toml
Normal file
5
packages/website-test/Makefile.toml
Normal file
@ -0,0 +1,5 @@
|
||||
[tasks.doc-test]
|
||||
command = "cargo"
|
||||
args = [
|
||||
"test"
|
||||
]
|
||||
100
packages/website-test/build.rs
Normal file
100
packages/website-test/build.rs
Normal 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(" ");
|
||||
}
|
||||
}
|
||||
}
|
||||
50
packages/website-test/src/agents.rs
Normal file
50
packages/website-test/src/agents.rs
Normal 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
|
||||
}
|
||||
}
|
||||
3
packages/website-test/src/lib.rs
Normal file
3
packages/website-test/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod agents;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/website_tests.rs"));
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -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 />
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
}
|
||||
|
||||
@ -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" }
|
||||
|
||||
@ -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} />
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
@ -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()
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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-->
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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!");
|
||||
});
|
||||
|
||||
|
||||
@ -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-->
|
||||
|
||||
|
||||
@ -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>
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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$;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user