Xavientois 4a14d0f0a4
Add requirement for braces around most props (#1939)
* Limit the properties to literals and brace-enclosed expressions

* Update examples with new syntax

* Update packages/yew-macro/src/props/prop.rs

Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>

* Fix lints and strip braces around single expressions

* Update docs with new prop syntax

* Add some test cases for new syntax

* Ensure all tests are passing

* Clean up missed code

* Update tests

* Update reverted docs

* Revert versioned docs

* Fix optional attributes paragraph

* Remove accidentally added files

* Remove accidentally added french docs

* Update packages/yew-macro/src/props/prop.rs

Co-authored-by: mc1098 <m.cripps1@uni.brighton.ac.uk>

* Fix forgotten braces and test cases

* Revert i18n old docs

* Revert translated docs

* Remove suggested fix in favour of more succinct error message

* Update errors after rebase

* Remove files accidentally added while rebasing

* Fix merge conflicts

Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>
Co-authored-by: mc1098 <m.cripps1@uni.brighton.ac.uk>
2021-07-18 18:54:21 +02:00

7.2 KiB

title description
Pre-defined Hooks The pre-defined Hooks that Yew comes with

use_state

use_state is used to manage state in a function component. It returns a UseState object which Derefs 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.

Example

#[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! {
        <div>
            <button onclick={onclick}>{ "Increment value" }</button>
            <p>
                <b>{ "Current value: " }</b>
                { *counter }
            </p>
        </div>
    }
}

use_ref

use_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.

Example

#[function_component(UseRef)]
fn ref_hook() -> Html {
    let (message, set_message) = use_state(|| "".to_string());
    let message_count = use_ref(|| 0);

    let onclick = Callback::from(move |e| {
        let window = yew::utils::window();

        if *message_count.borrow_mut() > 3 {
            window.alert_with_message("Message limit reached");
        } else {
            *message_count.borrow_mut() += 1;
            window.alert_with_message("Message sent");
        }
    });

    let onchange = Callback::from(move |e| {
        if let ChangeData::Value(value) = e {
            set_message(value)
        }
    });

    html! {
        <div>
            <input onchange={onchange} value={message} />
            <button onclick={onclick}>{ "Send" }</button>
        </div>
    }
}

use_reducer

use_reducer is an alternative to 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 a reducer function and initial state and returns Rc pointing 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.

For lazy initialization, consider using use_reducer_with_init instead.

Example

#[function_component(UseReducer)]
fn reducer() -> Html {
    /// reducer's Action
    enum Action {
        Double,
        Square,
    }

    /// reducer's State
    struct CounterState {
        counter: i32,
    }

    let counter = use_reducer(
        // the reducer function
        |prev: Rc<CounterState>, action: Action| CounterState {
            counter: match action {
                Action::Double => prev.counter * 2,
                Action::Square => prev.counter * prev.counter,
            }
        },
        // initial state
        CounterState { counter: 1 },
    );

   let double_onclick = {
        let counter = counter.clone();
        Callback::from(move |_| counter.dispatch(Action::Double))
    };
    let square_onclick = {
        let counter = counter.clone();
        Callback::from(move |_| counter.dispatch(Action::Square))
    };

    html! {
        <>
            <div id="result">{ counter.counter }</div>

            <button onclick={double_onclick}>{ "Double" }</button>
            <button onclick={square_onclick}>{ "Square" }</button>
        </>
    }
}

use_reducer_with_init

use_reducer but with init argument. The Hook is passed the initial state which is then passed down to init function which initializes the state and returns it. The hook then returns this state.

This is useful for lazy initialization where it is beneficial not to perform expensive computation up-front.

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_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

#[function_component(UseEffect)]
fn effect() -> Html {
    let (counter, set_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));
    
            // 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))
    };

    html! {
        <button onclick={onclick}>{ format!("Increment to {}", counter) }</button>
    }
}

use_effect_with_deps

Sometimes, it's needed to manually define dependencies for use_effect. In such cases, we use use_effect_with_deps.

use_effect_with_deps(
    move |_| {
        // ...
        || ()
    },
    (), // dependents
);

Note: dependents must implement PartialEq.

use_context

use_context is used for consuming contexts in function components.

Example

/// 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<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.
            <Toolbar />
        </ContextProvider<Theme>>
    }
}

/// The toolbar.
/// This component has access to the context
#[function_component(Toolbar)]
pub fn toolbar() -> Html {
    html! {
        <div>
            <ThemedButton />
        </div>
    }
}

/// 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::<Theme>().expect("no ctx found");

    html! {
        <button style={format!("background: {}; color: {};", theme.background, theme.foreground)}>
            { "Click me!" }
        </button>
    }
}