Hooks added a lot of functionality to stateless components. Not only did they add functionality but reusability and sharing of logic across your app. You can combine many of the hooks to leverage each other to build out this reusable logic.
We'll create a separate file called useMousePosition
and import a function that we'll export later. We just expect that it will return a position
that is the mouse position. As you can see we have no logic here. We are just requesting a position to render.
The reason we start the function with use
is that React provides an eslint plugin that will lint any function that starts with use
and make sure you are not violating any of the hook requirements.
import React from "react"; import { useMousePosition } from "./useMousePosition"; const App = () => { const position = useMousePosition(); return <div />; }; export default App;
Lets create our function and setup our useState
. The useState
will take an initial state and we'll just initialize it with {x: 0, y: 0}
. This will return an array that we can destructure. It will destructure to the state position, and then a function to update the state.
import { useState, useEffect } from "react"; export const useMousePosition = () => { const [position, setPosition] = useState({ x: 0, y: 0 }); return position; };
If you're confused about the destructuring of the array here is the equivalent.
const positionState = useState({ x: 0, y: 0 }); const position = positionState[0]; const setPosition = positionState[1];
This is just an ES6 and beyond feature.
Now we setup our useEffect
. Within our useEffect
we listen on the window for any mouse movements. We need to be sure and clean up any effects that we make and to do that we return a function. This will run when the component is unmounted, or anything in our second argument array changes.
The second argument to useEffect
is empty because we only need to attach the window listener once. React will look at the array each render and see if anything has changed. If you didn't pass the array it would always reapply this effect every re-render.
useEffect(() => { window.addEventListener("mousemove"); return () => { window.removeEventListener("mousemove"); }; }, []);
You'll notice we didn't apply a function to our addEventListener
. When registering an event listener you supply a function. To unregister you need to call removeEventListener
with the same function. We no longer have this
like we do with classes to store the exact function to unregister.
To do that we can just create a function inside of useEffect
. This function will then receive our event and call our setPosition
update function with the values.
const setFromEvent = (e) => setPosition({ x: e.clientX, y: e.clientY });
Putting it all together our hook will look like this.
import { useEffect, useState } from "react"; export const useMousePosition = () => { const [position, setPosition] = useState({ x: 0, y: 0 }); useEffect(() => { const setFromEvent = (e) => setPosition({ x: e.clientX, y: e.clientY }); window.addEventListener("mousemove", setFromEvent); return () => { window.removeEventListener("mousemove", setFromEvent); }; }, []); return position; };
Now that we're returning our state with an x/y
we can render it and move our mouse around and see our div update.
const App = () => { const position = useMousePosition(); return ( <div> {position.x}:{position.y} </div> ); };