Bolero icon indicating copy to clipboard operation
Bolero copied to clipboard

[Proposal] Reactive Components

Open scottbot95 opened this issue 1 year ago • 0 comments

Overview

Add "functional"/"reactive" components similar to React using something like Hooks for state management as an alternative to using Elmish.

Problem

Elmish is a very system for state management. However it comes with some costs, namely a large amount of boilerplate and significant complexity for deeply nested apps. The later is potentially solved by Nested Program Components (#236), however it looks like that feature never got implemented.

Proposed Solution

Defining components:

Users could define components simply as a function of shape IReactiveComponentContext -> Node ie:

let counter ctx =
   let count, setCount = ctx.useState(0).WithSetter()
   ctx.useEffect(fun _ -> printfn "Counter instantiated!")
   div {
      text $"Count: {count}"
      button {
         on.click (fun _ -> setCount (count + 1))
         "Increment"
      }
   }

Instantiating Components:

Reactive components would then be instantiated by a new rcomp function (similar to comp/ecomp).

Hooks:

  • useState<'T>(initialValue: 'T): ReactiveValue<'T>: Essentially the same as the useState hook from React. The ReactiveValue<'T> type contains the current value for that state object as well as a Set function to update state and trigger a re-render. Additionally ReactiveValue is an IObservable so arbitrary change listeners can be attached. A WithSetter() helper extension can be used to provide a similar experience to React (at the loss of the IObservable object).
  • useEffect(effect: unit -> unit, ?triggers: EffectTrigger list): Again very similar to useEffect from React. Essentially it allows users to provide a callback to be called under certain conditions. Valid triggers are AfterInit, AfterRender, and AfterChange of IObservable

Proof-of-Concept:

I have a proof-of-concept created at https://github.com/scottbot95/Bolero/tree/reactive. Happy to submit a PR if that will aid discussion.

Internally it works by creating a new ReactiveComponent class that inherits Component that manages state through a ReactiveComponentContext object and will call the provided render function with the context object on each Render() call.

scottbot95 avatar Jul 10 '22 20:07 scottbot95