Skip to content

Commit

Permalink
Fix debounce issues with react forms 🐛 #575
Browse files Browse the repository at this point in the history
  • Loading branch information
Freymaurer committed Nov 21, 2024
1 parent ed19073 commit f7edf1f
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 58 deletions.
34 changes: 10 additions & 24 deletions src/Client/Helper.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let log (a) = Browser.Dom.console.log a

let logw (a) = Browser.Dom.console.warn a

let logf a b =
let logf a b =
let txt : string = sprintf a b
log txt

Expand Down Expand Up @@ -54,12 +54,12 @@ let debounce<'T> (storage:DebounceStorage) (key: string) (timeout: int) (fn: 'T
| _ -> ()

// Create a new timeout and memoize it
let timeoutId =
Fable.Core.JS.setTimeout
(fun () ->
let timeoutId =
Fable.Core.JS.setTimeout
(fun () ->
storage.Remove(key) |> ignore
fn value
)
)
timeout
storage.Add(key, timeoutId, fun () -> fn value)

Expand All @@ -71,36 +71,22 @@ let debouncel<'T> (storage:DebounceStorage) (key: string) (timeout: int) (setLoa
| _ -> setLoading true; ()

// Create a new timeout and memoize it
let timeoutId =
Fable.Core.JS.setTimeout
(fun () ->
let timeoutId =
Fable.Core.JS.setTimeout
(fun () ->
match storage.TryGetValue key with
| Some _ ->
storage.Remove(key) |> ignore
setLoading false
fn value
| None ->
setLoading false
)
)
timeout
storage.Add(key, timeoutId, fun () -> fn value)

let newDebounceStorage = fun () -> DebounceStorage()

let debouncemin (fn: 'a -> unit, timeout: int) =
let ids : ResizeArray<int> = ResizeArray()
fun (arg: 'a) ->
for id in ids do
Fable.Core.JS.clearTimeout id
ids.Clear()
let timeoutId =
Fable.Core.JS.setTimeout
(fun () ->
fn arg
)
timeout
ids.Add timeoutId

let throttle (fn: 'a -> unit, interval: int) =
let mutable lastCall = System.DateTime.MinValue

Expand All @@ -125,7 +111,7 @@ let throttleAndDebounce(fn: 'a -> unit, timespan: int) =
fn arg
| _, Some id -> Fable.Core.JS.clearTimeout id
| _, None -> ()
let timeoutId =
let timeoutId =
Fable.Core.JS.setTimeout
(fun () ->
fn arg
Expand Down
20 changes: 20 additions & 0 deletions src/Client/React.useListener.fs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,28 @@ module Impl =
Some (jsOptions<RemoveEventListenerOptions>(fun o -> o.capture <- true))
else None)

type React =

static member inline useDebouncedCallback<'A>(func: 'A -> unit, ?delay: int) =
let timeout = React.useRef(None)
let delay = defaultArg delay 500

React.useCallback(
(fun (arg: 'A) ->

let later = fun () ->
timeout.current |> Option.iter(Fable.Core.JS.clearTimeout)
func arg

timeout.current |> Option.iter(Fable.Core.JS.clearTimeout)
timeout.current <- Some(Fable.Core.JS.setTimeout later delay)
),
[| func; delay |]
)

[<Erase;RequireQualifiedAccess>]
module React =

[<Erase>]
type useListener =
static member inline on (eventType: string, action: #Event -> unit, ?options: AddEventListenerOptions, ?dependencies: obj []) =
Expand Down
59 changes: 25 additions & 34 deletions src/Client/SharedComponents/Metadata/Forms.fs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ module private Helper =
modal.active
prop.children [
Daisy.modalBackdrop []
Daisy.modalBox.form [
Daisy.modalBox.div [
cardFormGroup [
readOnlyFormElement(person.FirstName, "Given Name")
readOnlyFormElement(person.LastName, "Family Name")
Expand Down Expand Up @@ -410,11 +410,7 @@ type FormComponents =
let loading, setLoading = React.useState(false)
let isValid, setIsValid = React.useState(true)
let ref = React.useInputRef()
let debounceSetter = React.useMemo(
(fun () ->
debouncemin ((fun s -> setValue s; setLoading false), 1000)),
[||]
)
let debounceSetter = React.useDebouncedCallback(setValue)
React.useEffect(
(fun () ->
if ref.current.IsSome then
Expand All @@ -424,7 +420,7 @@ type FormComponents =
),
[|box value|]
)
let onChange = React.useMemo(fun () ->
let onChange =
fun (e: string) ->
if validator.IsSome then
let isValid = validator.Value.fn e
Expand All @@ -435,7 +431,6 @@ type FormComponents =
else
setLoading true
debounceSetter e
)
Html.div [
prop.className "grow not-prose"
prop.children [
Expand Down Expand Up @@ -588,20 +583,23 @@ type FormComponents =

[<ReactComponent>]
static member PersonInput(input: Person, setter: Person -> unit, ?rmv: MouseEvent -> unit) =
log ("rerender", input)
let nameStr =
let fn = Option.defaultValue "" input.FirstName
let ln = Option.defaultValue "" input.LastName
let mi = Option.defaultValue "" input.MidInitials
let x = $"{fn} {mi} {ln}".Trim()
if x = "" then "<name>" else x
let orcid = Option.defaultValue "<orcid>" input.ORCID
let createPersonFieldTextInput(field: string option, label, personSetter: string option -> unit) =
let updatePersonField =
fun s personSetter input ->
let s = if s = "" then None else Some s
personSetter input s
input |> setter
let createPersonFieldTextInput(field: string option, label, personSetter: Person -> string option -> unit) =
FormComponents.TextInput(
field |> Option.defaultValue "",
(fun s ->
let s = if s = "" then None else Some s
personSetter s
input |> setter),
(fun s -> updatePersonField s personSetter input),
label
)
let countFilledFieldsString (person: Person) =
Expand All @@ -628,29 +626,30 @@ type FormComponents =
// content
[
Helper.cardFormGroup [
createPersonFieldTextInput(input.FirstName, "First Name", fun s -> input.FirstName <- s)
createPersonFieldTextInput(input.LastName, "Last Name", fun s -> input.LastName <- s)
createPersonFieldTextInput(input.FirstName, "First Name", fun input s -> input.FirstName <- s)
createPersonFieldTextInput(input.LastName, "Last Name", fun input s -> input.LastName <- s)
]
Helper.cardFormGroup [
createPersonFieldTextInput(input.MidInitials, "Mid Initials", fun s -> input.MidInitials <- s)
createPersonFieldTextInput(input.MidInitials, "Mid Initials", fun input s -> input.MidInitials <- s)
FormComponents.PersonRequestInput(
input.ORCID,
(fun s ->
let s = if s = "" then None else Some s
input.ORCID <- s
input |> setter),
(fun s -> setter s),
"ORCID"
input |> setter
),
(fun s -> setter s),
"ORCID"
)
]
Helper.cardFormGroup [
createPersonFieldTextInput(input.Affiliation, "Affiliation", fun s -> input.Affiliation <- s)
createPersonFieldTextInput(input.Address, "Address", fun s -> input.Address <- s)
createPersonFieldTextInput(input.Affiliation, "Affiliation", fun input s -> input.Affiliation <- s)
createPersonFieldTextInput(input.Address, "Address", fun input s -> input.Address <- s)
]
createPersonFieldTextInput(input.EMail, "Email", fun input s -> input.EMail <- s)
Helper.cardFormGroup [
createPersonFieldTextInput(input.EMail, "Email", fun s -> input.EMail <- s)
createPersonFieldTextInput(input.Phone, "Phone", fun s -> input.Phone <- s)
createPersonFieldTextInput(input.Fax, "Fax", fun s -> input.Fax <- s)
createPersonFieldTextInput(input.Phone, "Phone", fun input s -> input.Phone <- s)
createPersonFieldTextInput(input.Fax, "Fax", fun input s -> input.Fax <- s)
]
FormComponents.OntologyAnnotationsInput(
input.Roles,
Expand All @@ -676,26 +675,18 @@ type FormComponents =

[<ReactComponent>]
static member DateTimeInput (input_: string, setter: string -> unit, ?label: string) =
let loading, setLoading = React.useState(false)
let ref = React.useInputRef()
let debounceSetter = React.useMemo(
(fun () ->
debouncemin ((fun s -> setter s; setLoading false), 1000)),
[||]
)
let debounceSetter = React.useDebouncedCallback (fun s -> setter s)
React.useEffect(
(fun () ->
if ref.current.IsSome then
setLoading false
ref.current.Value.value <- input_
),
[|box input_|]
)
let onChange = React.useMemo(fun () ->
let onChange =
fun (e: string) ->
setLoading true
debounceSetter e
)
Html.div [
prop.className "grow"
prop.children [
Expand Down

0 comments on commit f7edf1f

Please sign in to comment.