mc1098 ed2e1ea00e
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
2021-08-28 13:17:28 +02:00

5.1 KiB

title
Children

General usage

Most of the time, when allowing a component to have children, you don't care what type of children the component has. In such cases, the below example will suffice.

use yew::{html, Children, Component, Context, Html, Properties};

#[derive(Properties, PartialEq)]
pub struct ListProps {
    #[prop_or_default]
    pub children: Children,
}

pub struct List;

impl Component for List {
    type Message = ();
    type Properties = ListProps;

    fn create(_ctx: &Context<Self>) -> Self {
        Self
    }

    fn view(&self, ctx: &Context<Self>) -> Html {
        html! {
            <div class="list">
                { for ctx.props().children.iter() }
            </div>
        }
    }
}

Advanced usage

Typed children

In cases where you want one type of component to be passed as children to your component, you can use yew::html::ChildrenWithProps<T>.

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 {
    #[prop_or_default]
    pub children: ChildrenWithProps<Item>,
}

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! {
            <div class="list">
                { for ctx.props().children.iter() }
            </div>
        }
    }
}

Enum typed children

Of course, sometimes you might need to restrict the children to a few different components. In these cases, you have to get a little more hands-on with Yew.

The derive_more crate is used here for better ergonomics. If you don't want to use it, you can manually implement From for each variant.

use yew::{
    html, html::ChildrenRenderer, virtual_dom::VChild, Component, 
    Context, Html, Properties,
};

pub struct Primary;

impl Component for Primary {
    type Message = ();
    type Properties = ();

    fn create(_ctx: &Context<Self>) -> Self {
        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 {
            Self::Primary(child) => child.into(),
            Self::Secondary(child) => child.into(),
        }
    }
}

#[derive(Properties, PartialEq)]
pub struct ListProps {
    #[prop_or_default]
    pub children: ChildrenRenderer<Item>,
}

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! {
            <div class="list">
                { for ctx.props().children.iter() }
            </div>
        }
    }
}

Optional typed child

You can also have a single optional child component of a specific type too:

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 {
    #[prop_or_default]
    pub sidebar: Option<VChild<PageSideBar>>,
}

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! {
            <div class="page">
                { ctx.props().sidebar.clone().map(Html::from).unwrap_or_default() }
                // ... page content
            </div>
        }
    }
}

// The page component can be called either with the sidebar or without: 

pub fn render_page(with_sidebar: bool) -> Html {
    if with_sidebar {
        // Page with sidebar
        html! {
            <Page sidebar={{html_nested! {
                <PageSideBar />
            }}} />
        }
    } else {
        // Page without sidebar
        html! {
            <Page />
        }
    }
}