elmish / react

Elmish React extensions for writing SPA and Native apps
https://elmish.github.io/react
Other
104 stars 21 forks source link

Render componentDidMount() #53

Closed bdaniel7 closed 3 years ago

bdaniel7 commented 3 years ago

Hello, I'm trying to build a proof of concept app with Fable and Elmish, app that renders a FullCalendar and the users can interact, drag some "items" onto the calendar. Using https://github.com/Zaid-Ajaj/Fable.React.Flatpickr as inspiration, I was able to make it work, except for the part where the items are made "draggable".

The React example is here: https://codesandbox.io/s/vm45zwmo07?file=/src/index.js

The calendar is used like this:

importDefault "@fullcalendar/react"
let dayGridPlugin : unit =
    importDefault "@fullcalendar/daygrid"

let interactionPlugin : unit =
    importDefault "@fullcalendar/interaction"

FullCalendar.fullCalendar  [
             FullCalendar.Plugins [| dayGridPlugin; interactionPlugin; timeGridPlugin |]
             FullCalendar.HeaderToolbar [ ToolbarSection.Right, "prev,next today"
                                          ToolbarSection.Left, "title"
                                          ToolbarSection.Center, "timeGridDay,timeGridWeek,dayGridMonth"
                                        ]

So, is there a way to render this React function using Fable and Elmish?

componentDidMount() { <---- this one
    let draggableEl = document.getElementById("external-events");
    new Draggable(draggableEl, {
      itemSelector: ".fc-event",
      eventData: function(eventEl) {
        let title = eventEl.getAttribute("title");
        let id = eventEl.getAttribute("data");
        return {
          title: title,
          id: id
        };
      }
    });
  }

Thanks.

MangelMaxime commented 3 years ago

Hello,

I am not sure to under this sentence:

So, is there a way to render this React function using Fable and Elmish?

Elmish.React is only a library responsible to provide a React view to your Elmish program. From there, you can either use Feliz to write your code or Fable.React

If you want to use Fable.React then here should be the solution:

If you want to create a stateful component and customize the componentDidMount methods, then you need to make a class which inherit from Component<'P, 'S>.

As shown here:

https://github.com/fable-compiler/fable-react/blob/e946a81371cce1ec9dde09ffc12ae4a716642caa/src/Fable.React.fs#L44-L58

Then you can write override this.componentDidMount ....

In order to instantiate your component you need to call ofType

https://github.com/fable-compiler/fable-react/blob/1944e9ad30c3175322cd60870a87aa25bd1495a7/src/Fable.React.Helpers.fs#L145-L148

bdaniel7 commented 3 years ago

Thanks. This is my first experience with Fable and the related technologies, so I might be saying stupid things. I don't necessarily need to implement a React component per se - I hope - instead, I need to initialize that new Draggable(..) when the view is rendered. Because the underlying FullCalendar library and modules are already loaded and running.

MangelMaxime commented 3 years ago

I don't necessarily need to implement a React component per se - I hope - instead, I need to initialize that new Draggable(..) when the view is rendered.

Well, if you are using stateful component you have to write it this way. This is how react works not Elmish (it is what your example does) ^^

Also, we newer version of React, you can also use what they call hooks which are more functional than object/class oriented.

In your case, you would want to use useEffect hooks which has a similar behaviour as componentDidMount.

If you are using Fable.React it is available via Hooks.useEffect Source and if you use Feliz it will be React.useEffect Source

See:

bdaniel7 commented 3 years ago

I was able to make it work, but I find there is a lot of hassle just to integrate some complex components, so I will leave this at the proof of concept level.

type OrderListComponent (props) =
    inherit Component<OrderListProps, obj>(props)

    override this.componentDidMount () =
        props.onComponentDidMount()

    override this.render() =
         div [prop.id "orders-to-drag"
              prop.children [
             for order in props.orders do
                      Html.div [ prop.className "card"
                        prop.children [
                         Html.header [ prop.className ("card-header" + getClassByAvailable order.IsFullAvailable)
                                prop.children [
                                     Html.p [
                                            prop.className "fc-event card-header-title o-drag"
                                            prop.title order.CustOrderId
       ....

And in view

let view (dispatch: Msg -> unit) (model: Model) =
  let inline ordersComponent props = ofType<OrderListComponent, _, _> props []

  match model.Orders with
              | Success orders -> ordersComponent {
                                                 orders = orders
                                                  onComponentDidMount = (fun () -> dispatch OrderListRendered)
                                                 }

Maybe in the future it will be possible to use JS/React code directly. Thank you again.

MangelMaxime commented 3 years ago

You probably should not add let inline ordersComponent props = ofType<OrderListComponent, _, _> props [] in the view function but outside, to make it re-usable.

but I find there is a lot of hassle just to integrate some complex components, so I will leave this at the proof of concept level.

Well, you could have written the same code as in the JavaScript example, this is what I tried to explain you.

Maybe in the future it will be possible to use JS/React code directly.

This is already possible Fable Docs - Call JS from Fable.

This is how we can leverage the JavaScript ecosystem like React and not re-implement everything.