A collection of useful custom React hooks.
To install this package run:
npm i -D @rs1/react-hooksHere you can view a working example of every hook, with walk through sample code:
Demo site
useAgentParser
Use it to parse the current user-agent string and to detect if the device is touch enabled.useAutoIdle
Use it to update your UI when the user isn't interacting with a component.useDynamicRef
Use it to keep a dynamic – auto-updating or manual-updating – reference to a DOM node.useFullScreen
Use it to gain access to the native Full Screen API.useKeyAction
Use it to link a callback to a key press, updating it when any dependencies change.useKeyLinkedRef
Use it to easily let a keyboard key press simulate the click on a DOM node.useListener
Use it to attach an event listener to a DOM node, updating it when the DOM node or any dependencies change.useMediaQuery
Use it to update your UI based on the viewport size of the browser.useRectRef
Use it to keep a reference to the bounding client rect of a DOM node.useRefEffect
Use it in combination withuseDynamicRefto react to a DOM node change.useSliderRef
Use it to transform any DOM node in a horizontal slider, handling touch and drag events
Useful to parse the current user-agent string and to detect if the device is touch enabled.
How to use it in your React component:
importReactfrom'react'import{useAgentParser}from'@rs1/react-hooks'exportdefault(props)=>{const{
isTouch,OS: {name: osName,version: osVersion,},browser: {name: browserName,version: browserVersion,major: browserMajor,},
agent,}=useAgentParser()return(<div><h3>{`${osName} (v${osVersion})`}</h3>{`You're ${isTouch ? 'touching' : 'surfing'} the web with ${browserName}${browserMajor} (v${browserVersion}), what a wonderful browser!`}<hr/><pre>{agent}</pre></div>)}Useful if you need to update your UI when the user isn't interacting with a component.
How to use it in your React component:
exportdefaultprops=>{constnode=useRef()const[isIdle]=useAutoIdle(// Which node should be watched?node?.current,{// What callback should be fired on change?callback: idle=>console.log(`The watched node is now ${idle ? 'idle' : 'active'}`),// How many seconds shoud be waited before going to idle?wait: 5,// Is there a condition that should be true before going to idle?condition: true,// Is the node initially idle?initial: false,})return(<divref={node}style={{minWidth: '300px',minHeight: '300px',background: isIdle ? 'red' : 'blue',color: '#ffffff',padding: '15px',}}>
This node reacts to your mouse movements. After 5 seconds that you
stopped interacting, it'll go idle.
<hr/>{`This node is currently ${isIdle ? 'idle' : 'active'}`}</div>)}Useful to keep a dynamic – auto-updating or manual-updating – reference to a DOM node.
How to use it in your React component:
importReact,{useEffect,useRef}from'react'import{useDynamicRef}from'@rs1/react-hooks'exportdefault({ url, isVideo, ...props})=>{const[media,mediaRef/* , updateMediaRef */]=useDynamicRef(/* Initial value: */null,/* Require manual-update: */false)useEffect(()=>{console.log(`The media ref tag is: ${media?.tagName}`)},[media])/* Comparison with the standard useRef hook */conststdRef=useRef()useEffect(()=>{console.log(`The standard ref tag is: ${media?.tagName}`)},[stdRef])return(<div><h6>
useDynamicRef - Current tag: {media?.tagName||'undefined'}</h6>{isVideo ? (<videoref={mediaRef}src={url}controlsstyle={{width: '100%'}}/>) : (<audioref={mediaRef}src={url}controlsstyle={{width: '100%'}}/>)}<hr/><h6>useRef - Current tag: {stdRef?.tagName||'undefined'}</h6>{isVideo ? (<videoref={stdRef}src={url}controlsstyle={{width: '100%'}}/>) : (<audioref={stdRef}src={url}controlsstyle={{width: '100%'}}/>)}</div>)}Useful to gain access to the native Full Screen API.
How to use it in your React component:
importReact,{useRef}from'react'import{useFullScreen}from'@rs1/react-hooks'exportdefaultprops=>{constcontainerRef=useRef()constvideoRef=useRef()const{// request,// exit,
toggle,
isActive,
isSupported,}=useFullScreen({onChange: ()=>console.log(`Full screen status changed`),onError: ()=>console.log(`Full screen request failed`),})constbbbMovie='http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4'return(<div><divref={containerRef}style={{width: '100%',height: '100%',minHeight: '200px',position: 'relative',background: isActive ? '#000000' : '#ffffff',}}><videoref={videoRef}style={{width: '100%',height: '100%'}}controls><sourcesrc={bbbMovie}type='video/mp4'/></video>{isSupported(containerRef,videoRef)&&(<buttononClick={()=>{/* We can feed two DOM nodes to the API, the first is the one we want to be full screen, the second is an optional media element to be set full screen on the WebKit mobile version that doesn't support the full screen API for normal DOM nodes. */toggle(containerRef,videoRef)/* This is the same as: if (isActive) exit() else request(containerRef, videoRef) */}}style={{position: 'absolute',top: '25px',right: '25px',}}>{`${isActive ? 'Exit' : 'Enter'} full screen`}</button>)}</div></div>)}Useful to link a callback to a key press, updating it when any dependencies change.
How to use it in your React component:
importReact,{useCallback}from'react'import{useKeyAction}from'@rs1/react-hooks'exportdefault(props)=>{constcallback=()=>alert(`'K' key pressed!`)useKeyAction(/* 75 is the code for the 'K' key */75,/* The callback to fire when the key is pressed */callback,/* Optional array: dependencies */[],/* Optional boolean: whether or not should listen * to key press events coming from input elements * (input, button, select, textarea) */false)return(<div><h5>Press 'K' and you'll see!</h5></div>)}Useful to easily let a keyboard key press simulate the click on a DOM node.
How to use it in your React component:
importReact,{useCallback}from'react'import{useKeyLinkedRef}from'@rs1/react-hooks'exportdefault(props)=>{/* 75 is the code for the 'K' key */constbuttonRef=useKeyLinkedRef(75)consthandleClick=useCallback(e=>{console.log(`Received click on: "${e?.currentTarget?.id}"`)},[])return(<div><buttonref={buttonRef}onClick={handleClick}id="key-linked-button">
Click me or press 'K'
</button><h5>...and keep an eye on the console!</h5></div>)}Useful to attach an event listener to a DOM node, updating it when the DOM node or any dependencies change.
How to use it in your React component:
importReact,{useState,useCallback,useRef}from'react'import{useListener}from'@rs1/react-hooks'exportdefaultprops=>{constsectionRef=useRef()const[counter,setCounter]=useState(0)useListener(sectionRef?.current,'click',()=>console.log(`The counter is: ${counter}`),[counter])consthandleClick=useCallback(()=>{setCounter(counter=>counter+1)},[])return(<div><p>Click the red section and check the console!</p><buttononClick={handleClick}>Increase counter ({counter})</button><hr/><divref={sectionRef}style={{padding: '15px',background: 'red',color: '#ffffff'}}>
I've used the useListener hook.
</div></div>)}Useful to update your UI based on the viewport size of the browser.
How to use it in your React component:
importReactfrom'react'import{useMediaQuery}from'@rs1/react-hooks'exportdefaultprops=>{/* * You can pass an array of breakpoint widths * to this custom hook, it'll return an array * of boolean values indicating if that size * is supported. Additionally an extra parameter * is returned to signal if no width is supported. * The widths must be passed in descending order. */const[desktop,laptop,tablet,smartphone,none]=useMediaQuery([1920,1024,768,414])/* * Alternatively, you can pass an object of * breakpoint widths for every key to this custom * hook, it'll return an object of boolean values * for every key indicating if that size is * supported. Additionally an extra key 'none' * is returned to signal if no width is supported. * The widths must be passed in descending order. */constmq=useMediaQuery({desktop: 1920,laptop: 1024,tablet: 768,smartphone: 414,})/* * The above 'useMediaQuery' hooks transform the * breakpoint widths to these conditions: * desktop: min-width == 1920px * laptop: min-width == 1024px && max-width == 1919px * tablet: min-width == 768px && max-width == 1023px * smartphone: min-width == 414px && max-width == 767px * none: max-width == 413px */return(<div>{desktop&&<p>I'm visible on desktops</p>}{laptop&&<p>I'm visible on laptops</p>}{tablet&&<p>I'm visible on tablets</p>}{smartphone&&<p>I'm visible on smartphones</p>}{none&&<p>This screen size is not managed :(</p>}<hr/>{mq.desktop&&<p>I'm visible on desktops too</p>}{mq.laptop&&<p>I'm visible on laptops too</p>}{mq.tablet&&<p>I'm visible on tablets too</p>}{mq.smartphone&&<p>I'm visible on smartphones too</p>}{mq.none&&<p>This screen size is not managed here too :(</p>}</div>)}Useful to keep a reference to the bounding client rect of a DOM node.
How to use it in your React component:
importReactfrom'react'import{useRectRef}from'@rs1/react-hooks'exportdefaultprops=>{const[rect,sectionRef]=useRectRef()return(<divref={sectionRef}>{`I'm ${rect.width}px x ${rect.height}px, try resizing the window!`}</div>)}Useful in combination with useDynamicRef to react to a DOM node change.
How to use it in your React component:
importReact,{useReducer}from'react'import{useRefEffect,useDynamicRef}from'@rs1/react-hooks'exportdefaultprops=>{const[header,toggle]=useReducer(s=>!s,true)const[section,sectionRef]=useDynamicRef(null,false)useRefEffect(section,()=>{console.log(`The section tag is now ${section?.tagName}`)},[])return(<div><buttononClick={toggle}>Toggle</button><hr/>{header ? (<headerref={sectionRef}style={{width: '100%',minHeight: '200px',background: 'red',}}/>) : (<footerref={sectionRef}style={{width: '100%',minHeight: '200px',background: 'blue',}}/>)}</div>)}Useful to transform any DOM node in a horizontal slider, handling touch and drag events.
How to use it in your React component:
importReactfrom'react'import{useSliderRef}from'@rs1/react-hooks'exportdefaultprops=>{const[value,sliderRef]=useSliderRef(/* Initial ref value: */null,/* Initial slider value: */0,/* Slider direction: */'LTR')/* * The slider direction should be one of: * 'LTR', 'RTL', 'TTB' or 'BTT'. * 'LTR': horizontal - left to right (default) * 'RTL': horizontal - right to left * 'TTB': vertical - top to bottom * 'BTT': vertical - bottom to top */return(<div><p>{`Touch me or drag me to change my value! Actually I'm ${Math.round(value*100)}%`}</p><divref={sliderRef}style={{width: '100%',height: '10px',background: 'red',cursor: 'pointer',}}/><divstyle={{width: `${Math.round(value*100)}%`,height: '10px',background: 'blue',marginTop: '-10px',pointerEvents: 'none',}}/></div>)}