You can find all the code in this post at the repo Github.
Event-related React custom hooks challenges
useBodyScrollLock()
import { useLayoutEffect, useState } from "react";
function useBodyScrollLock() {
useLayoutEffect(() => {
const originalStyle = window.getComputedStyle(document.body).overflow;
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = originalStyle;
};
}, []);
}
function Modal({ onClose }) {
useBodyScrollLock();
return (
<div
style={{
zIndex: 100,
background: "rgba(0,0,0,0.5)",
display: "flex",
justifyContent: "center",
alignItems: "center",
height: "100vh",
}}
>
<div style={{ padding: 20, background: "#fff", borderRadius: 8 }}>
<h2>Modal Content</h2>
<button onClick={onClose}>Close</button>
</div>
</div>
);
}
export default function App() {
const [modalOpen, setModalOpen] = useState(false);
return (
<div style={{ height: "200vh", textAlign: "center", paddingTop: 100 }}>
<button onClick={() => setModalOpen(true)}>Open Modal</button>
{modalOpen && <Modal onClose={() => setModalOpen(false)} />}
</div>
);
}
useClickOutside()
import { useRef, useEffect } from "react";
function useClickOutside(callbackFn) {
const ref = useRef(null);
useEffect(() => {
const click = ({ target }) => {
if (target && ref.current && !ref.current.contains(target)) {
callbackFn();
}
};
document.addEventListener("mousedown", click);
return () => {
document.removeEventListener("mousedown", click);
};
}, []);
return ref;
}
/* Usage example */
export default function App() {
const ref = useClickOutside();
return <button>ClickOutside: {String(ref.current)}</button>;
}
useEventListener()
import { useEffect, useState } from "react";
function useEventListener(eventName, handler, element = window) {
useEffect(() => {
const isSupported = element && element.addEventListener;
if (!isSupported) {
return;
}
const eventListener = (event) => {
handler(event);
};
element.addEventListener(eventName, eventListener);
return () => {
element.removeEventListener(eventName, eventListener);
};
}, [eventName, handler, element]);
}
// Usage example
export default function App() {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount((prevCount) => prevCount + 1);
};
useEventListener("click", handleClick);
return (
<div>
<h2>Click Count: {count}</h2>
<p>Click anywhere in the window to increment the count.</p>
</div>
);
}
useFocus()
import { useRef, useState, useEffect, useCallback } from "react";
function useFocus() {
const [isFocus, setIsFocus] = useState(false);
const focusRef = useRef(null);
const handleFocus = useCallback(() => {
setIsFocus(true);
}, []);
const handleBlur = useCallback(() => {
setIsFocus(false);
}, []);
useEffect(() => {
const node = focusRef.current;
setIsFocus(document.activeElement === node);
if (node) {
node.addEventListener("focus", handleFocus);
node.addEventListener("blur", handleBlur);
}
return () => {
node.removeEventListener("focus", handleFocus);
node.removeEventListener("blur", handleBlur);
};
}, [focusRef.current]);
return [focusRef, isFocus];
}
/* Usage example */
export default function App() {
const [focusRef, isFocus] = useFocus();
return (
<div>
<input ref={focusRef} value={isFocus} />
</div>
);
}
useHover()
import { useState, useRef, useEffect, useCallback } from "react";
function useHover() {
const [isHovered, setIsHovered] = useState(false);
const ref = useRef(null);
const handleMouseEnter = useCallback(() => {
setIsHovered(true);
}, []);
const handleMouseLeave = useCallback(() => {
setIsHovered(false);
}, []);
useEffect(() => {
setIsHovered(false);
const node = ref.current;
if (node) {
node.addEventListener("mouseenter", handleMouseEnter);
node.addEventListener("mouseleave", handleMouseLeave);
return () => {
node.removeEventListener("mouseenter", handleMouseEnter);
node.removeEventListener("mouseleave", handleMouseLeave);
};
}
}, [ref.current, handleMouseEnter, handleMouseLeave]);
return [ref, isHovered];
}
/* Usage example */
export default function App() {
const [inputRef, isHovered] = useHover();
return (
<div>
<input ref={inputRef} value={isHovered} />
</div>
);
}
useKeyPress()
import { useState, useEffect, useCallback } from "react";
function useKeyPress(targetKey) {
const [keyPressed, setKeyPressed] = useState(false);
const handleKeyDown = useCallback((event) => {
if (event.key === targetKey) {
setKeyPressed(true);
}
}, []);
const handleKeyUp = useCallback((event) => {
if (event.key === targetKey) {
setKeyPressed(false);
}
}, []);
useEffect(() => {
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("keyup", handleKeyUp);
return () => {
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("keyup", handleKeyUp);
};
}, []);
return keyPressed;
}
function App() {
const isSpacePressed = useKeyPress(" "); // Detect spacebar press
const isEnterPressed = useKeyPress("Enter"); // Detect Enter key press
return (
<div>
<h2>Key Press Demo</h2>
<p>Press and hold the Spacebar or Enter key</p>
<div>
Spacebar pressed: <strong>{isSpacePressed ? "Yes" : "No"}</strong>
</div>
<div>
Enter key pressed: <strong>{isEnterPressed ? "Yes" : "No"}</strong>
</div>
</div>
);
}
export default App;
usePosition()
import { useState, useEffect, useRef } from "react";
function usePosition(ref) {
const [position, setPosition] = useState({ top: 0, left: 0 });
useEffect(() => {
const updatePosition = () => {
if (ref.current) {
const { top, left } = ref.current.getBoundingClientRect();
setPosition({ top, left });
}
};
// Initial position update
updatePosition();
// Set up a resize observer to listen for position changes
const resizeObserver = new ResizeObserver(updatePosition);
if (ref.current) {
resizeObserver.observe(ref.current);
}
// Cleanup function to disconnect the observer
return () => {
if (ref.current) {
resizeObserver.unobserve(ref.current);
}
};
}, [ref]); // Re-run when the ref changes
return position;
}
function PositionDisplay() {
const ref = useRef(null);
const position = usePosition(ref);
return (
<div>
<h2>Element Position:</h2>
<div
ref={ref}
style={{
width: "200px",
height: "200px",
backgroundColor: "lightblue",
position: "relative",
}}
>
Move me around!
</div>
<pre>{JSON.stringify(position, null, 2)}</pre>
</div>
);
}
export default function App() {
return (
<div>
<h1>Listening to Element Position Changes</h1>
<PositionDisplay />
</div>
);
}
useToggle()
import { useState, useCallback } from 'react';
function useToggle(on) {
const [isToggled, setIsToggled] = useState(on);
const toggle = useCallback(() => {
setIsToggled((t) => !t);
}, []);
return [isToggled, toggle];
}
/* Usage example */
export default function App() {
const [isToggled, toggle] = useToggle(false);
return (
<div>
<button onClick={toggle}>Click to see</button>
{isToggled && <div>There is a hidden content</div>}
</div>
);
}
useWindowResize()
import { useState, useEffect } from "react";
function useWindowResize() {
const [windowSize, setWindowSize] = useState({
width: window.innerWidth,
height: window.innerHeight,
});
useEffect(() => {
const handleResize = () => {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return windowSize;
}
export default function App() {
const { width, height } = useWindowResize();
return (
<div style={{ position: "fixed", top: 0, right: 0 }}>
<h4>Window Size</h4>
<p>Width: {width}px</p>
<p>Height: {height}px</p>
</div>
);
}
useWindowScroll()
import { useState, useEffect } from "react";
function useWindowScroll() {
const [scrollPosition, setScrollPosition] = useState({
scrollX: window.scrollX,
scrollY: window.scrollY,
});
useEffect(() => {
const handleScroll = () => {
setScrollPosition({
scrollX: window.scrollX,
scrollY: window.scrollY,
});
};
window.addEventListener("scroll", handleScroll, { passive: true });
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, [handleScroll]);
return scrollPosition;
}
// Usage example
function App() {
const { scrollX, scrollY } = useWindowScroll();
return (
<div
style={{
position: "fixed",
top: 10,
right: 10,
background: "white",
padding: 10,
}}
>
<p>Scroll X: {scrollX.toFixed(0)}px</p>
<p>Scroll Y: {scrollY.toFixed(0)}px</p>
</div>
);
}
export default App;