Fork me on GitHub
#re-frame
<
2019-10-25
>
Bobbi Towers06:10:48

Hi, I'm learning re-frame by building a fairly complex audio application, and am looking for examples for reference/inspiration. I'm not having any particular issues but just curious to see how folks deal with the Web Audio API, and want to learn how to do everything "properly".

kenny23:10:25

Thoughts on how to create a debounced subscription?

kenny23:10:52

Interesting

mikethompson23:10:53

@kenny the UI is only redrawn once every animation frame. subscriptions are calculated at that frequency too. Do you need to debounce more than that?

mikethompson23:10:59

For example, are you de-bouncing server interactions, in response to user input changes?

kenny23:10:48

Unfortunately, I think so. I have a s/valid? call in a subscription. That subscription is getting updated every time the user types in an input box causing significant lag while typing.

mikethompson23:10:41

@kenny So perhaps the other way of saying this is: I need to debounce the server requests?

kenny23:10:57

No. It's all local.

kenny23:10:14

The subscription is pulling in a large map and validating it against a spec. The user's input is only changing one small part of the large map.

kenny23:10:09

When the user types in an input, it causes that subscription to run every single time a letter is added/removed.

mikethompson23:10:23

Reminder: subscription are simply a way to deliver data (a materialized view of data) to views. They are reactive. When the underlying data in app-db changes, data will flow through the subscription. So why is the subscription doing validation?

kenny23:10:01

To enable/disable a button & show an invalid message.

mikethompson23:10:10

The event handler putting data INTO app-db should be validating.

kenny23:10:36

You'd have the same problem though.

mikethompson23:10:31

So: 1. each time the user types something in 2. an event is dispatched 3. an event handler updates app-db 4. causing subscriptions to fire?

kenny23:10:07

Correct. Somewhere the "expensive" s/valid? call needs to occur.

mikethompson23:10:20

IMO, any validation should really be happening in step 3.

mikethompson23:10:28

As you say, perhaps we are just moving the problem,

kenny23:10:28

Technically the valid? is a projection of the data in the app-db. It seems clean to have it in a subscription.

lilactown23:10:31

I think in this case I would debounce the event, and have the input be uncontrolled

lilactown23:10:47

or use a local ratom that wasn’t tied to the validation

mikethompson23:10:07

valid? is about the state in the app

mikethompson23:10:22

When you update the app-db you are changing that sate

kenny23:10:31

Right, but being valid depends on context.

mikethompson23:10:43

Hmm. What context?

mikethompson23:10:54

Other data in app-db?

kenny23:10:58

The view.

lilactown23:10:28

there are two contexts: - UI state - state of application you want the user to be able to put garbage in the UI, and validate it before it populates the application state

kenny23:10:37

It gets complicated to have it in every event handler. We have hundreds of inputs that would all need to be debounced and include the s/valid? call.

kenny23:10:14

No, sorry. Data going into the DB not passing the Spec is ok.

mikethompson23:10:26

So the key consideration here is that valid? is expensive to compute

✔️ 4
mikethompson23:10:51

And each user imput processed requires that is be recomputed

lilactown23:10:55

even still, updating the app-db in this case is expensive because of the validation

lilactown23:10:32

you can easily build a debounced-input component that will track its UI state locally and call the on-change handler only once per 300ms or so

lilactown23:10:44

from the outside, it’s pure but you get your perf benefits

kenny23:10:59

Hmm, interesting.

lilactown23:10:09

I know because I just did this :<

mikethompson23:10:22

So you are debouncing the changes to app-db

mikethompson23:10:06

But I'd still be thinking carefully about the expensive validation process.

mikethompson23:10:27

Is there some way to make that less expensive More incremental

mikethompson23:10:27

Then, this whole problem goers away

mikethompson23:10:43

^^ slack reordered my messages, have corrected

kenny23:10:57

It's tricky. Probably is the answer but would likely require more engineering time.

kenny23:10:25

There's lots of and type predicates in this validation that requires a larger "scope" to correctly validate.

kenny23:10:53

So it wouldn't be as easy as, say, attaching a Spec to a single :input.

kenny23:10:46

Really the tradeoff is in UX, I suppose. You either provide the user an immediate feedback on valid/invalid or you do it on click.

kenny23:10:40

Not all UIs can do the latter though.

lilactown23:10:54

I think it’s generally accepted practice to have complex UIs that track their state locally and then populate up on debounce/blur/etc. for performance or UX reasons.

mikethompson23:10:26

Hard to know what to do without knowing your application but you could do: 1. Update the app-db straight away 2. signal that you want to do a validation in 300ms time Which is effectively debouncing the expensive valid? check

mikethompson23:10:04

But updating app-db straight away

lilactown23:10:08

problem is that you still need to do the work of debouncing the signal to validate or else you still end up chugging

mikethompson23:10:14

Dunno if that would work

lilactown23:10:33

the use case I had recently was filtering a huge table of complex, calculated data. there had been several attempts to address this with various methods, one of which was firing an event to do the re-calculating <n>ms after updating the db with the filter. this still caused lag, because the CPU from the re-calc events would end up blocking the event queue

lilactown23:10:17

the best way we solved it was with the debounced-input component idea above.

lilactown23:10:06

1. it’s quite easy to debounce the input using the google closure library, whereas debouncing a dispatch required us to do side-effects directly in our event handler 2. we can now remove all of the logic spread across our event handlers in dealing with this perf problem and instead isolate it to this component that is a drop-in replacement for an :input tag

kenny23:10:02

Interesting ideas. I'm going to go down the path of the debounced-input since we already have an input component and this is really easy to throw in there. Sounds like it'll just work 🙂 Thank you for the help!