diff --git a/packages/yew/src/functional/mod.rs b/packages/yew/src/functional/mod.rs index 4d3f587ec36..447e74e32ff 100644 --- a/packages/yew/src/functional/mod.rs +++ b/packages/yew/src/functional/mod.rs @@ -1,12 +1,18 @@ //! Function components are a simplified version of normal components. -//! They consist of a single function annotated with the attribute `#[function_component(_)]` +//! They consist of a single function annotated with the attribute `#[function_component]` //! that receives props and determines what should be rendered by returning [`Html`](crate::Html). //! +//! Functions with the attribute have to return `Html` and may take a single parameter for the type of props the component should accept. +//! The parameter type needs to be a reference to a `Properties` type (ex. `props: &MyProps`). +//! If the function doesn't have any parameters the resulting component doesn't accept any props. +//! +//! Just mark the component with the attribute. The component will be named after the function. +//! //! ```rust //! # use yew::prelude::*; //! # -//! #[function_component(HelloWorld)] -//! fn hello_world() -> Html { +//! #[function_component] +//! fn HelloWorld() -> Html { //! html! { "Hello world" } //! } //! ``` diff --git a/website/docs/concepts/components/children.mdx b/website/docs/advanced-topics/children.mdx similarity index 100% rename from website/docs/concepts/components/children.mdx rename to website/docs/advanced-topics/children.mdx diff --git a/website/docs/concepts/components/callbacks.mdx b/website/docs/advanced-topics/struct-components/callbacks.mdx similarity index 100% rename from website/docs/concepts/components/callbacks.mdx rename to website/docs/advanced-topics/struct-components/callbacks.mdx diff --git a/website/docs/advanced-topics/struct-components/hoc.mdx b/website/docs/advanced-topics/struct-components/hoc.mdx new file mode 100644 index 00000000000..76cf5a9fdea --- /dev/null +++ b/website/docs/advanced-topics/struct-components/hoc.mdx @@ -0,0 +1,82 @@ +--- +title: "Higher Order Components" +--- + +There are several cases where Struct components dont directly support a feature (ex. Suspense) or require a lot of boiler plate to use the features (ex. Context). + +In those cases it is recommended to create function components that are higher order components. + +## Higher Order Components Definition + +Higher Order Components are components that dont add any new Html and only wrap some other component to provide extra functionality. + +### Example + +Hook into Context and pass it down to a struct component + +```rust +use yew::prelude::*; + +#[derive(Clone, Debug, PartialEq)] +struct Theme { + foreground: String, + background: String, +} + +#[function_component] +pub fn App() -> Html { + let ctx = use_state(|| Theme { + foreground: "#000000".to_owned(), + background: "#eeeeee".to_owned(), + }); + + html! { + context={(*ctx).clone()}> + + > + } +} + +// highlight-start +#[function_component] +pub fn ThemedButtonHOC() -> Html { + let theme = use_context::().expect("no ctx found"); + + html! {} +} +// highlight-end + +#[derive(Properties, PartialEq)] +pub struct Props { + pub theme: Theme, +} + +struct ThemedButtonStructComponent; + +impl Component for ThemedButtonStructComponent { + type Message = (); + type Properties = Props; + + fn create(_ctx: &Context) -> Self { + Self + } + + fn view(&self, ctx: &Context) -> Html { + let theme = &ctx.props().theme; + html! { + + } + } +} + + + + +``` diff --git a/website/docs/concepts/components/introduction.mdx b/website/docs/advanced-topics/struct-components/introduction.mdx similarity index 91% rename from website/docs/concepts/components/introduction.mdx rename to website/docs/advanced-topics/struct-components/introduction.mdx index b1c0d20dca3..31983628777 100644 --- a/website/docs/concepts/components/introduction.mdx +++ b/website/docs/advanced-topics/struct-components/introduction.mdx @@ -1,7 +1,6 @@ --- title: "Introduction" description: "Components in Yew" -slug: /concepts/components --- ## What are Components? @@ -21,7 +20,7 @@ much stricter. It also provides super-powers like conditional rendering and rend ## Passing data to a component -Yew components use *props* to communicate between parent and children. A parent component may pass any data as props to +Yew components use _props_ to communicate between parent and children. A parent component may pass any data as props to its children. Props are similar to HTML attributes but any Rust type can be passed as props. :::info diff --git a/website/docs/concepts/components/lifecycle.mdx b/website/docs/advanced-topics/struct-components/lifecycle.mdx similarity index 100% rename from website/docs/concepts/components/lifecycle.mdx rename to website/docs/advanced-topics/struct-components/lifecycle.mdx diff --git a/website/docs/concepts/components/properties.mdx b/website/docs/advanced-topics/struct-components/properties.mdx similarity index 97% rename from website/docs/concepts/components/properties.mdx rename to website/docs/advanced-topics/struct-components/properties.mdx index 693e86321da..cdda1392e60 100644 --- a/website/docs/concepts/components/properties.mdx +++ b/website/docs/advanced-topics/struct-components/properties.mdx @@ -21,7 +21,7 @@ The following attributes allow you to give your props initial values which will :::tip Attributes aren't visible in Rustdoc generated documentation. -The docstrings of your properties should mention whether a prop is optional and if it has a special default value. +The doc strings of your properties should mention whether a prop is optional and if it has a special default value. ::: #### `#[prop_or_default]` diff --git a/website/docs/concepts/components/refs.mdx b/website/docs/advanced-topics/struct-components/refs.mdx similarity index 100% rename from website/docs/concepts/components/refs.mdx rename to website/docs/advanced-topics/struct-components/refs.mdx diff --git a/website/docs/concepts/components/scope.mdx b/website/docs/advanced-topics/struct-components/scope.mdx similarity index 100% rename from website/docs/concepts/components/scope.mdx rename to website/docs/advanced-topics/struct-components/scope.mdx diff --git a/website/docs/concepts/basic-web-technologies/css.mdx b/website/docs/concepts/basic-web-technologies/css.mdx new file mode 100644 index 00000000000..07a77948fbc --- /dev/null +++ b/website/docs/concepts/basic-web-technologies/css.mdx @@ -0,0 +1,104 @@ +--- +title: "CSS with classes!" +description: "A handy macro to handle classes" +comment: "Keep this file as short and simple as possible. Its purpose is to ease in the reader into components in Yew instead of providing proper API docs" +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +> Yew mostly operates on the idea of keeping everything that a reusable piece of +> UI may need, in one place - rust files. But also seeks to stay close to the +> original look of the technology. + +Yew does not provide css in rs solutions natively, but helps with css by providing +programmatic ways to interact with css classes. + +## Classes + +The struct `Classes` simplifies use of HTML classes: + + + + +```rust +use yew::{classes, html}; + +html! { +
+}; +``` + +
+ + +```rust +use yew::{classes, html}; + +html! { +
+}; +``` + +
+ + +```rust +use yew::{classes, html}; + +html! { +
+}; +``` + +
+ + +```rust +use yew::{classes, html}; + +html! { +
+}; +``` + + + + +```rust +use yew::{classes, html}; + +html! { +
+}; +``` + +
+ + +```rust +use yew::{classes, html}; + +html! { +
+}; +``` + +
+ + +We will expand upon this concept in [more CSS](../../more/css). + +## Inline Styles + +Currently Yew does not provide any help with inline styles natively: + +```rust +use yew::{classes, html}; + +html! { +
+}; +``` + +We will expand upon this concept in [more CSS](../../more/css). diff --git a/website/docs/concepts/basic-web-technologies/html.mdx b/website/docs/concepts/basic-web-technologies/html.mdx new file mode 100644 index 00000000000..f7b5c5e4b5c --- /dev/null +++ b/website/docs/concepts/basic-web-technologies/html.mdx @@ -0,0 +1,88 @@ +--- +title: "HTML with html!" +description: "Its HTML but not quite!" +comment: "Keep this file as short and simple as possible. Its purpose is to ease in the reader into components in Yew instead of providing proper API docs" +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +> Yew mostly operates on the idea of keeping everything that a reusable piece of +> UI may need, in one place - rust files. But also seeks to stay close to the +> original look of the technology. + +For HTML we achieve this by providing the `html!` macro: + +```rust +use yew::prelude::*; + +let my_header: Html = html!{Girl in a jacket}; +``` + +`html!` macro allows you to write html generation as if it was in a `.html` file and then behind the scenes by yew gets turned into internal builder patterns that generate all DOM nodes. + +As it is part of rust code it provides a easy way to switch from html to rust by applying curly brackets: + +```rust +use yew::prelude::*; + +let header_text = "Hello world".to_string(); +let header_html: Html = html!{

{header_text}

}; + +let count: usize = 5; +let counter_html: Html = html!{

{"My age is: "}{count}

}; + +let combined_html: Html = html!{
{header_html}{counter_html}
}; +``` + +One rule major rule comes with use of `html!` - you can only return 1 wrapping node: + + + + +```rust +use yew::html; + +html! { +
+
+

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

+}; + +``` + +
+
+ +To help with this rule and avoid `div` abuse `html!` allows fragments. Fragments are empty tags that do not provide any html result: + +```rust +use yew::html; + +html! { + <> +
+

+ +}; + +``` + +We will introduce Yew and HTML further in depth in [more HTML](../html). diff --git a/website/docs/concepts/basic-web-technologies/js.mdx b/website/docs/concepts/basic-web-technologies/js.mdx new file mode 100644 index 00000000000..d0d5a3ee0fc --- /dev/null +++ b/website/docs/concepts/basic-web-technologies/js.mdx @@ -0,0 +1,56 @@ +--- +title: "JS with RS" +description: "Javascript with Rust" +comment: "Keep this file as short and simple as possible. Its purpose is to ease in the reader into components in Yew instead of providing proper API docs" +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +> Yew mostly operates on the idea of keeping everything that a reusable piece of +> UI may need, in one place - rust files. But also seeks to stay close to the +> original look of the technology. + +Sadly as of today WebAssembly is not feature-complete for DOM interactions. This means even in Yew we sometimes rely on calling Javascript. + +## wasm-bindgen + +[`wasm-bindgen`](https://github.com/rustwasm/wasm-bindgen) is a library and tool that allows to call javascript from rust and rust from javascript. + +We highly recommend you give a look to their [documentation](https://rustwasm.github.io/docs/wasm-bindgen/) + +We will also expand upon this concept in the [more wasm-bindgen](../wasm-bindgen). + +## web-sys + +The [`web-sys` crate](https://crates.io/crates/web-sys) provides bindings for Web APIs and allows us to write Javascript code in a rustyfied and safe way. + +Example: + + + + +```js +let document = window.document; +``` + + + + + +```rust ,no_run +use wasm_bindgen::UnwrapThrowExt; +use web_sys::window; + +let document = window() + .expect_throw("window is undefined") + .document() + .expect_throw("document is undefined"); +``` + + + + +Once again we highly recommend you give a look to their [documentation](https://rustwasm.github.io/docs/wasm-bindgen/) + +We will also expand upon this concept in the [more wasm-bindgen](../wasm-bindgen). diff --git a/website/docs/concepts/contexts.mdx b/website/docs/concepts/contexts.mdx index bf2234cf7b6..615431526ab 100644 --- a/website/docs/concepts/contexts.mdx +++ b/website/docs/concepts/contexts.mdx @@ -4,11 +4,12 @@ sidebar_label: Contexts description: "Using contexts to pass data within application" --- -Generally data is passed down the component tree using props but that becomes tedious for values such as -user preferences, authentication information etc. Consider the following example which passes down the +Generally data is passed down the component tree using props but that becomes tedious for values such as +user preferences, authentication information etc. Consider the following example which passes down the theme using props: + ```rust -use yew::{html, Children, Component, Context, Html, Properties}; +use yew::{html, Children, Component, Context, Html, Properties, function_component}; #[derive(Clone, PartialEq)] pub struct Theme { @@ -21,27 +22,17 @@ pub struct NavbarProps { theme: Theme, } -pub struct Navbar; - -impl Component for Navbar { - type Message = (); - type Properties = NavbarProps; - - fn create(_ctx: &Context) -> Self { - Self - } - - fn view(&self, ctx: &Context) -> Html { - html! { -
- - { "App title" } - - - { "Somewhere" } - -
- } +#[function_component] +fn Navbar(props: &NavbarProps) -> Html { + html! { +
+ + { "App title" } + + + { "Somewhere" } + +
} } @@ -51,14 +42,14 @@ pub struct ThemeProps { children: Children, } -#[yew::function_component(Title)] -fn title(_props: &ThemeProps) -> Html { +#[function_component] +fn Title(_props: &ThemeProps) -> Html { html! { // impl } } -#[yew::function_component(NavButton)] -fn nav_button(_props: &ThemeProps) -> Html { +#[function_component] +fn NavButton(_props: &ThemeProps) -> Html { html! { // impl } @@ -75,16 +66,17 @@ html! { }; ``` -Passing down data like this isn't ideal for something like a theme which needs to be available everywhere. +Passing down data like this isn't ideal for something like a theme which needs to be available everywhere. This is where contexts come in. -Contexts provide a way to share data between components without passing them down explicitly as props. -They make data available to all components in the tree. +Contexts can be understood as a global-ish opt in props. +You need to provide the value somewhere in the component tree and all sub-components will be able to listen into its value and changes. ## Using Contexts In order to use contexts, we need a struct which defines what data is to be passed. For the above use-case, consider the following struct: + ```rust #[derive(Clone, Debug, PartialEq)] struct Theme { @@ -101,49 +93,9 @@ The children are re-rendered when the context changes. #### Struct components -The `Scope::context` method is used to consume contexts in struct components. - -##### Example - -```rust -use yew::{Callback, html, Component, Context, Html}; - -#[derive(Clone, Debug, PartialEq)] -struct Theme { - foreground: String, - background: String, -} - -struct ContextDemo; - -impl Component for ContextDemo { - type Message = (); - type Properties = (); - - fn create(_ctx: &Context) -> Self { - Self - } - - fn view(&self, ctx: &Context) -> Html { - let (theme, _) = ctx - .link() - .context::(Callback::noop()) - .expect("context to be set"); - html! { - - } - } -} -``` +Use [HOC](../advanced-topics/struct-components/hoc) #### Function components -`use_context` hook is used to consume contexts in function components. -See [docs for use_context](function-components/pre-defined-hooks.mdx#use_context) to learn more. +`use_context` hook is used to consume contexts in function components. +See [docs for use_context](function-components/hooks/use-context) to learn more. diff --git a/website/docs/concepts/function-components/attribute.mdx b/website/docs/concepts/function-components/attribute.mdx deleted file mode 100644 index 02edccd0d13..00000000000 --- a/website/docs/concepts/function-components/attribute.mdx +++ /dev/null @@ -1,142 +0,0 @@ ---- -title: "#[function_component]" -description: "The #[function_component] attribute" ---- - -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; - -`#[function_component(_)]` turns a normal Rust function into a function component. -Functions with the attribute have to return `Html` and may take a single parameter for the type of props the component should accept. -The parameter type needs to be a reference to a `Properties` type (ex. `props: &MyProps`). -If the function doesn't have any parameters the resulting component doesn't accept any props. - -Just mark the component with the attribute. The component will be named after the function. - -```rust -use yew::{function_component, html, Html}; - -#[function_component] -pub fn ChatContainer() -> Html { - html! { - // chat container impl - } -} - -html! { - -}; -``` - -## Specifying a custom component name - -You need to provide a name as an input to the attribute which will be the identifier of the component. -Assuming you have a function called `chat_container` and you add the attribute `#[function_component(ChatContainer)]` you can use the component like this: - -```rust -use yew::{function_component, html, Html}; - -#[function_component(ChatContainer)] -pub fn chat_container() -> Html { - html! { - // chat container impl - } -} - -html! { - -}; -``` - -## Example - - - - -```rust -use yew::{function_component, html, Properties, Html}; - -#[derive(Properties, PartialEq)] -pub struct RenderedAtProps { - pub time: String, -} - -#[function_component] -pub fn RenderedAt(props: &RenderedAtProps) -> Html { - html! { -

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

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

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

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

- { &props.data } -

- } -} - -// used like this -html! { - data=123 /> -}; - -// or -html! { - data={"foo".to_string()} /> -}; -``` diff --git a/website/docs/concepts/function-components/callbacks.mdx b/website/docs/concepts/function-components/callbacks.mdx new file mode 100644 index 00000000000..374969bff09 --- /dev/null +++ b/website/docs/concepts/function-components/callbacks.mdx @@ -0,0 +1,72 @@ +--- +title: "Callbacks" +--- + +Callbacks used to asynchronously communicate in Yew upwards the components tree and other things like agents or DOM. +Internally their type is just `Fn` wrapped in `Rc` to allow them to be cloned. You will need to clone them often. + +They have an `emit` function that takes their `` types as an arguments. + +```rust +use yew::{html, Component, Context, Html, Callback}; + +let cb: Callback = Callback::from(move |name: String| { + format!("Bye {}", name) +}); + +let result = cb.emit(String::from("Bob")); // call the callback +// web_sys::console::log_1(&result.into()); // if uncommented will print "Bye Bob" +``` + +## Passing callbacks as props + +A common pattern in yew is to create a callback and pass it down as a prop. + +```rust +use yew::{function_component, html, Html, Properties, Callback}; + +#[derive(Properties, PartialEq)] +pub struct Props { + pub on_name_entry: Callback, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + + props.on_name_entry.emit(String::from("Bob")); + + html! { "Hello" } +} + +// Then supply the prop +#[function_component] +fn App() -> Html { + let on_name_entry: Callback = Callback::from(move |name: String| { + let greeting = format!("Hey, {}!", name); + // web_sys::console::log_1(&greeting.into()); // if uncommented will print + }); + + html! {} +} + +``` + +## DOM Events and Callbacks + +Callbacks are also used to hook into DOM events. + +For example here we define a callback that will be called when user clicks the button: + +```rust +use yew::{function_component, html, Html, Properties, Callback}; + +#[function_component] +fn App() -> Html { + let onclick = Callback::from(move |_| { + let greeting = String::from("Hi there"); + // web_sys::console::log_1(&greeting.into()); // if uncommented will print + }); + + html! {} +} +``` diff --git a/website/docs/concepts/function-components/children.mdx b/website/docs/concepts/function-components/children.mdx new file mode 100644 index 00000000000..1edda034f67 --- /dev/null +++ b/website/docs/concepts/function-components/children.mdx @@ -0,0 +1,42 @@ +--- +title: "Children" +--- + +`Children` is a special prop type that allows you to take what was provided as `Html` children. + +```rust +use yew::{function_component, html, Html, Properties, Children}; + +#[function_component] +fn App() -> Html { + html! { + // highlight-start + + {"Hey what is up ;)"} +

{"THE SKY"}

+
+ // highlight-end + } +} + +#[derive(Properties, PartialEq)] +pub struct Props { + // highlight-next-line + pub children: Children, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! { +
+ // highlight-next-line + { for props.children.iter() } //then use like this +
+ } +} + +``` + +## Further reading + +- [Advanced ways to handle children](../../advanced-topics/children) diff --git a/website/docs/concepts/function-components/communication.mdx b/website/docs/concepts/function-components/communication.mdx new file mode 100644 index 00000000000..250543666b2 --- /dev/null +++ b/website/docs/concepts/function-components/communication.mdx @@ -0,0 +1,11 @@ +--- +title: "Communication between components" +--- + +## Parent to child messaging + +Pass data as [props](./properties) that cause a rerender, this is the way to pass messages to children. + +## Child to parent messaging + +Pass down a callback via props, that the child on an event can call. [Example](callbacks#passing-callbacks-as-props) diff --git a/website/docs/concepts/function-components/generics.mdx b/website/docs/concepts/function-components/generics.mdx new file mode 100644 index 00000000000..6a5ac49f3cf --- /dev/null +++ b/website/docs/concepts/function-components/generics.mdx @@ -0,0 +1,44 @@ +--- +title: "Generic Components" +description: "The #[function_component] attribute" +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +The `#[function_component]` attribute also works with generic functions for creating generic components. + +```rust +use std::fmt::Display; +use yew::{function_component, html, Properties, Html}; + +#[derive(Properties, PartialEq)] +pub struct Props +where + T: PartialEq, +{ + data: T, +} + +#[function_component] +pub fn MyGenericComponent(props: &Props) -> Html +where + T: PartialEq + Display, +{ + html! { +

+ { &props.data } +

+ } +} + +// then can be used like this +html! { + data=123 /> +}; + +// or +html! { + data={"foo".to_string()} /> +}; +``` diff --git a/website/docs/concepts/function-components/custom-hooks.mdx b/website/docs/concepts/function-components/hooks/custom-hooks.mdx similarity index 98% rename from website/docs/concepts/function-components/custom-hooks.mdx rename to website/docs/concepts/function-components/hooks/custom-hooks.mdx index 95445b1b56f..b6adcea87c5 100644 --- a/website/docs/concepts/function-components/custom-hooks.mdx +++ b/website/docs/concepts/function-components/hooks/custom-hooks.mdx @@ -1,6 +1,5 @@ --- title: "Custom Hooks" -description: "Defining your own Hooks " --- ## Defining custom Hooks @@ -42,6 +41,7 @@ We'll start by creating a new function called `use_event`. The `use_` prefix denotes that a function is a hook. This function will take an event target, a event type and a callback. All hooks must be marked by `#[hook]` to function as as hook. + ```rust use web_sys::{Event, EventTarget}; use std::borrow::Cow; diff --git a/website/docs/concepts/function-components/hooks/introduction.mdx b/website/docs/concepts/function-components/hooks/introduction.mdx new file mode 100644 index 00000000000..cad9ad71cc9 --- /dev/null +++ b/website/docs/concepts/function-components/hooks/introduction.mdx @@ -0,0 +1,47 @@ +--- +title: "Hooks" +slug: /concepts/function-components/hooks +--- + +## Hooks + +Hooks are functions that let you store state and perform side-effects. + +Yew comes with a few pre-defined Hooks. You can also create your own or discover many [community made hooks](/community/awesome#hooks). + +## Rules of hooks + +1. A hook function name always has to start with `use_` +2. Hooks can only be used at the following locations: + - Top level of a function / hook. + - If condition inside a function / hook, given it's not already branched. + - Match condition inside a function / hook, given it's not already branched. + - Blocks inside a function / hook, given it's not already branched. +3. Hooks every render must be called in the same order + +All these rules are enforced by either compile time or run-time errors. + +### Pre-defined Hooks + +Yew comes with the following predefined Hooks: + +- [`use_state`](./pre-defined-hooks.mdx#use_state) +- [`use_state_eq`](./pre-defined-hooks.mdx#use_state_eq) +- [`use_memo`](./pre-defined-hooks.mdx#use_memo) +- [`use_mut_ref`](./pre-defined-hooks.mdx#use_mut_ref) +- [`use_node_ref`](./pre-defined-hooks.mdx#use_node_ref) +- [`use_reducer`](./pre-defined-hooks.mdx#use_reducer) +- [`use_reducer_eq`](./pre-defined-hooks.mdx#use_reducer_eq) +- [`use_effect`](./pre-defined-hooks.mdx#use_effect) +- [`use_effect_with_deps`](./pre-defined-hooks.mdx#use_effect_with_deps) +- [`use_context`](./pre-defined-hooks.mdx#use_context) + +### Custom Hooks + +There are cases where you want to define your own Hooks for reasons. Yew allows you to define your own Hooks which lets you extract your potentially stateful logic from the component into reusable functions. +See the [Defining custom hooks](./custom-hooks.mdx#defining-custom-hooks) section for more information. + +## Further reading + +- The React documentation has a section on [React hooks](https://reactjs.org/docs/hooks-intro.html). + These are not exactly the same as Yew's hooks, but the underlying concept is similar. diff --git a/website/docs/concepts/function-components/hooks/use-context.mdx b/website/docs/concepts/function-components/hooks/use-context.mdx new file mode 100644 index 00000000000..be031fe9a5e --- /dev/null +++ b/website/docs/concepts/function-components/hooks/use-context.mdx @@ -0,0 +1,65 @@ +--- +title: "use_context" +--- + +[Contexts](../contexts.mdx) will be introduced later. + +`use_context` is used for consuming [contexts](../contexts.mdx) in function components. + +## Example + +```rust +use yew::{ContextProvider, function_component, html, use_context, use_state, Html}; + + +/// App theme +#[derive(Clone, Debug, PartialEq)] +struct Theme { + foreground: String, + background: String, +} + +/// Main component +#[function_component] +pub fn App() -> Html { + let ctx = use_state(|| Theme { + foreground: "#000000".to_owned(), + background: "#eeeeee".to_owned(), + }); + + html! { + // `ctx` is type `Rc>` while we need `Theme` + // so we deref it. + // It derefs to `&Theme`, hence the clone + context={(*ctx).clone()}> + // Every child here and their children will have access to this context. + + > + } +} + +/// The toolbar. +/// This component has access to the context +#[function_component] +pub fn Toolbar() -> Html { + html! { +
+ +
+ } +} + +/// Button placed in `Toolbar`. +/// As this component is a child of `ThemeContextProvider` in the component tree, it also has access to the context. +#[function_component] +pub fn ThemedButton() -> Html { + //highlight-next-line + let theme = use_context::().expect("no ctx found"); + + html! { + + } +} +``` diff --git a/website/docs/concepts/function-components/hooks/use-effect.mdx b/website/docs/concepts/function-components/hooks/use-effect.mdx new file mode 100644 index 00000000000..8a0515af26a --- /dev/null +++ b/website/docs/concepts/function-components/hooks/use-effect.mdx @@ -0,0 +1,160 @@ +--- +title: "use_effect" +--- + +`use_effect` is used for hooking into the component's lifecycle and creating side-effects. + +It takes a function which is called after every component's render finishes. + +The input function has to return a closure - a cleanup function, which is called right before starting a new render. + +## Example + +```rust +use yew::{Callback, function_component, html, use_effect, use_state, Html}; + +#[function_component] +fn EffectExample() -> Html { + let counter = use_state(|| 0); + + { + let counter = counter.clone(); + use_effect(move || { + // Make a call to DOM API after component is rendered + gloo::utils::document().set_title(&format!("You clicked {} times", *counter)); + + // Perform the cleanup + || gloo::utils::document().set_title("You clicked 0 times") + }); + } + let onclick = { + let counter = counter.clone(); + Callback::from(move |_| counter.set(*counter + 1)) + }; + + html! { + + } +} +``` + +## `use_effect_with_deps` + +`use_effect_with_deps` is a more enhanced version of [`use_effect`](#use_effect). + +It takes a second argument - dependencies. + +Only when dependencies change, it calls the provided function. + +Same way with the clean-up function - it gets called only before re-calling the function from the first argument. + +:::note + +`dependencies` must implement `PartialEq`. + +::: + +```rust +use yew::{function_component, html, Html, Properties, use_effect_with_deps}; + +#[derive(Properties, PartialEq)] +pub struct Props { + pub is_loading: bool, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + let is_loading = props.is_loading.clone(); + + use_effect_with_deps( + move |_| { + web_sys::console::log_1(&" Is loading prop changed!".into()); + || () + }, + // highlight-next-line + is_loading, + ); + + html! { <>{"Am I loading? - "}{is_loading} } +} +``` + +## Tips + +### Only on first render + +Provide a empty tuple `()` as dependencies when you need to do something only on first render. + +```rust +use yew::{function_component, html, Html, use_effect_with_deps}; + +#[function_component] +fn HelloWorld() -> Html { + + use_effect_with_deps( + move |_| { + web_sys::console::log_1(&"I got rendered, yay!".into()); + || () + }, + // highlight-next-line + (), + ); + + html! { "Hello" } +} +``` + +### On destructing or last render + +Use [Only on first render](#only-on-first-render) and write the code in the cleanup function. +It will only get called when the component is removed from view / gets destroyed. + +```rust +use yew::{function_component, html, Html, use_effect_with_deps}; + +#[function_component] +fn HelloWorld() -> Html { + use_effect_with_deps( + move |_| { + // move is not needed bellow but the docs page breaks without it + move || { + web_sys::console::log_1(&"Noo dont kill me, ahhh!".into()); + } + }, + (), + ); + html! { "Hello" } +} +``` + +### Applying event listeners to the DOM + +Generally you can directly add the `on` + event name as just a html attribute. + +But for cases when you need to do it manually always re-apply and destroy old event listeners every render to avoid duplicate listeners in the DOM. + +Same tip applies to intervals and timeouts. + +```rust +use yew::{function_component, html, Html, use_effect_with_deps, use_node_ref}; + +#[function_component] +fn HelloWorld() -> Html { + let span_ref = use_node_ref(); + + { + let span_ref = span_ref.clone(); + use_effect_with_deps( + move |_| { + let listener = gloo::events::EventListener::new(&span_ref.cast::().unwrap(), "click", move |event| { + web_sys::console::log_1(&"I got clicked".into()); + }); + move || drop(listener) + }, + (), + ); + } + + html! {{"Hello"}} +} +``` diff --git a/website/docs/concepts/function-components/hooks/use-memo.mdx b/website/docs/concepts/function-components/hooks/use-memo.mdx new file mode 100644 index 00000000000..31a9342126f --- /dev/null +++ b/website/docs/concepts/function-components/hooks/use-memo.mdx @@ -0,0 +1,34 @@ +--- +title: "use_memo" +--- + +`use_memo` is used for obtaining an immutable reference to a memoized value. +Its state persists across renders. +Its value will be recalculated only if any of the dependencies values change. + +`use_memo` can be useful for keeping things in scope for the lifetime of the component, so long as +you don't store a clone of the resulting `Rc` anywhere that outlives the component. + +```rust +use yew::{function_component, html, use_memo, use_state, Callback, Html, Properties}; + +#[derive(PartialEq, Properties)] +pub struct Props { + pub step: usize, +} + +#[function_component(UseMemo)] +fn ref_hook(props: &Props) -> Html { + // Will only get recalculated if `props.step` value changes + let message = use_memo( + |step| format!("{}. Do Some Expensive Calculation", step), + props.step + ); + + html! { +
+ { (*message).clone() } +
+ } +} +``` diff --git a/website/docs/concepts/function-components/hooks/use-mut-ref.mdx b/website/docs/concepts/function-components/hooks/use-mut-ref.mdx new file mode 100644 index 00000000000..8fd148d8597 --- /dev/null +++ b/website/docs/concepts/function-components/hooks/use-mut-ref.mdx @@ -0,0 +1,55 @@ +--- +title: "use_mut_ref" +--- + +`use_mut_ref` is used for obtaining a mutable reference to a value. +Its state persists across renders. + +It is important to note that you do not get notified of state changes. +If you need the component to be re-rendered on state change, consider using [`use_state`](./use-state). + +## Example + +Here you can see we count `message_count` as that number is not used in `Html` and thus does not need to cause a rerender. + +```rust +use web_sys::HtmlInputElement; +use yew::{ + events::Event, + function_component, html, use_mut_ref, use_state, + Callback, TargetCast, + Html, +}; + +#[function_component] +fn MutRefExample() -> Html { + let message = use_state(|| "".to_string()); + let message_count = use_mut_ref(|| 0); + + let onclick = Callback::from(move |_| { + let window = gloo::utils::window(); + + if *message_count.borrow_mut() > 3 { + window.alert_with_message("Message limit reached").unwrap(); + } else { + *message_count.borrow_mut() += 1; + window.alert_with_message("Message sent").unwrap(); + } + }); + + let onchange = { + let message = message.clone(); + Callback::from(move |e: Event| { + let input: HtmlInputElement = e.target_unchecked_into(); + message.set(input.value()); + }) + }; + + html! { +
+ + +
+ } +} +``` diff --git a/website/docs/concepts/function-components/hooks/use-node-ref.mdx b/website/docs/concepts/function-components/hooks/use-node-ref.mdx new file mode 100644 index 00000000000..ac04638d1fe --- /dev/null +++ b/website/docs/concepts/function-components/hooks/use-node-ref.mdx @@ -0,0 +1,50 @@ +--- +title: "use_node_ref" +--- + +`use_node_ref` is used for maintaining an easy reference to the DOM element represented as a `NodeRef`. It also persists across renders. + +## Example + +```rust +use web_sys::HtmlInputElement; +use yew::{ + function_component, functional::*, html, + NodeRef, Html +}; + +#[function_component(UseRef)] +pub fn ref_hook() -> Html { + // highlight-next-line + let input_ref = use_node_ref(); + let value = use_state(|| 25_f64); + + let onclick = { + let input_ref = input_ref.clone(); + let value = value.clone(); + move |_| { + // highlight-start + if let Some(input) = input_ref.cast::() { + value.set(*value + input.value_as_number()); + } + // highlight-end + } + }; + + html! { +
+ // highlight-next-line + + +
+ } +} +``` + +:::tip Advanced tip + +When conditionally rendering elements you can use `NodeRef` in conjunction with `use_effect_with_deps` +to perform actions each time an element is rendered and just before its going to be removed from the +DOM. + +::: diff --git a/website/docs/concepts/function-components/hooks/use-reducer.mdx b/website/docs/concepts/function-components/hooks/use-reducer.mdx new file mode 100644 index 00000000000..d8f1d027755 --- /dev/null +++ b/website/docs/concepts/function-components/hooks/use-reducer.mdx @@ -0,0 +1,109 @@ +--- +title: "use_reducer" +--- + +`use_reducer` is an alternative to [`use_state`](./use-state). It is used to handle more complex component's state. + +It accepts an initial state function and returns a `UseReducerHandle` that dereferences to the state, +and a dispatch function. +The dispatch function takes one argument of type `Action`. When called, the action and current value +are passed to the reducer function which computes a new state that is returned, +and the component is re-rendered. + +:::tip Advanced tip + +The dispatch function is guaranteed to be the same across the entire +component lifecycle. You can safely omit the `UseReducerHandle` from the +dependents of `use_effect_with_deps` if you only intend to dispatch +values from within the hooks. + +::: + +The state object returned by the initial state function is required to +implement a `Reducible` trait which provides an `Action` type and a +reducer function. + +This hook will always trigger a re-render upon receiving an action. See +[`use_reducer_eq`](#use_reducer_eq) if you want the component to only +re-render when the state changes. + +## Example + +```rust +use yew::prelude::*; +use std::rc::Rc; + +/// reducer's Action +enum CounterAction { + Double, + Square, +} + +/// reducer's State +struct CounterState { + counter: i32, +} + +impl Default for CounterState { + fn default() -> Self { + Self { counter: 1 } + } +} + +impl Reducible for CounterState { + /// Reducer Action Type + type Action = CounterAction; + + /// Reducer Function + fn reduce(self: Rc, action: Self::Action) -> Rc { + let next_ctr = match action { + CounterAction::Double => self.counter * 2, + CounterAction::Square => self.counter.pow(2) + }; + + Self { counter: next_ctr }.into() + } +} + +#[function_component] +fn ReducerExample() -> Html { + // The use_reducer hook takes an initialization function which will be called only once. + let counter = use_reducer(CounterState::default); + + let double_onclick = { + let counter = counter.clone(); + Callback::from(move |_| counter.dispatch(CounterAction::Double)) + }; + let square_onclick = { + let counter = counter.clone(); + Callback::from(move |_| counter.dispatch(CounterAction::Square)) + }; + + html! { + <> +
{ counter.counter }
+ + + + + } +} +``` + +:::caution + +The value held in the handle will reflect the value of at the time the +handle is returned by the `use_reducer`. It is possible that the handle does +not dereference to an up to date value if you are moving it into a +`use_effect_with_deps` hook. You can register the +state to the dependents so the hook can be updated when the value changes. + +::: + +## `use_reducer_eq` + +This hook has the same effect as `use_reducer` but will only trigger a +re-render when the reducer function produces a value that `prev_state != next_state`. + +This hook requires the state object to implement `PartialEq` in addition +to the `Reducible` trait required by `use_reducer`. diff --git a/website/docs/concepts/function-components/hooks/use-state.mdx b/website/docs/concepts/function-components/hooks/use-state.mdx new file mode 100644 index 00000000000..1bf1a64ce22 --- /dev/null +++ b/website/docs/concepts/function-components/hooks/use-state.mdx @@ -0,0 +1,67 @@ +--- +title: "use_state" +--- + +`use_state` is used to manage state in a function component. +It returns a `UseStateHandle` object which `Deref`s to the currently stored value +and provides a `set` method to update the value. + +The hook takes a function as input which determines the initial value. + +:::tip Advanced tip + +The setter function is guaranteed to be the same across the entire +component lifecycle. You can safely omit the `UseStateHandle` from the +dependents of `use_effect_with_deps` if you only intend to set +values from within the hook. + +::: + +This hook will always trigger a re-render upon receiving a new state. See +[`use_state_eq`](#use_state_eq) if you want the component to only +re-render when the state changes. + +## Example + +```rust +use std::ops::Deref; +use yew::{Callback, function_component, html, use_state, Html}; + +#[function_component] +fn StateExample() -> Html { + let name_handle = use_state(|| String::from("Bob")); + let name = name_handle.deref().clone(); + let onclick = { + let name = name.clone(); + Callback::from(move |_| name_handle.set(format!("{}y Jr.", name))) + }; + + + html! { +
+ +

+ { "My name is: " } + { name } +

+
+ } +} +``` + +:::caution + +The value held in the handle will reflect the value at the time the +handle is returned by the `use_state`. It is possible that the handle +does not dereference to an up to date value if you are moving it into a +`use_effect_with_deps` hook. You can register the +state to the dependents so the hook can be updated when the value changes. + +::: + +## `use_state_eq` + +This hook has the same effect as `use_state` but will only trigger a +re-render when the setter receives a value that `prev_state != next_state`. + +This hook requires the state object to implement `PartialEq`. diff --git a/website/docs/concepts/function-components/introduction.mdx b/website/docs/concepts/function-components/introduction.mdx index 64098c07238..1f6a8f6fa1d 100644 --- a/website/docs/concepts/function-components/introduction.mdx +++ b/website/docs/concepts/function-components/introduction.mdx @@ -1,19 +1,35 @@ --- title: "Function Components" -sidebar_label: Introduction -description: "Introduction to function components " +slug: /concepts/function-components --- -Function components are a simplified version of normal components. They consist of a single function -that receives props and determines what should be rendered by returning `Html`. Basically, it's a -component that's been reduced to just the `view` method. On its own that would be quite limiting -because you can only create pure components, but that's where Hooks come in. Hooks allow function -components to maintain their own internal state and use other Yew features without needing to manually -implement the `Component` trait. +Lets revisit this previous statement: + +> Yew mostly operates on the idea of keeping everything that a reusable piece of +> UI may need, in one place - rust files. + +It is only partially correct, "rust files" should be replaced with "components". + +## What are Components? + +Components are the building blocks of Yew. + +They: + +- Take arguments in form of [Props](./properties) +- Can have their own state +- Get computed into HTML visible to the user (DOM) + +## Two flavours of Yew Components + +You are currently reading about function components - the recommended way to write components when starting with Yew. + +But we have to note that there is a more advanced, but less recommended way to write them - [Struct components](../advanced-topics/struct-components/introduction) ## Creating function components -The easiest way to create a function component is to add the [`#[function_component]`](./../function-components/attribute.mdx) attribute to a function. +To create a function component add the `#[function_component]` attribute to a function. +Also name the function in PascalCase as it is the convention for naming components. ```rust use yew::{function_component, html, Html}; @@ -22,50 +38,32 @@ use yew::{function_component, html, Html}; fn HelloWorld() -> Html { html! { "Hello world" } } -``` - -### Under the hood - -There are two parts to how Yew implements function components. - -The first part is the `FunctionProvider` trait which is analogous to the `Component` trait, except -that it only has a single method (called `run`). The second part is the `FunctionComponent` struct -which wraps types implementing `FunctionProvider` and implements `Component`. -The `#[function_component]` attribute is a procedural macro which automatically implements -`FunctionProvider` for you and exposes it wrapped in `FunctionComponent`. +// Then somewhere else you can use the component inside the `html!` +#[function_component] +fn App() -> Html { + html! {} +} +``` -### Hooks +## What happens to components -Hooks are functions that let you "hook into" components' state and/or lifecycle and perform -actions. Yew comes with a few pre-defined Hooks. You can also create your own. +Yew will build a tree of these components that from the previous example would look like this: -Hooks can only be used at the following locations: -- Top level of a function / hook. -- If condition inside a function / hook, given it's not already branched. -- Match condition inside a function / hook, given it's not already branched. -- Blocks inside a function / hook, given it's not already branched. +```md + + | + +``` -#### Pre-defined Hooks +It will call those functions / function components to compute a virtual version of the DOM (VDOM) that you as the library user see as the `Html` type. -Yew comes with the following predefined Hooks: -- [`use_state`](./../function-components/pre-defined-hooks.mdx#use_state) -- [`use_state_eq`](./../function-components/pre-defined-hooks.mdx#use_state_eq) -- [`use_memo`](./../function-components/pre-defined-hooks.mdx#use_memo) -- [`use_mut_ref`](./../function-components/pre-defined-hooks.mdx#use_mut_ref) -- [`use_node_ref`](./../function-components/pre-defined-hooks.mdx#use_node_ref) -- [`use_reducer`](./../function-components/pre-defined-hooks.mdx#use_reducer) -- [`use_reducer_eq`](./../function-components/pre-defined-hooks.mdx#use_reducer_eq) -- [`use_effect`](./../function-components/pre-defined-hooks.mdx#use_effect) -- [`use_effect_with_deps`](./../function-components/pre-defined-hooks.mdx#use_effect_with_deps) -- [`use_context`](./../function-components/pre-defined-hooks.mdx#use_context) +Yew will compare current DOM with VDOM and only update the new/changed/necessary parts. -#### Custom Hooks +This is what we call **rendering**. -There are cases where you want to define your own Hooks for reasons. Yew allows you to define your own Hooks which lets you extract your potentially stateful logic from the component into reusable functions. -See the [Defining custom hooks](./../function-components/custom-hooks.mdx#defining-custom-hooks) section for more information. +:::note -## Further reading +Behind the scenes `Html` type is just an alias for `VNode` - virtual node. -* The React documentation has a section on [React hooks](https://reactjs.org/docs/hooks-intro.html). -These are not exactly the same as Yew's hooks, but the underlying concept is similar. +::: diff --git a/website/docs/concepts/function-components/node-refs.mdx b/website/docs/concepts/function-components/node-refs.mdx new file mode 100644 index 00000000000..4cfd3b4e962 --- /dev/null +++ b/website/docs/concepts/function-components/node-refs.mdx @@ -0,0 +1,14 @@ +--- +title: "Node Refs" +description: "Out-of-band DOM access" +--- + +The `ref` keyword can be used inside of any HTML element or component to get the DOM `Element` that +the item is attached to. This can be used to make changes to the DOM outside of the `view` lifecycle +method. + +This is useful for getting ahold of canvas elements, or scrolling to different sections of a page. + +## Example + +See [use_node_ref hook](./hooks/use-node-ref) diff --git a/website/docs/concepts/function-components/pre-defined-hooks.mdx b/website/docs/concepts/function-components/pre-defined-hooks.mdx deleted file mode 100644 index 5402ebfa66d..00000000000 --- a/website/docs/concepts/function-components/pre-defined-hooks.mdx +++ /dev/null @@ -1,411 +0,0 @@ ---- -title: "Pre-defined Hooks" -description: "The pre-defined Hooks that Yew comes with " ---- - -## `use_state` - -`use_state` is used to manage state in a function component. -It returns a `UseStateHandle` object which `Deref`s to the current value -and provides a `set` method to update the value. - -The hook takes a function as input which determines the initial state. -This value remains up-to-date on subsequent renders. - -The setter function is guaranteed to be the same across the entire -component lifecycle. You can safely omit the `UseStateHandle` from the -dependents of `use_effect_with_deps` if you only intend to set -values from within the hook. - -This hook will always trigger a re-render upon receiving a new state. See -[`use_state_eq`](#use_state_eq) if you want the component to only -re-render when the state changes. - -### Example - -```rust -use yew::{Callback, function_component, html, use_state, Html}; - -#[function_component(UseState)] -fn state() -> Html { - let counter = use_state(|| 0); - let onclick = { - let counter = counter.clone(); - Callback::from(move |_| counter.set(*counter + 1)) - }; - - - html! { -
- -

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

-
- } -} -``` - -:::caution - -The value held in the handle will reflect the value at the time the -handle is returned by the `use_state`. It is possible that the handle -does not dereference to an up to date value if you are moving it into a -`use_effect_with_deps` hook. You can register the -state to the dependents so the hook can be updated when the value changes. - -::: - -## `use_state_eq` - -This hook has the same effect as `use_state` but will only trigger a -re-render when the setter receives a value that `prev_state != next_state`. - -This hook requires the state object to implement `PartialEq`. - -## `use_memo` -`use_memo` is used for obtaining an immutable reference to a memoized value. -Its state persists across renders. -Its value will be recalculated only if any of the dependencies values change. - -`use_memo` can be useful for keeping things in scope for the lifetime of the component, so long as -you don't store a clone of the resulting `Rc` anywhere that outlives the component. - -```rust -use yew::{function_component, html, use_memo, use_state, Callback, Html, Properties}; - -#[derive(PartialEq, Properties)] -pub struct Props { - pub step: usize, -} - -#[function_component(UseMemo)] -fn ref_hook(props: &Props) -> Html { - // Will only get recalculated if `props.step` value changes - let message = use_memo( - |step| format!("{}. Do Some Expensive Calculation", step), - props.step - ); - - html! { -
- { (*message).clone() } -
- } -} -``` - -## `use_mut_ref` -`use_mut_ref` is used for obtaining a mutable reference to a value. -Its state persists across renders. - -It is important to note that you do not get notified of state changes. -If you need the component to be re-rendered on state change, consider using [`use_state`](#use_state). - -### Example - -```rust -use web_sys::HtmlInputElement; -use yew::{ - events::Event, - function_component, html, use_mut_ref, use_state, - Callback, TargetCast, - Html, -}; - -#[function_component(UseMutRef)] -fn mut_ref_hook() -> Html { - let message = use_state(|| "".to_string()); - let message_count = use_mut_ref(|| 0); - - let onclick = Callback::from(move |_| { - let window = gloo::utils::window(); - - if *message_count.borrow_mut() > 3 { - window.alert_with_message("Message limit reached").unwrap(); - } else { - *message_count.borrow_mut() += 1; - window.alert_with_message("Message sent").unwrap(); - } - }); - - let onchange = { - let message = message.clone(); - Callback::from(move |e: Event| { - let input: HtmlInputElement = e.target_unchecked_into(); - message.set(input.value()); - }) - }; - - html! { -
- - -
- } -} -``` - -## `use_node_ref` -`use_node_ref` is used for obtaining a `NodeRef` that persists across renders. - -When conditionally rendering elements you can use `NodeRef` in conjunction with `use_effect_with_deps` -to perform actions each time an element is rendered and just before its going to be removed from the -DOM. - -### Example - -```rust -use web_sys::HtmlInputElement; -use yew::{ - function_component, functional::*, html, - NodeRef, Html -}; - -#[function_component(UseRef)] -pub fn ref_hook() -> Html { - let input_ref = use_node_ref(); - let value = use_state(|| 25f64); - - let onclick = { - let input_ref = input_ref.clone(); - let value = value.clone(); - move |_| { - if let Some(input) = input_ref.cast::() { - value.set(*value + input.value_as_number()); - } - } - }; - - html! { -
- - -
- } -} -``` - -## `use_reducer` - -`use_reducer` is an alternative to [`use_state`](#use_state). It is used to handle component's state and is used -when complex actions needs to be performed on said state. - -It accepts an initial state function and returns a `UseReducerHandle` that dereferences to the state, -and a dispatch function. -The dispatch function takes one argument of type `Action`. When called, the action and current value -are passed to the reducer function which computes a new state which is returned, -and the component is re-rendered. - -The dispatch function is guaranteed to be the same across the entire -component lifecycle. You can safely omit the `UseReducerHandle` from the -dependents of `use_effect_with_deps` if you only intend to dispatch -values from within the hooks. - -The state object returned by the initial state function is required to -implement a `Reducible` trait which provides an `Action` type and a -reducer function. - -This hook will always trigger a re-render upon receiving an action. See -[`use_reducer_eq`](#use_reducer_eq) if you want the component to only -re-render when the state changes. - -### Example - -```rust -use yew::prelude::*; -use std::rc::Rc; - -/// reducer's Action -enum CounterAction { - Double, - Square, -} - -/// reducer's State -struct CounterState { - counter: i32, -} - -impl Default for CounterState { - fn default() -> Self { - Self { counter: 1 } - } -} - -impl Reducible for CounterState { - /// Reducer Action Type - type Action = CounterAction; - - /// Reducer Function - fn reduce(self: Rc, action: Self::Action) -> Rc { - let next_ctr = match action { - CounterAction::Double => self.counter * 2, - CounterAction::Square => self.counter.pow(2) - }; - - Self { counter: next_ctr }.into() - } -} - -#[function_component(UseReducer)] -fn reducer() -> Html { - // The use_reducer hook takes an initialization function which will be called only once. - let counter = use_reducer(CounterState::default); - - let double_onclick = { - let counter = counter.clone(); - Callback::from(move |_| counter.dispatch(CounterAction::Double)) - }; - let square_onclick = { - let counter = counter.clone(); - Callback::from(move |_| counter.dispatch(CounterAction::Square)) - }; - - html! { - <> -
{ counter.counter }
- - - - - } -} -``` - -:::caution - -The value held in the handle will reflect the value of at the time the -handle is returned by the `use_reducer`. It is possible that the handle does -not dereference to an up to date value if you are moving it into a -`use_effect_with_deps` hook. You can register the -state to the dependents so the hook can be updated when the value changes. - -::: - -## `use_reducer_eq` - -This hook has the same effect as `use_reducer` but will only trigger a -re-render when the reducer function produces a value that `prev_state != next_state`. - -This hook requires the state object to implement `PartialEq` in addition -to the `Reducible` trait required by `use_reducer`. - -## `use_effect` - -`use_effect` is used for hooking into the component's lifecycle. -Similar to `rendered` from the `Component` trait, -`use_effect` takes a function which is called after the render finishes. - -The input function has to return a closure, the destructor, which is called when the component is destroyed. -The destructor can be used to clean up the effects introduced and it can take ownership of values to delay dropping them until the component is destroyed. - -### Example - -```rust -use yew::{Callback, function_component, html, use_effect, use_state, Html}; - -#[function_component(UseEffect)] -fn effect() -> Html { - let counter = use_state(|| 0); - - { - let counter = counter.clone(); - use_effect(move || { - // Make a call to DOM API after component is rendered - gloo::utils::document().set_title(&format!("You clicked {} times", *counter)); - - // Perform the cleanup - || gloo::utils::document().set_title("You clicked 0 times") - }); - } - let onclick = { - let counter = counter.clone(); - Callback::from(move |_| counter.set(*counter + 1)) - }; - - html! { - - } -} -``` - -### `use_effect_with_deps` - -Sometimes, it's needed to manually define dependencies for [`use_effect`](#use_effect). In such cases, we use `use_effect_with_deps`. -```rust ,no_run -use yew::use_effect_with_deps; - -use_effect_with_deps( - move |_| { - // ... - || () - }, - (), // dependents -); -``` - -**Note**: `dependents` must implement `PartialEq`. - -## `use_context` - -`use_context` is used for consuming [contexts](../contexts.mdx) in function components. - - -### Example - -```rust -use yew::{ContextProvider, function_component, html, use_context, use_state, Html}; - - -/// App theme -#[derive(Clone, Debug, PartialEq)] -struct Theme { - foreground: String, - background: String, -} - -/// Main component -#[function_component(App)] -pub fn app() -> Html { - let ctx = use_state(|| Theme { - foreground: "#000000".to_owned(), - background: "#eeeeee".to_owned(), - }); - - html! { - // `ctx` is type `Rc>` while we need `Theme` - // so we deref it. - // It derefs to `&Theme`, hence the clone - context={(*ctx).clone()}> - // Every child here and their children will have access to this context. - - > - } -} - -/// The toolbar. -/// This component has access to the context -#[function_component(Toolbar)] -pub fn toolbar() -> Html { - html! { -
- -
- } -} - -/// Button placed in `Toolbar`. -/// As this component is a child of `ThemeContextProvider` in the component tree, it also has access to the context. -#[function_component(ThemedButton)] -pub fn themed_button() -> Html { - let theme = use_context::().expect("no ctx found"); - - html! { - - } -} -``` diff --git a/website/docs/concepts/function-components/properties.mdx b/website/docs/concepts/function-components/properties.mdx new file mode 100644 index 00000000000..06257eccffa --- /dev/null +++ b/website/docs/concepts/function-components/properties.mdx @@ -0,0 +1,258 @@ +--- +title: "Properties" +description: "Parent to child communication" +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +:::note + +Properties are often shortened as "Props". + +::: + +Properties are essentially component function arguments that Yew can keep watch on. +Components can have an associated properties type which describes what is passed down from the parent. + +Props are defined using the `Properties` trait. + +## Reactivity + +Yew always checks if props changed to know if the component needs to be rerendered. +This way Yew can be considered a very reactive framework as any change from the parent will always be propagated downwards +and the view will never be out of sync from the data coming from props/state. + +:::tip + +If you have not yet completed the [tutorial](../../tutorial), try it out and test this reactivity yourself! + +::: + +## Memory/speed overhead of using Properties + +Internally properties are reference counted. This means that only a pointer is passed down the component tree for props. +It saves us from the cost of having to clone the entire props, which might be expensive. + +## Derive macro + +Yew provides a derive macro to easily implement the `Properties` trait. + +Types for which you derive `Properties` must also implement `PartialEq` so Yew can do data comparison. + +```rust +use yew::Properties; + +#[derive(Properties, PartialEq)] +pub struct Props { + pub is_loading: bool, +} +``` + +## Use in function components + +The attribute `#[function_component]` allows to optionally specify Props in the function arguments + + + + +```rust +use yew::{function_component, html, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct Props { + pub is_loading: bool, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! { <>{"Am I loading? - "}{props.is_loading.clone()} } +} + +// Then supply the prop +#[function_component] +fn App() -> Html { + html! {} +} + +``` + + + + +```rust +use yew::{function_component, html, Html}; + + + + + + +#[function_component] +fn HelloWorld() -> Html { + html! { "Hello world" } +} + +// No props to supply +#[function_component] +fn App() -> Html { + html! {} +} + +``` + + + + +## Derive macro field attributes + +When deriving `Properties`, all fields are required by default. +The following attributes allow you to give your props initial values which will be used when parent has not set them. + +:::tip +Attributes aren't visible in Rustdoc generated documentation. +The doc strings of your properties should mention whether a prop is optional and if it has a special default value. +::: + + + + +Initialize the prop value with the default value of the field's type using the `Default` trait. + +```rust +use yew::{function_component, html, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct Props { + // highlight-start + #[prop_or_default] + // highlight-end + pub is_loading: bool, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + if props.is_loading.clone() { + html! { "Loading" } + } else { + html! { "Hello world" } + } +} + +// Then use like this with default +#[function_component] +fn Case1() -> Html { + html! {} +} +// Or no override the default +#[function_component] +fn Case2() -> Html { + html! {} +} +``` + + + + +Use `value` to initialize the prop value. `value` can be any expression that returns the field's type. +For example, to default a boolean prop to `true`, use the attribute `#[prop_or(true)]`. + +```rust +use yew::{function_component, html, Html, Properties}; + +#[derive(Properties, PartialEq)] +pub struct Props { + // highlight-start + #[prop_or("Bob".to_string())] + // highlight-end + pub name: String, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! {<>{"Hello world"}{props.name.clone()}} +} + +// Then use like this with default +#[function_component] +fn Case1() -> Html { + html! {} +} +// Or no override the default +#[function_component] +fn Case2() -> Html { + html! {} +} +``` + + + + +Call `function` to initialize the prop value. `function` should have the signature `FnMut() -> T` where `T` is the field type. + +```rust +use yew::{function_component, html, Html, Properties}; + +fn create_default_name() -> String { + "Bob".to_string() +} + +#[derive(Properties, PartialEq)] +pub struct Props { + // highlight-start + #[prop_or_else(create_default_name)] + // highlight-end + pub name: String, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! {<>{"Hello world"}{props.name.clone()}} +} + +// Then use like this with default +#[function_component] +fn Case1() -> Html { + html! {} +} +// Or no override the default +#[function_component] +fn Case2() -> Html { + html! {} +} +``` + + + + +## Props macro + +The `yew::props!` macro allows you to build properties the same way the `html!` macro does it. + +The macro uses the same syntax as a struct expression except that you can't use attributes or a base expression (`Foo { ..base }`). +The type path can either point to the props directly (`path::to::Props`) or the associated properties of a component (`MyComp::Properties`). + +```rust +use yew::{function_component, html, Html, Properties, props}; + +#[derive(Properties, PartialEq)] +pub struct Props { + #[prop_or("Bob".to_string())] + pub name: String, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + html! {<>{"Hello world"}{props.name.clone()}} +} + +#[function_component] +fn App() -> Html { + // highlight-start + let pre_made_props = props! { + Props {} // Notice we did not need to specify name prop + }; + // highlight-end + html! {} +} +``` diff --git a/website/docs/concepts/function-components/pure-components.mdx b/website/docs/concepts/function-components/pure-components.mdx new file mode 100644 index 00000000000..8977a00853e --- /dev/null +++ b/website/docs/concepts/function-components/pure-components.mdx @@ -0,0 +1,36 @@ +--- +title: "Pure Components" +--- + +## Pure function definition + +A function is considered pure when the return values are always identical to passed down parameters. And it has not side-effects. + +## Pure components + +In the same sense component is pure when it does not have any state or side_effects and with the given props always returns the same `Html`. + +For example below is a pure component, for a given prop `is_loading` it will always result in same `Html` without any side effects. + +```rust +use yew::{Properties, function_component, Html, html}; + +#[derive(Properties, PartialEq)] +pub struct Props { + pub is_loading: bool, +} + +#[function_component] +fn HelloWorld(props: &Props) -> Html { + if props.is_loading.clone() { + html! { "Loading" } + } else { + html! { "Hello world" } + } +} +``` + +## Un-pure components + +Though you probably wonder how can a component even be un-pure if its just a function that is called every render and if it does not use any globals. +This is where the next topic comes in - [hooks](./hooks) diff --git a/website/docs/concepts/function-components/state.mdx b/website/docs/concepts/function-components/state.mdx new file mode 100644 index 00000000000..3211f5419f0 --- /dev/null +++ b/website/docs/concepts/function-components/state.mdx @@ -0,0 +1,17 @@ +--- +title: "State" +--- + +## General view of how to store state + +This table can be used as a guide when deciding what state storing type fits best for your use case: + +| Type | Rerender when? | Scope | +| ---------------------------------------------------- | ---------------------------- | ------------------- | +| [use_state](./hooks/use-state) | got set | component instance | +| [use_state_eq](./hooks/use-state#use_state_eq) | got set with diff. value | component instance | +| [use_reducer](./hooks/use-reducer) | got reduced | component instance | +| [use_reducer_eq](./hooks/use-reducer#use_reducer_eq) | got reduced with diff. value | component instance | +| [use_memo](./hooks/use-memo) | dependencies changed | component instance | +| [use_mut_ref](./hooks/use-mut-ref) | - | component instance | +| a static global variable | - | global, used by all | diff --git a/website/docs/concepts/suspense.mdx b/website/docs/concepts/suspense.mdx index 444ccf4be22..e3092448c26 100644 --- a/website/docs/concepts/suspense.mdx +++ b/website/docs/concepts/suspense.mdx @@ -133,11 +133,10 @@ fn app() -> Html { } ``` - ### Use Suspense in Struct Components It's not possible to suspend a struct component directly. However, you -can use a function component as a Higher-Order-Component to +can use a function component as a [HOC](../advanced-topics/struct-components/hoc) to achieve suspense-based data fetching. ```rust ,ignore diff --git a/website/docs/getting-started/introduction.mdx b/website/docs/getting-started/introduction.mdx index a84ec5cf1eb..183dd2ba442 100644 --- a/website/docs/getting-started/introduction.mdx +++ b/website/docs/getting-started/introduction.mdx @@ -1,10 +1,7 @@ --- -title: "Project Setup" -sidebar_label: Introduction -description: "Set yourself up for success" +title: "Getting Started" --- - You will need a couple of tools to compile, build, package and debug your Yew application. When getting started, we recommend using [Trunk](https://trunkrs.dev/). Trunk is a WASM web application bundler for Rust. diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current.json b/website/i18n/ja/docusaurus-plugin-content-docs/current.json index 5ad46f9ab01..09d1ff38a90 100644 --- a/website/i18n/ja/docusaurus-plugin-content-docs/current.json +++ b/website/i18n/ja/docusaurus-plugin-content-docs/current.json @@ -154,5 +154,37 @@ "sidebar.docs.category.yew-router": { "message": "yew-router", "description": "The label for category yew-router in sidebar docs" + }, + "sidebar.docs.category.Intro With Basic Web Technologies": { + "message": "Intro With Basic Web Technologies", + "description": "The label for category Intro With Basic Web Technologies in sidebar docs" + }, + "sidebar.docs.category.Intro With Basic Web Technologies.link.generated-index.title": { + "message": "Yew Take on Basic Web Technologies", + "description": "The generated-index page title for category Intro With Basic Web Technologies in sidebar docs" + }, + "sidebar.docs.category.Intro With Basic Web Technologies.link.generated-index.description": { + "message": "Yew mostly operates on the idea of keeping everything that a reusable piece of UI may need, in one place - rust files. But also seeks to stay close to the original look of the technology. Explore further to fully grasp what we mean by these statements:", + "description": "The generated-index page description for category Intro With Basic Web Technologies in sidebar docs" + }, + "sidebar.docs.category.Hooks": { + "message": "Hooks", + "description": "The label for category Hooks in sidebar docs" + }, + "sidebar.docs.category.Javascript with Rust": { + "message": "Javascript with Rust", + "description": "The label for category Javascript with Rust in sidebar docs" + }, + "sidebar.docs.category.Javascript with Rust.link.generated-index.title": { + "message": "wasm-bindgen", + "description": "The generated-index page title for category Javascript with Rust in sidebar docs" + }, + "sidebar.docs.category.Javascript with Rust.link.generated-index.description": { + "message": "Learn about wasm-bindgen", + "description": "The generated-index page description for category Javascript with Rust in sidebar docs" + }, + "sidebar.docs.category.Struct Components": { + "message": "Struct Components", + "description": "The label for category Struct Components in sidebar docs" } } diff --git a/website/i18n/zh-CN/docusaurus-plugin-content-docs/current.json b/website/i18n/zh-CN/docusaurus-plugin-content-docs/current.json index 5ad46f9ab01..09d1ff38a90 100644 --- a/website/i18n/zh-CN/docusaurus-plugin-content-docs/current.json +++ b/website/i18n/zh-CN/docusaurus-plugin-content-docs/current.json @@ -154,5 +154,37 @@ "sidebar.docs.category.yew-router": { "message": "yew-router", "description": "The label for category yew-router in sidebar docs" + }, + "sidebar.docs.category.Intro With Basic Web Technologies": { + "message": "Intro With Basic Web Technologies", + "description": "The label for category Intro With Basic Web Technologies in sidebar docs" + }, + "sidebar.docs.category.Intro With Basic Web Technologies.link.generated-index.title": { + "message": "Yew Take on Basic Web Technologies", + "description": "The generated-index page title for category Intro With Basic Web Technologies in sidebar docs" + }, + "sidebar.docs.category.Intro With Basic Web Technologies.link.generated-index.description": { + "message": "Yew mostly operates on the idea of keeping everything that a reusable piece of UI may need, in one place - rust files. But also seeks to stay close to the original look of the technology. Explore further to fully grasp what we mean by these statements:", + "description": "The generated-index page description for category Intro With Basic Web Technologies in sidebar docs" + }, + "sidebar.docs.category.Hooks": { + "message": "Hooks", + "description": "The label for category Hooks in sidebar docs" + }, + "sidebar.docs.category.Javascript with Rust": { + "message": "Javascript with Rust", + "description": "The label for category Javascript with Rust in sidebar docs" + }, + "sidebar.docs.category.Javascript with Rust.link.generated-index.title": { + "message": "wasm-bindgen", + "description": "The generated-index page title for category Javascript with Rust in sidebar docs" + }, + "sidebar.docs.category.Javascript with Rust.link.generated-index.description": { + "message": "Learn about wasm-bindgen", + "description": "The generated-index page description for category Javascript with Rust in sidebar docs" + }, + "sidebar.docs.category.Struct Components": { + "message": "Struct Components", + "description": "The label for category Struct Components in sidebar docs" } } diff --git a/website/i18n/zh-TW/docusaurus-plugin-content-docs/current.json b/website/i18n/zh-TW/docusaurus-plugin-content-docs/current.json index 5ad46f9ab01..09d1ff38a90 100644 --- a/website/i18n/zh-TW/docusaurus-plugin-content-docs/current.json +++ b/website/i18n/zh-TW/docusaurus-plugin-content-docs/current.json @@ -154,5 +154,37 @@ "sidebar.docs.category.yew-router": { "message": "yew-router", "description": "The label for category yew-router in sidebar docs" + }, + "sidebar.docs.category.Intro With Basic Web Technologies": { + "message": "Intro With Basic Web Technologies", + "description": "The label for category Intro With Basic Web Technologies in sidebar docs" + }, + "sidebar.docs.category.Intro With Basic Web Technologies.link.generated-index.title": { + "message": "Yew Take on Basic Web Technologies", + "description": "The generated-index page title for category Intro With Basic Web Technologies in sidebar docs" + }, + "sidebar.docs.category.Intro With Basic Web Technologies.link.generated-index.description": { + "message": "Yew mostly operates on the idea of keeping everything that a reusable piece of UI may need, in one place - rust files. But also seeks to stay close to the original look of the technology. Explore further to fully grasp what we mean by these statements:", + "description": "The generated-index page description for category Intro With Basic Web Technologies in sidebar docs" + }, + "sidebar.docs.category.Hooks": { + "message": "Hooks", + "description": "The label for category Hooks in sidebar docs" + }, + "sidebar.docs.category.Javascript with Rust": { + "message": "Javascript with Rust", + "description": "The label for category Javascript with Rust in sidebar docs" + }, + "sidebar.docs.category.Javascript with Rust.link.generated-index.title": { + "message": "wasm-bindgen", + "description": "The generated-index page title for category Javascript with Rust in sidebar docs" + }, + "sidebar.docs.category.Javascript with Rust.link.generated-index.description": { + "message": "Learn about wasm-bindgen", + "description": "The generated-index page description for category Javascript with Rust in sidebar docs" + }, + "sidebar.docs.category.Struct Components": { + "message": "Struct Components", + "description": "The label for category Struct Components in sidebar docs" } } diff --git a/website/sidebars/docs.js b/website/sidebars/docs.js index 9340b5ee049..50f5fe0c003 100644 --- a/website/sidebars/docs.js +++ b/website/sidebars/docs.js @@ -30,15 +30,46 @@ module.exports = { items: [ { type: "category", - label: "Components", - link: { type: "doc", id: "concepts/components/introduction" }, + label: "Intro With Basic Web Technologies", + link: { + type: "generated-index", + title: "Yew Take on Basic Web Technologies", + description: "Yew mostly operates on the idea of keeping everything that a reusable piece of UI may need, in one place - rust files. But also seeks to stay close to the original look of the technology. Explore further to fully grasp what we mean by these statements:", + }, items: [ - "concepts/components/lifecycle", - "concepts/components/scope", - "concepts/components/callbacks", - "concepts/components/properties", - "concepts/components/children", - "concepts/components/refs", + "concepts/basic-web-technologies/html", + "concepts/basic-web-technologies/css", + "concepts/basic-web-technologies/js", + ], + }, + { + type: "category", + label: "Function Components", + link: { type: "doc", id: "concepts/function-components/introduction" }, + items: [ + "concepts/function-components/properties", + "concepts/function-components/callbacks", + "concepts/function-components/children", + "concepts/function-components/pure-components", + { + type: "category", + label: "Hooks", + link: { type: "doc", id: "concepts/function-components/hooks/introduction" }, + items: [ + "concepts/function-components/hooks/use-state", + "concepts/function-components/hooks/use-reducer", + "concepts/function-components/hooks/use-mut-ref", + "concepts/function-components/hooks/use-node-ref", + "concepts/function-components/hooks/use-effect", + "concepts/function-components/hooks/use-memo", + "concepts/function-components/hooks/use-context", + "concepts/function-components/hooks/custom-hooks", + ], + }, + "concepts/function-components/node-refs", + "concepts/function-components/state", + "concepts/function-components/communication", + "concepts/function-components/generics", ], }, { @@ -58,17 +89,7 @@ module.exports = { }, { type: "category", - label: "Function Components", - items: [ - "concepts/function-components/introduction", - "concepts/function-components/attribute", - "concepts/function-components/pre-defined-hooks", - "concepts/function-components/custom-hooks", - ], - }, - { - type: "category", - label: "wasm-bindgen", + label: "Javascript with Rust", link: { type: "generated-index", title: "wasm-bindgen", @@ -97,6 +118,21 @@ module.exports = { }, items: [ "advanced-topics/how-it-works", + { + type: "category", + label: "Struct Components", + link: { type: "doc", id: "advanced-topics/struct-components/introduction" }, + items: [ + "advanced-topics/struct-components/hoc", + "advanced-topics/struct-components/introduction", + "advanced-topics/struct-components/lifecycle", + "advanced-topics/struct-components/scope", + "advanced-topics/struct-components/callbacks", + "advanced-topics/struct-components/properties", + "advanced-topics/struct-components/refs", + ], + }, + "advanced-topics/children", "advanced-topics/optimizations", "advanced-topics/portals", "advanced-topics/server-side-rendering", @@ -109,12 +145,7 @@ module.exports = { type: "generated-index", title: "Miscellaneous", }, - items: [ - "more/debugging", - "more/css", - "more/testing", - "more/roadmap", - ], + items: ["more/debugging", "more/css", "more/testing", "more/roadmap"], }, { type: "category", @@ -138,5 +169,5 @@ module.exports = { ], }, ], - api: [{type: 'autogenerated', dirName: 'tutorial'}], + api: [{ type: "autogenerated", dirName: "tutorial" }], };