Skip to content

Proposal: Hooks API #1026

@ZainlessBrombie

Description

@ZainlessBrombie

Is your feature request related to a problem? Please describe.
Currently component classes are required to have (stateful) components. They contain state management and lifecycle methods, requiring a message to be sent back for changes. This process can be simplified with functional components that use hooks.

Describe the solution you'd like
Introducing: hooks. Hooks are used in React to enable components, complex or simple, whose state is managed by the framework. This enables developers to progress faster and avoid some pitfalls. The hooks API is described at https://reactjs.org/docs/hooks-reference.html

Describe alternatives you've considered
Hooks aren't strictly necessary, neither for react nor yew, so they are optional. But they do have clear advantages, not least being easier to think about than messages.

Implementation
I propose (and have implemented) the following api:

use_state is the most basic hook. It outputs a state, provided by initial_state initially, and a function to update that state. Once the set_state method is called, the component rerenders with the call to use_state outputting that new state instead of the initial state.

pub fn use_state<T, F>(initial_state_fn: F) -> (Rc<T>, Box<impl Fn(T)>)
where
    F: FnOnce() -> T,
    T: 'static,
{
 // ...
}

use_effect lets you initialize a component and recompute if selected state changes. It is provided a closure and its arguments. If any of the arguments, which need to implement ==, change, the provided closure is executed again with those arguments and not executed again until the arguments change. There are use_effect1 through use_effect5 for number of arguments, while not giving any arguments executes the closure only once (it is debatable whether it should execute once or always in that case)

pub fn use_effect1<F, Destructor, T1>(callback: Box<F>, o1: T1)
where
    F: FnOnce(&T1) -> Destructor,
    Destructor: FnOnce() + 'static,
    T1: PartialEq + 'static,
{
  // ...
}

use_reducer2 is an advanced use_state function that lets you externalize computations of state change and initial state. It is given a function that combines an action and the previous state, as well as an initial state argument and a function that computes it into the initial state.
It returns the current state and a dispatch(Action) function to update the state.

pub fn use_reducer2<Action: 'static, Reducer, State: 'static, InitialState, InitFn>(
    reducer: Reducer,
    initial_state: InitialState,
    init: InitFn,
) -> (Rc<State>, Box<impl Fn(Action)>)
where
    Reducer: Fn(Rc<State>, Action) -> State + 'static,
    InitFn: Fn(InitialState) -> State,
{
  // ...
}

*use_reducer1 is a variaton that takes the initial state directly instead of a compute function to compute it.

pub fn use_reducer1<Action: 'static, Reducer, State: 'static>(
    reducer: Reducer,
    initial_state: State,
) -> (Rc<State>, Box<impl Fn(Action)>)
where
    Reducer: Fn(Rc<State>, Action) -> State + 'static,
{
  // ...
}

use_ref lets you have e RefCell of a state in your component that you can update yourself as need be

pub fn use_ref<T: 'static, InitialProvider>(initial_value: InitialProvider) -> Rc<RefCell<T>>
where
    InitialProvider: FnOnce() -> T,
{
  // ...
}

Reference implementation (needs to be cleaned up): https://pastebin.com/rWMn7YBX
Example uses follow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions