¶
Question¶
My input field is laggy. When the user types quickly, it is dropping characters. I have implemented it like this:
[:input
{:type "text"
:value @(rf/subscribe [::subs/username])
:on-change #(rf/dispatch [::events/change-username (-> % .-target .-value)])}]
Answer¶
That on-change
handler is being called after the user types every character. If the user is typing very quickly, then the following race condition can occur:
- The user types a new character taking the field from
state A
tostate B
(B
has one new, extra character in it, compared tostate A
) - The change event for
state B
is dispatched byon-change
. And that event is queued for processing. - But before that event can be processed, the browser schedules an animation frame.
- In that animation frame the component is re-rendered
- But during that re-render the
subscribe
will deliverstate A
- That means the text in the box will revert from
state B
tostate A
(the character just typed won't be in the input) - Now if nothing happened till the next animation frame the situation would resolve itself. Because
state B
would be rendered next time because the event which included the new character would have been processed well before then. - BUT if the user immediately types another character, the state dispatched will be
State A + new character
. The previous character typed, which caused A -> B, is now lost.
Bottom line: with very fast typing, characters can get dropped just before animation-frames.
There are three solutions:
- don't use
on-change
, and instead useon-blur
which is only called when the user has done all their fast typing and they leave the field. - if you have to use
on-change
then switch to usedispatch-sync
inon-change
, instead ofdispatch
. The event will not be placed on the queue. It will be handled immediately. Synchronously. - use a component from something like re-com because it has been engineered to not have this problem. Or copy the (local state) technique it uses.