typeerrorelmelm-architecture

Type Mismatch - 1st argument to sandbox is not what I expect


I am trying to add subscriptions as I have a dropdown, this helps ensure that the dropdowns automatically close when you click outside of them. On doing so, I had to change the model as well as my update.

This link (will take you to the Elm Bootstrap site) is the dropdown I am working with which is using Bootstrap 4.

Error I am getting

The 1st argument to sandbox is not what I expect:

295| Browser.sandbox 296|> { init = initialModel 297|>
, update = update 298|> , view = view 299|> }

This argument is a record of type:

{ init : ( Model, Cmd Msg )
, update : Msg -> Model -> ( Model, Cmd Msg )
, view : Model -> Html Msg
}

But sandbox needs the 1st argument to be:

{ init : ( Model, Cmd Msg )
, update : Msg -> ( Model, Cmd Msg ) -> ( Model, Cmd Msg )
, view : ( Model, Cmd Msg ) -> Html Msg
}

Alias Model

type alias Model =
    { currentNumber : Int, clicks : Int, outputList : List(String), uniqueValues : Dict Int Int, firstNumber : String, secondNumber : String, myDropState : Dropdown.State, items : List String, selectedItem : String, dictKeyToRemove : String,
    modalVisibility : Modal.Visibility  }

Initial Model

initialModel : (Model, Cmd Msg)
initialModel =
    ({ currentNumber = 0, clicks = 0, outputList = [""], uniqueValues = Dict.empty, firstNumber = "", secondNumber = "", myDropState = Dropdown.initialState, items = ["Small", "Medium", "Large"], selectedItem = "Small", dictKeyToRemove = "",
    modalVisibility = Modal.hidden }, Cmd.none)

Main

main : Program () Model Msg
main =
    Browser.sandbox
        { init = initialModel           
        , update = update      
        , view = view   
        }

Subscriptions

subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.batch
        [ Dropdown.subscriptions model.myDropState DropMsg ]

Update

update : Msg -> Model -> ( Model, Cmd Msg)
update msg model =
    case msg of   
        DropMsg state ->
            ({model | myDropState = state }, Cmd.none)

I am not sure what I am missing at this point, I have tried changing the argument with no luck.


Solution

  • Browser.sandbox will create a simple and very limited program. The dropdown requires capabilities beyond that, namely subscriptions, which means you need to use either Browser.element or Browser.document instead.

    The type of Browser.element is:

    element :
        { init : flags -> ( model, Cmd msg )
        , view : model -> Html msg
        , update : msg -> model -> ( model, Cmd msg )
        , subscriptions : model -> Sub msg
        }
        -> Program flags model msg
    

    Compared to Browser.sandbox:

    sandbox :
        { init : model
        , view : model -> Html msg
        , update : msg -> model -> model
        }
        -> Program () model msg
    

    There are three differences here:

    1. init takes an argument, flags, which can be anything and will be interpreted by the runtime according to its type. For your purpose just using () should be enough (which is essentially what sandbox does), but see the flags section of the guide for more details.

    2. init and update returns ( model, Cmd msg ) instead of just model. This is the root cause of your error, because you have update and init functions which return ( model, Cmd msg ) as element would expect, but try to feed them to sandbox. This makes the compiler unhappy, because it thinks that model should be ( Model, Cmd msg ) instead of just Model.

    3. element expects an additional subscriptions function, which you have defined but currently aren't doing anything with since sandbox doesn't accept it.

    Putting this all together, substituting the following main function should work for you:

    main : Program () Model Msg
    main =
        Browser.element
            { init = \() -> initialModel           
            , update = update      
            , view = view
            , subscriptions = subscriptions  
            }