¶
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 Atostate B(Bhas one new, extra character in it, compared tostate A) - The change event for
state Bis 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
subscribewill deliverstate A - That means the text in the box will revert from
state Btostate 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 Bwould 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-blurwhich is only called when the user has done all their fast typing and they leave the field. - if you have to use
on-changethen switch to usedispatch-syncinon-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.