This page provides complete, working examples of custom React hooks from the 30 Seconds of Code collection. Each example includes the hook implementation and a practical usage demo.
Timing Hooks
useInterval - Declarative Intervals
Wrapping setInterval() in a React hook requires careful handling of closures and cleanup. Here’s a complete implementation:
Hook Implementation
Usage Example
How It Works
const useInterval = ( callback , delay ) => {
const savedCallback = React . useRef ();
React . useEffect (() => {
savedCallback . current = callback ;
}, [ callback ]);
React . useEffect (() => {
const tick = () => {
savedCallback . current ();
}
if ( delay !== null ) {
let id = setInterval ( tick , delay );
return () => clearInterval ( id );
}
}, [ delay ]);
};
const Timer = props => {
const [ seconds , setSeconds ] = React . useState ( 0 );
useInterval (() => {
setSeconds ( seconds + 1 );
}, 1000 );
return < p > { seconds } </ p > ;
};
ReactDOM . createRoot ( document . getElementById ( 'root' )). render (
< Timer />
);
The hook solves three key problems:
Fresh Closures : Uses useRef to ensure the interval always has access to the latest callback
Cleanup : Returns a cleanup function to clear the interval on unmount
Dynamic Delay : Recreates the interval when delay changes, or clears it if delay is null
The wrapper function tick() calls savedCallback.current(), which always points to the latest callback.
useTimeout - Declarative Timeouts
Similar to useInterval, but for one-time delayed execution:
const useTimeout = ( callback , delay ) => {
const savedCallback = React . useRef ();
React . useEffect (() => {
savedCallback . current = callback ;
}, [ callback ]);
React . useEffect (() => {
const tick = () => {
savedCallback . current ();
}
if ( delay !== null ) {
let id = setTimeout ( tick , delay );
return () => clearTimeout ( id );
}
}, [ delay ]);
};
// Usage
const OneSecondTimer = props => {
const [ seconds , setSeconds ] = React . useState ( 0 );
useTimeout (() => {
setSeconds ( seconds + 1 );
}, 1000 );
return < p > { seconds } </ p > ;
};
The implementation is nearly identical to useInterval, with the key difference being setTimeout vs setInterval.
useEffectOnce - Conditional One-Time Effects
Run a callback at most once when a condition becomes true:
const useEffectOnce = ( callback , when ) => {
const hasRunOnce = React . useRef ( false );
React . useEffect (() => {
if ( when && ! hasRunOnce . current ) {
callback ();
hasRunOnce . current = true ;
}
}, [ when ]);
};
// Usage
const App = () => {
const [ clicked , setClicked ] = React . useState ( false );
useEffectOnce (() => {
console . log ( 'mounted' );
}, clicked );
return < button onClick = { () => setClicked ( true ) } > Click me </ button > ;
};
Network Hooks
useFetch - Declarative Data Fetching
Fetch data with proper state management and abort controller support:
Complete Implementation
Usage Example
State Management
const useFetch = ( url , options ) => {
const [ response , setResponse ] = React . useState ( null );
const [ error , setError ] = React . useState ( null );
const [ abort , setAbort ] = React . useState (() => {});
React . useEffect (() => {
const fetchData = async () => {
try {
const abortController = new AbortController ();
const signal = abortController . signal ;
setAbort ( abortController . abort );
const res = await fetch ( url , { ... options , signal });
const json = await res . json ();
setResponse ( json );
} catch ( error ) {
setError ( error );
}
};
fetchData ();
return () => {
abort ();
}
}, []);
return { response , error , abort };
};
const ImageFetch = props => {
const res = useFetch ( 'https://dog.ceo/api/breeds/image/random' , {});
if ( ! res . response ) {
return < div > Loading... </ div > ;
}
const imageUrl = res . response . message ;
return (
< div >
< img src = { imageUrl } alt = "avatar" width = { 400 } height = "auto" />
</ div >
);
};
ReactDOM . createRoot ( document . getElementById ( 'root' )). render (
< ImageFetch />
);
The hook manages three pieces of state:
response : The successful response data (null while loading)
error : Any error that occurred during fetch
abort : Function to cancel the request
You can render different components based on these states: if ( ! res . response && ! res . error ) return < div > Loading... </ div > ;
if ( res . error ) return < div > Error: { res . error . message } </ div > ;
return < div > { /* render data */ } </ div > ;
Event Hooks
useClickOutside - Outside Click Detection
Detect clicks outside an element - essential for dropdowns, modals, and popovers:
const useClickOutside = ( ref , callback ) => {
const handleClick = e => {
if ( ref . current && ! ref . current . contains ( e . target )) {
callback ();
}
};
React . useEffect (() => {
document . addEventListener ( 'click' , handleClick );
return () => {
document . removeEventListener ( 'click' , handleClick );
};
});
};
// Usage
const ClickBox = ({ onClickOutside }) => {
const clickRef = React . useRef ();
useClickOutside ( clickRef , onClickOutside );
return (
< div
className = "click-box"
ref = { clickRef }
style = { {
border: '2px dashed orangered' ,
height: 200 ,
width: 400 ,
display: 'flex' ,
justifyContent: 'center' ,
alignItems: 'center'
} }
>
< p > Click out of this element </ p >
</ div >
);
};
ReactDOM . createRoot ( document . getElementById ( 'root' )). render (
< ClickBox onClickOutside = { () => alert ( 'click outside' ) } />
);
useClickInside - Inside Click Detection
Complement to useClickOutside for detecting clicks within an element:
const useClickInside = ( ref , callback ) => {
const handleClick = e => {
if ( ref . current && ref . current . contains ( e . target )) {
callback ();
}
};
React . useEffect (() => {
document . addEventListener ( 'click' , handleClick );
return () => {
document . removeEventListener ( 'click' , handleClick );
};
});
};
// Usage
const ClickBox = ({ onClickInside }) => {
const clickRef = React . useRef ();
useClickInside ( clickRef , onClickInside );
return (
< div
className = "click-box"
ref = { clickRef }
style = { {
border: '2px dashed orangered' ,
height: 200 ,
width: 400 ,
display: 'flex' ,
justifyContent: 'center' ,
alignItems: 'center'
} }
>
< p > Click inside this element </ p >
</ div >
);
};
Use useClickInside when you need to handle clicks on multiple children without adding individual onClick handlers to each.
Observer Hooks
useMutationObserver - DOM Change Detection
Watch for changes in the DOM tree using the MutationObserver API:
const useMutationObserver = (
ref ,
callback ,
options = {
attributes: true ,
characterData: true ,
childList: true ,
subtree: true ,
}
) => {
React . useEffect (() => {
if ( ref . current ) {
const observer = new MutationObserver ( callback );
observer . observe ( ref . current , options );
return () => observer . disconnect ();
}
}, [ callback , options ]);
};
// Usage
const App = () => {
const mutationRef = React . useRef ();
const [ mutationCount , setMutationCount ] = React . useState ( 0 );
const incrementMutationCount = () => {
return setMutationCount ( mutationCount + 1 );
};
useMutationObserver ( mutationRef , incrementMutationCount );
const [ content , setContent ] = React . useState ( 'Hello world' );
return (
<>
< label htmlFor = "content-input" > Edit this to update the text: </ label >
< textarea
id = "content-input"
style = { { width: '100%' } }
value = { content }
onChange = { e => setContent ( e . target . value ) }
/>
< div style = { { width: '100%' } } ref = { mutationRef } >
< div
style = { {
resize: 'both' ,
overflow: 'auto' ,
maxWidth: '100%' ,
border: '1px solid black' ,
} }
>
< h2 > Resize or change the content: </ h2 >
< p > { content } </ p >
</ div >
</ div >
< div >
< h3 > Mutation count { mutationCount } </ h3 >
</ div >
</>
);
};
Lifecycle Hooks
Replicate Class Component Lifecycle
If you’re transitioning from class components, these hooks replicate lifecycle methods:
componentDidMount
componentDidUpdate
componentWillUnmount
const useComponentDidMount = onMountHandler => {
React . useEffect (() => {
onMountHandler ();
}, []);
};
// Usage
const Mounter = () => {
useComponentDidMount (() => console . log ( 'Component did mount' ));
return < div > Check the console! </ div > ;
};
const useComponentDidUpdate = ( callback , condition ) => {
const mounted = React . useRef ( false );
React . useEffect (() => {
if ( mounted . current ) callback ();
else mounted . current = true ;
}, condition );
};
// Usage
const App = () => {
const [ value , setValue ] = React . useState ( 0 );
const [ otherValue , setOtherValue ] = React . useState ( 1 );
useComponentDidUpdate (() => {
console . log ( `Current value is ${ value } .` );
}, [ value ]);
return (
<>
< p > Value: { value } , other value: { otherValue } </ p >
< button onClick = { () => setValue ( value + 1 ) } > Increment value </ button >
< button onClick = { () => setOtherValue ( otherValue + 1 ) } >
Increment other value
</ button >
</>
);
};
const useComponentWillUnmount = onUnmountHandler => {
React . useEffect (
() => () => {
onUnmountHandler ();
},
[]
);
};
// Usage
const Unmounter = () => {
useComponentWillUnmount (() => console . log ( 'Component will unmount' ));
return < div > Check the console! </ div > ;
};
Key Patterns
useRef for Mutable Values Use useRef when you need values that persist across renders but don’t trigger re-renders when changed.
Cleanup Functions Always return cleanup functions from effects that add event listeners or create subscriptions.
Dependency Arrays Include all values from component scope that are used in the effect to avoid stale closures.
Abort Controllers Use AbortController for network requests to prevent memory leaks and race conditions.
Next Steps