A slot type for Formless and non-formless components

I’m new to Halogen and Halogen Formless and I have a problem writing the Slot type for 2 child components.

The root component in my Halogen app renders two components:

  • A basic component with an Output.
  • And a Formless component with an Output.

As per the Halogen guide, writing the Slot type for the first component worked and my code compiled.

type Slots =    
( nonFormlessComponent  :: forall query. H.Slot query NonFormlessOutput Unit)

However, I’m not sure how to define the type for the Formless Component in the Slots type.

type Slots =    
    ( nonFormlessComponent  :: forall query. H.Slot query NonFormlessOutput Unit
    , formlessComponent     :: ???? <<-----------
    )
    
_nonFormlessComponent = SProxy :: SProxy "nonFormlessComponent"
_formlessComponent = SProxy :: SProxy "formlessComponent"

root :: forall query input output m. MonadAff m => H.Component HH.HTML query input output m
root =
    H.mkComponent
    { initialState
    , render
    , eval: H.mkEval $ H.defaultEval { handleAction = handleAction}
    }
    where
   
    render _ = 
        HH.div_
            [ HH.slot _nonFormlessComponent unit component1 unit (Just <<< HandleNonFormless)
            
            , HH.slot _formlessComponent    unit compoment2 unit (Just <<< HandleFormless)
            -- , HH.slot F._formless           unit component2 unit (Just <<< HandleFormless)

            ]

My formless component:

form :: forall i o. F.Component ContactForm q i o Contact m
form =
        F.component (const formInput)
                    F.defaultSpec
                        { render = rende
                        , handleEvent = handleEvent
                        }

Thanks

1 Like

The Formless Slot and Slot' types are meant to help with this:

If your form is self-contained (it doesn’t raise a message to its parent when submitted), then I believe this type will work:

type Slots =
  ( ...
  , formless :: F.Slot' ContactForm Void
  )

But it’s pretty common to respond to a successful submission by raising the resulting validated type up to the parent, in which case your form spec would be:

form = 
  F.component 
    (const formInput) 
    (F.defaultSpec { render = render, handleEvent = F.raiseResult })

and your slot type would include the output type of your form (because you’re raising it in a message):

type Slots =
  ( ...
  , formless :: F.Slot' ContactForm Contact
  )

and your parent would handle the Formless child component by doing whatever you’d like with the validated result of your form.

I tried using the F.Slot' from the examples directory before posting my question here, but got a compilation error.
I can confirm that I get the same error in try.purescript.org.

Could not match kind
Type -> Type
with kind
Type
import Prelude
import Data.DateTime (DateTime(..))
import Data.Newtype (class Newtype)
import Halogen as H
import Formless as F

type DocumentData = { fileName :: String }

newtype DocumentForm r f = DocumentForm (r
    ( fileName      :: f Void      String  String
    ))

derive instance newTypeDocumentForm :: Newtype (DocumentForm r f) _

data Output = DocumentsDropped (Array String)

type Slots =
  ( someComponent       :: forall query. H.Slot query Output Int
  , formlessComponent   :: F.Slot' DocumentForm  DocumentData
                           ^^^^^^ --- Error here
  )

Do you mind sharing a link to the try.purescript.org reproduction and I can help figure out what’s going on?

Edit: Oh, I think I see it. F.Slot' needs one more argument than you’re giving it – you still need to supply the slot index.

F.Slot' is an eta-reduced type synonym, meaning that the last type variable is being left off of both sides of the =:

-- Halogen slot type
data Slot (query :: Type -> Type) output slot

-- Formless Slot' type
type Slot' form output = Halogen.Slot (F.Query' form) output

-- Identical to
type Slot' form output slot = Halogen.Slot (F.Query' form) output slot

Right now you’re only supplying 2 of the 3 arguments that Slot' requires. It would need to change to this:

type Slots =
  ( someComponent       :: forall query. H.Slot query Output Int
  , formlessComponent   :: F.Slot' DocumentForm  DocumentData Unit
                                                      -- added ^^ 
  )

Here I’m supplying Unit assuming you only have one of these forms, but if you had several of them you could use Int as you’re using for someComponent.

The library really ought to just use the fully-expanded type as it makes it harder to understand. (I’d welcome a PR that expands it!). For example:

- type Slot form query slots msg = H.Slot (Query form query slots) msg
+ type Slot form query slots msg slot = H.Slot (Query form query slots) msg slot

- type Slot' form msg = H.Slot (Query' form) msg
+ type Slot' form msg slot = H.Slot (Query' form) msg slot

Both versions are the same, but the second one is easier to see what’s going on.

2 Likes

This is a clunky explanation of what’s going on with the eta-reduced Slot' – perhaps someone else can give a better / more technical explanation of how that works.

Incidentally – it means that this documentation comment is incorrect (I would also welcome a PR to fix that!).

F.Slot' is an eta-reduced type synonym

Duh, I didn’t know that eta-reduction applies to types. Thanks for enlightening me.
After spending hours, my code finally compiles.

Thanks :slight_smile:

2 Likes