Search code examples
reactjsf#fable-f#elmish

Call component method when model property changes


In my Fable app with Elmish I have a view that uses react-slick and a button that should be able to change the slide number on click:

Fable.Import.Slick.slider
  [ InitialSlide model.SlideNumber
    AfterChange (SlideTo >> dispatch) ]
  children

Button.button
  [ Button.OnClick (fun _ev -> dispatch (SlideTo 5)) ]
  [ str "Go to slide 5" ]

The react component for the slider is defined by react-slick.
The fable wrapper I had to write on my own, so it's not complete because I only defined the properties I need.

module Fable.Import.Slick

open Fable.Core
open Fable.Core.JsInterop
open Fable.Helpers
open Fable.Helpers.React.Props
open Fable.Import.React

type SliderProps =
    | InitialSlide of int
    | AfterChange of (int -> unit)
    interface IHTMLProp

let slickStyles = importAll<obj> "slick-carousel/slick/slick.scss"
let slickThemeStyles = importAll<obj> "slick-carousel/slick/slick-theme.scss"
let Slider = importDefault<ComponentClass<obj>> "react-slick/lib/slider"
let slider (b: IHTMLProp list) c = React.from Slider (keyValueList CaseRules.LowerFirst b) c

So while react-slick defines a property InitialSlide to set the slide that should initially be shown, there's no property to update the slide afterwards. There is a method slickGoTo that should do what I want. But I don't know how or where to call that method while still being compliant with Elmish.

I could imagine that I have to extend the react component and listen to the model property and then call slickGoTo whenever that property changes. But I don't know if that's possible.

So my question is: How can I have a button that changes the slide number on click using slickGoTo that is defined by the component while not hacking the Elmish architecture?


Solution

  • An alternative to storing the reference to the slider object in the model is to use a mutable variable:

    let mutable sliderRef   : Browser.Element option = None
    
    let gotoSlide n = 
        match sliderRef with None ->
        | None -> () 
        | Some slider -> slider?slickGoTo n
    

    the rest is similar:

    type Msg =
    | CurrentSlide of int
    | GotoSlide    of int
    ...
    
    let update msg model =
        match msg with
        | CurrentSlide n -> { model with slideNumber = n      }  , []
        | GotoSlide    n -> gotoSlide n ;                   model, []
    

    Create your slider like this:

    slider 
        [ Ref          (fun slider = sliderRef <- Some slider) 
          AfterChange  (CurrentSlide >> dispatch)
          InitialSlide 0
        ]
        slides