4
\$\begingroup\$

I wanted to detect when a user stops typing. I encountered this answer from SO (Not the selected answer, but the second one which seem to be better):

https://stackoverflow.com/a/5926782/17746636

But I edited it slightly to use keydown instead of keyup. Also the target is window for my example (I need to detect any key strokes on the window):

let typingTimer;
let doneTypingInterval = 1000;

window.addEventListener('keydown', () => {
  clearTimeout(typingTimer);
  typingTimer = setTimeout(doneTyping, doneTypingInterval);
});

function doneTyping() {
  console.log("Stopped typing");
}

I changed from keyup to keydown because there might be cases where a user can click on another window while typing, and then the timeout will never be released. Using keydown seem to work better and ensures that if a user is typing and then clicks on another window, then it will trigger the doneTyping function

\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

Structurally, there is nothing wrong with the approach.

  • You could consider const for doneTypingInterval, I would put a comment that this is in milliseconds
  • I would create a self-executing function to hide typingTimer from the global scope.
    • doneTypingInterval could then be a parameter or remain a constant
    • doneTyping could be a local function, or could be a parameter
  • I am not the biggest fan of anonymous functions, you could consider naming it

function doneTyping() {
  console.log("Stopped typing");
}

(function setupTypingDetection(element, maximumDelayBetweenKeystrokes, postTypingFunction) {
  let typingTimer;

  element.addEventListener('keydown', () => {
    clearTimeout(typingTimer);
    typingTimer = setTimeout(postTypingFunction, maximumDelayBetweenKeystrokes);
  });
})(window, 1000, doneTyping);

\$\endgroup\$
1
  • 3
    \$\begingroup\$ Instead of a comment, you could rename doneTypingInterval to something that includes the unit, such as maxIdleMilliseconds or something. \$\endgroup\$ Commented Oct 16, 2023 at 14:45
2
\$\begingroup\$
let typingTimer;
let doneTypingInterval = 1000;

window.addEventListener('keydown', () => {
  clearTimeout(typingTimer);
  typingTimer = setTimeout(doneTyping, doneTypingInterval);
});

If you need this behaviour more than once it might be a good idea to abstract the general idea of delayed execution and resetting the timer when called early.

function fnDelay(fn, msDelay) {
  let timeoutID;
  return function (...args) {
    clearTimeout(timeoutID);
    timeoutID = setTimeout(fn, msDelay, ...args);
  };
}

This abstraction would allow you to transform your code into:

window.addEventListener('keydown', fnDelay(doneTypeing, 1000));

function doneTyping() {
  console.log("Stopped typing");
}

ps. fnDelay is probably not the best name for this function, but I currently can't come up with a better one.


I personally also like to define time units as constants.

//           [------- unit definition ------] [--------- alias ---------]
export const MILLISECOND =    1             , MILLISECONDS = MILLISECOND;
export const SECOND      = 1000*MILLISECONDS, SECONDS      = SECOND     ;
export const MINUTE      =   60*SECONDS     , MINUTES      = MINUTE     ;
export const HOUR        =   60*MINUTES     , HOURS        = HOUR       ;
export const DAY         =   24*HOURS       , DAYS         = DAY        ;
export const WEEK        =    7*DAYS        , WEEKS        = WEEK       ;

This might seem overkill, but these constants are used quite often. I find fnDelay(doneTypeing, 1*SECOND) more expressive/readable than fnDelay(doneTypeing, 1000).

\$\endgroup\$

Not the answer you're looking for? Browse other questions tagged or ask your own question.