rebo / seed-quickstart-hooks

Example of how react style hooks might integrate with Seed.
5 stars 0 forks source link

useEffect, useContext, and other React utility apis. #3

Open rebo opened 4 years ago

rebo commented 4 years ago

Other than use_state React has a number of other utility apis that make working with their Hooks much more productive.

I have implemented most of them in some form however I want to really pin down what to include in a useful Seed api.

Here are the most commonly used React ones are.

useRef - is basically what we have with use_state basically synchronous updating of state with no forced re-rendering.

useEffect - accepts a block that runs after the next render. This may be useful to programatically update certain items or trigger a re-render after a timeout. It ensures that your current component has been rendered.

useContext allows nested state within a nested components. i.e. component A has sub-views B and C. component A defines a context object, and component B and C has access to it via useContext.

useReducer creates a dispatch object that messages can be passed to. This then returns and updates the relevant state. This is as opposed to mutating state directly.

useCallback / useMemo Memoizes a value and only updates it if a dependency changes.

Of the above useMemo and useReducer would be most useful.

The use case for use_memo would be to cache an expensive value and only update it on request. For instance you could have a function that does a fetch request to update profile data:

let ready_bool.get()  = use_state(|| false);
let profile = use_memo( ready_bool.get(), || {
     fetch_profile_async(ready_bool)
});

div![profile]

This pattern would display a dummy profile until ready_bool.get() returns true in which case fetch_profile_async will update the memo with the loaded profile.

Another example where use_memo might be useful is when Seed evaluates every single view function which could include evaluation hundreds of divs![] and other node macros in a complex view. Using use_memo you would only need to evaluate this once and the Node tree can then be memoized. i.e.

use_memo(re_render, || {  ..lots and lots of divs and expensive node macros... } );

This doesn't save any view diffing but does save the cost of creating the virtual dom in the first place.

The case for use_reducer means that logic can be separated from the view somewhat. It is like a mini seed message-update-view cycle. i.e. the classic counter example would be something like:

enum ButtonMsg {
    Increment,
    Decrement,
}
fn reducer<T>(state: &mut T, msg:ButtonMsg){
    match msg {
      ButtonMsg::Increment => *state +=1,
      ButtonMsg::Decrement => *state -=1,
    }
}

fn my_button() -> Node<Msg> {
    let count = use_reducer(reducer, || 3);
    div![
        button![
            "-",
            mouse_ev(Ev::Click, move |_| {
                count.dispatch(ButtonMsg::Decrement);
                Msg::NoOp
            }),
        ],
        count.get().to_string(),
        button![
            "+",
            mouse_ev(Ev::Click, move |_| {
                count.dispatch(ButtonMsg::Increment);
                Msg::NoOp
            }),
        ],
    ]
}

This lets one keep TEA style on a per view function basis and is more suited if there is more complex logic needed than a single state value. The view function is still self contained as a component and can be used wherever.

MartinKavik commented 4 years ago

useRef - we proved it's redundant.

useEffect - "accepts a block that runs after the next render" - we already have orders.on_next_render(|timestamp| ..) so maybe we can explore some simple alternatives for Seed. I can imagine it can be useful for animations.

useContext - "allows nested state within a nested components. i.e. component A has sub-views B and C. component A defines a context object, and component B and C has access to it via useContext." - it sounds pretty complicated and error-prone.

useReducer - I don't know if I like it. We wanted simple stateful components and boom, we have a stateful component with own React/Elm architecture and two message types. Imagine a question: "So... I want to write a component - should I write it in Seed/Elm architecture or React/Seed/Elm architecture or use Seed's use_state (which is basically React's use_ref) or write a Rust/Typescript WebComponent?" It would be tough question for experienced developer who knows Seed, React, Elm, Rust, Javascript, Typescript and WebComponents, beginners will run away.

useCallback / useMemo -

rebo commented 4 years ago

Yes all good points and keeping it simple is definitely a good strategy.

It's worth considering the above only in so far as they are part of React's core Hook api and some prior users of React may want to reach for them.

MartinKavik commented 4 years ago

"It's worth considering the above only in so far as they are part of [X]'s core [Y] api and some prior users of [X] may want to reach for them."

rebo commented 4 years ago

agreed!