User Activity
VueUse is a gold mine for Vue.js developers. It’s a collection of composables (aka hooks in the Vue world).
VueUse brings complex utility functions, like the useIdle
composable that tracks the user’s activity on the page. The composable listens to every mousemove
, mousedown
, resize
, keydown
, touchart
, and wheel
event and considers the user active for a specific duration after receiving one of these events.
The useIdle
composable restarts the timer whenever the user interacts with the page.
XState excels at orchestrating code with timers, and this example will be fun to reimplement! It combines the debouncing and throttling patterns.
Example
You can interact with the page and see the timer remain at 0s, or stop interacting with the page in any way,
and see it increase and the state Idle
value becoming true
:
The timeout has been reduced to five seconds for the demo. In the real-world you would be use a bigger one, like one minute.
Idle: false
Inactive: 0s
Listening to DOM events
The domEventListener
logic is a callback that sets up many event listeners on the window and optionally on the document.
A callback logic can return a callback function that’s called when an actor that was invoked or spawned based on the logic stops.
Every time the window
or the document
receives one of the listened events, the logic sends an "activity"
event to its parent actor.
The domEventListener
logic is invoked at the root state of the machine and renamed to Listen to DOM events
:
As we’ll see below, the state machine handles the "activity"
events differently based on its current state.
Use timers to detect the user’s inactivity
The user is, by default, considered active. The initial state is Active.Idle
,
and a timer is instantly started with the value of the dynamic delay "Inactivity timeout"
.
When the timer ends, it targets the Active.Done
final state, which triggers a transition to the Inactive
state.
I like to rely on final states to emphasize where a flow ends. I dislike using global state IDs as targets and prefer using final states.
When the state machine receives the "activity"
event in the Active.Idle
state, it targets the Active.Deduplicating
state:
The Active.Deduplicating
state throttles the "activity"
event.
Following the throttling pattern, the state machine stops listening to the event for some time.
It starts a timer of 50ms and then transitions back to the Active.Idle
state.
The useIdle
composable throttles events by 50ms.
I assume the reason is that it’s cheaper to create a 50ms timer than creating many more timers that would be created if it wasn’t throttling and the state machine was receiving a lot of "activity"
events.
Finally, the Inactive
state also listens to the "activity"
event and transitions to the Active
state:
The code for this example is short, but the logic represented is not simple. XState makes managing timers so easy that it hides the underlying complexity and instead makes the intrinsic logic stand out.
Code
Get news from XState by Example
Sign up for the newsletter to be notified when more machines or an interactive tutorial are released. I respect your privacy and will only send emails once in a while.