June 02, 2019
For an interactive application, you may need to know the position of the user’s cursor. Simple enough, there are native dom events to track the movements of the mouse. However, there are a few issues to consider:
We encapsulate the functionality inside a hook. This way, any component that needs the mouse position can easily obtain it, without making the mouse position a global variable.
import { useState } from 'react'
function useMousePosition() {
const [x, setX] = useState(null)
const [y, setY] = useState(null)
// We do not expose a way to update mouseX and mouseY
// This will be handled within the hook itself
return { mouseX: x, mouseY: y }
}
We can listen to mouse movement with vanilla JavaScript. However, RxJS Gives us a declarative API and easier performance tuning.
setTimeout
callbacks.import { useEffect, useState } from 'react'
import { fromEvent } from 'rxjs'
import { map, throttleTime } from 'rxjs/operators'
function useMousePosition() {
const [x, setX] = useState(null)
const [y, setY] = useState(null)
useEffect(() => {
// Subscribe to the mousemove event
const sub = fromEvent(document, 'mousemove')
// Extract out current mouse position from the event
.pipe(map(event => [event.clientX, event.clientY]))
// We have closure over the updater functions for our two state variables
// Use these updaters to bridge the gap between RxJS and React
.subscribe(([newX, newY]) => {
setX(newX)
setY(newY)
})
// When the component unmounts, remove the event listener
return () => {
sub.unsubscribe()
}
// We use [] here so that this effect fires exactly once.
// (After the first render)
}, [])
return { mouseX: x, mouseY: y }
}
The previous example provides a functioning mouse position hook. However, it may slow your site down. It will attempt to update the mouse position state with each mousemove
event. RxJS provides a way to throttle this.
We simple add a throttleTime()
to our mousemove
event pipeline.
import { fromEvent } from 'rxjs'
import { map, throttleTime } from 'rxjs/operators'
const sub = fromEvent(document, 'mousemove').pipe(
throttleTime(100), // Only respond to a mousemove event every 100ms
map(event => [event.clientX, event.clientY])
)
Here you can see the final implementation in it’s entirety. Notice that I take the throttle time as a parameter. For each use case, you should use the largest number that provides a quality experience to minimize resource usage.
import { useEffect, useState } from 'react'
import { fromEvent } from 'rxjs'
import { map, throttleTime } from 'rxjs/operators'
function useMousePosition(throttleTime = 100) {
const [x, setX] = useState(null)
const [y, setY] = useState(null)
useEffect(() => {
const sub = fromEvent(document, 'mousemove')
.pipe(
throttleTime(throttleTime),
map(event => [event.clientX, event.clientY])
)
.subscribe(([newX, newY]) => {
setX(newX)
setY(newY)
})
return () => {
sub.unsubscribe()
}
}, [])
return {
mouseX: x,
mouseY: y,
}
}
I'm David Harting, a full-stack developer from Westfield, Indiana.
Say hello on Twitter👋