concur: Loading 1100 clickEl's is very slow
I have a list of 1100 users and I tried to make a button
for each one (using clickEl
) that returned the userId to a parent element, which I would use to jump to a new route. But it’s extremely slow loading that many click elements. It could handle a couple hundred well, but approaching 1000 the performance really plummets. Once they load up, it’s pretty responsive, but loading them take at least 10 seconds.
For now I just made a fake button function that uses a href
instead of Concur’s events machinery:
linkButton :: String -> String -> Widget HTML ()
linkButton label url = el_ E.a [ A.href . JS.pack $ url ]
$ el E.button [ A.class_ "pure-button" ] [ text label ]
which is really fast and fine for now since I’m just jumping to an URL, but at some point I’m sure it would be nice/essential to be able to use clickEl
’s or some other event listener for a big list.
About this issue
- Original URL
- State: open
- Created 7 years ago
- Comments: 20 (14 by maintainers)
Commits related to this issue
- Add clickList benchmark [https://github.com/ajnsit/concur/issues/6] — committed to ajnsit/concur-benchmarks by ajnsit 7 years ago
Hah well, it’s really a new feature, and I’m glad it solved your problem! The changes are all here - https://github.com/ajnsit/concur/commit/78ac2b224d1d2454ad4ddb014bc48950cc46481a.
Gist
Basically I added support for blocking IO to Concur. Then used it for a very common use case where async io was a bottleneck.
Background
All actions like
liftIO
andliftSTM
are run async, and are effectively full widgets.A common pattern with a lot of widgets (like
button
) was to useliftSTM
to procure aNotify
(a very quick operation), and then used the Notify to link the view click and the handler. This means that a singleButton
was effectively a sequence of two widgets,liftSTM
and then the actual button UI.Also all widgets are encapsulated and run async, and any changes to the widget tree causes a full dom render cycle (albeit with virtual-dom diffing).
Problem
So when a
button
is first added to the widget tree, it actually renders theliftSTM
widget (i.e. no UI), which quickly finishes, and then renders the actual button UI.When you add two
button
s in quick succession, the firstliftSTM
widget renders and finishes, then the secondliftSTM
widget renders and finishes, and then the firstbutton
UI renders, and then the secondbutton
UI renders. The order can vary depending on how long it takes for aliftSTM
to finish.You see where this is going.
With 1100
button
widgets, you have 1100 quick dom rerender cycles in a row which takes ~10 seconds on your machine, including the overhead for all the rapid widget sequencing.Solution
I added a special function called
awaitViewAction
which provides aNotify
to a widget, but does so with blocking IO. Blocking IO prevents DOM updates so cannot be used for long running IO actions, and hence I am reluctant to make it available to clients. However this specific use case of constructing aNotify
in STM is guaranteed to be extremely short and can safely be exposed to client widgets.With this change, on adding 1100
button
widgets, the IO actions are quickly run in succession without touching the DOM, and finally each widget constructs its own UI which is populated into the DOM at once (one render total for all 1100 buttons). The same as what happens with your “fake”linkButton
code.Okay I’m pretty certain I understand why this is happening. Will push a fix soon-ish.