mikesol / purescript-deku

A PureScript web UI framework
https://purescript-deku.surge.sh/
Apache License 2.0
123 stars 12 forks source link

Any chance of an example showing how to debounce rendering an input #115

Open jerbaroo opened 5 months ago

jerbaroo commented 5 months ago

Love the library, chose it for a recent project I am working on. Struggling to figure some things out, but getting there.

I am a bit stumped on how to debounce the rendering of error text for this input component. The code should be fairly illustrative on what I am trying to accomplish:

input _ initialMay onKeyup = Deku.do
  -- We save the raw input to calculate error messages.
  setValueMay /\ valueMay <- useState initialMay
  D.div
    [ ]
    [ D.input
        ( [ DA.value $ valueMay <#> maybe "" repr
          , DL.keyup_ $ \event -> Event.withTargetValue (toEvent event) \s -> do
              setValueMay $ Just s
              onKeyup s -- Should be executed without any debounce delay.
          ]
            <> flip (maybe []) initialMay \v -> [ DA.value_ v ]
        )
        []
    , D.div
        [ DA.klass_ "input-error" ]
        [ valueMay <#~> case _ of 
            -- Should only be rendered after a debounce period.
            Nothing -> text_ ""
            Just value -> case parse value of
              Right (_ :: a) -> text_ ""
              Left error ->
                -- Only show the error if input is non-empty.
                text_ $ if String.length value == 0 then "" else error
        ]
    ]
mikesol commented 4 months ago

Yes, I can put this together & thanks for requesting it!

mikesol commented 2 weeks ago

Thanks for your patience: deku went through a big rewrite for this sort of thing recently & I can now report how to go about this.

You'll want to create a custom with combinator, ie withDebounce. You can build it off of withDelay (https://github.com/mikesol/purescript-hyrule/blob/main/src/FRP/Event/Time.purs) and then do ie under Op (withDebounce nMilliseconds) setValueMay $ Just s. The hyrule tests use this pattern quite a bit: https://github.com/mikesol/purescript-hyrule/blob/main/test/Main.purs, check withTime in there for example.

It's a fun little exercise to create a withDebounce from withDelay. GIve it a try! If it's too challenging, let me know and I can help out. Here are a couple hints.

  1. The signature will be withDebounce :: forall a. Int -> Op (Effect Unit) a -> Op (Effect Unit) a.
  2. It will use withDelay, so some intermediary function will need to have the signature: Op (Effect Unit) a -> Op (Effect Unit) (Either TimeoutId (Tuple TimeoutId a)). This feels a little backwards when you look at it because it looks like it's producing the type of withDelay but it's actually consuming it. This is one of the neat things about Op.