0

I'm building a simple video player component using purescript-halogen. The component is supposed to show a blank div with only an input button for the user to select a local file which will then act as a source URL for the video element.

I completed a working model in plain javascript and wanted to port it to purescript/halogen. I got the purescript version to compile but the web console gives me an error message Uncaught TypeError: component.initialState is not a function and points me to this MDN reference.

This would suggest an issue with how I defined my initialState function but it's fairly standard:

component :: forall q i o m. MonadAff m => H.Component q i o m
component =
  H.mkComponent
    { initialState
    , render
    , eval: H.mkEval $ H.defaultEval { handleAction = handleAction }
    }

type State =
  { videoUrl :: Maybe String }

initialState :: forall i. i -> State
initialState _ =
  { videoUrl : Nothing
  }

Is it possible that the function is called at the wrong time during its lifecycle, hence making it undefined?

The code for the component is hosted on this Github gist. And I've been following this Github issue as reference.

My package.json :


  "private": true,
  "devDependencies": {
    "parcel": "1.12.3",
    "purescript": "^0.14.0",
    "spago": "^0.19.1"
  },
  "scripts": {
    "build": "spago build",
    "test": "spago test",
    "serve": "parcel dev/index.html --open",
    "build-prod": "mkdir -p prod && cp dev/index.html prod/ && rm -rf dist && spago bundle-app --to prod/index.js && parcel build prod/index.html"
  },
  "dependencies": {
    "node": "^15.12.0"
  }
}

I compiled using purescript v0.14.0, spago v0.20.0, node v16.3.0 and tested on Firefox v89.0 in Ubuntu 20.04.2 LTS.

  • Can you show the rest of the program? How is that component then used? – Fyodor Soikin Jun 10 '21 at 11:30
  • @FyodorSoikin I've given the full program in my answer, I guess in this instance the error message was misleading. The problem was with the render function rather than the initialState function. – Newton Migosi Jun 10 '21 at 11:42
  • the error in this context seems very strange anyway - my best guess is that the value somehow fires the changed-event at a time where the component is in some unknown state ... if this is really the case I think this should be considered a bug in Halogen - is there anyway you can get a really short version producing this error? Maybe just the file-input with a handler only logging or something and this value? – Random Dev Jun 11 '21 at 17:06

1 Answers1

0

Seems the bug is from a pesky line in the code:

HH.input 
  [ ...
  , HP.value "Select file"
  ...]

According to MDN, the value property of an input element is used to hold the path to the selected files. Therefore, setting it during element creation is not a well defined action.

The final code (which runs as expected) is:

module App.Video (component) where

import DOM.HTML.Indexed.InputAcceptType (InputAcceptType, InputAcceptTypeAtom(..))
import Data.Foldable (traverse_)
import Data.Maybe (Maybe(..))
import Data.MediaType (MediaType(..))
import Data.Traversable (for_)
import Effect.Aff.Class (class MonadAff)
import Halogen as H
import Halogen.HTML as HH
import Halogen.HTML.Events as HE
import Halogen.HTML.Properties as HP
import Prelude (Unit, bind, const, discard, pure, unit, ($), (=<<), (>>=))
import Web.Event.Event as Event

import Web.File.File as File
import Web.File.FileList as FileList
import Web.File.FileReader.Aff as FileReaderAff
import Web.HTML.HTMLInputElement as InputElement

data Action = HandleFileUpload Event.Event

type State =
  { videoUrl :: Maybe String }

component :: forall q i o m. MonadAff m => H.Component q i o m
component =
  H.mkComponent
    { initialState
    , render
    , eval: H.mkEval $ H.defaultEval { handleAction = handleAction }
    }

initialState :: forall input. input -> State
initialState inp =
  { videoUrl : Nothing
  }

render :: forall m slots. State -> H.ComponentHTML Action slots m
render state =
    case state.videoUrl of
        Nothing -> blank_player
        Just url -> video_player url

supported_formats :: InputAcceptType
supported_formats = 
    HP.InputAcceptType
        [ AcceptMediaType (MediaType "video/mp4")
        , AcceptMediaType (MediaType "video/webm")
        ]

blank_player :: forall w. HH.HTML w Action
blank_player = 
    HH.div_ 
        [ HH.span_ [HH.text "Choose file to upload"]
        , HH.input
            [ HP.type_ HP.InputFile
            , HP.accept supported_formats
            , HE.onChange HandleFileUpload
            ]
    ]

video_player :: forall w i. String -> HH.HTML w i
video_player url = 
    HH.div_ [
        HH.video [HP.src url] []
    ]

handleAction :: forall m slots o. MonadAff m => Action -> H.HalogenM State Action slots o m Unit 
handleAction = case _ of
  HandleFileUpload ev → do
    traverse_ handleFileUpload (InputElement.fromEventTarget =<< Event.target ev)

handleFileUpload :: forall m slots o. MonadAff m => InputElement.HTMLInputElement -> H.HalogenM State Action slots o m Unit 
handleFileUpload inputEl = do
  H.liftEffect (InputElement.files inputEl) >>= traverse_ \files ->
    for_ (FileList.item 0 files) \file → do
      video_url ← H.liftAff $ FileReaderAff.readAsDataURL (File.toBlob file)
      H.modify_ (const { videoUrl : Just video_url})
      pure unit