As developers, we constantly seek patterns and practices that enhance our code's efficiency, reusability, and maintainability. A recent Twitter discussion I engaged in highlighted two methods for managing volume state in media players using React Hooks.
Because I believe this approach significantly enhances reusability across various media players, I will explore why adopting a flexible hook interface is beneficial. This post contrasts two methodologies and demonstrates how thoughtfully crafted hooks can be effectively utilized in multiple applications.
The Pitfall of Inflexible Interfaces
The discussion featured two examples. Example A depicted useVolume
tightly coupled to the useVideoPlayer
hook. This design limits useVolume
to the video player, preventing its independent use or integration with other player hooks like useAudioPlayer
.
In contrast, Example B presented useVolume
as a standalone hook that accepts a player object. While aligning with React's core principles, this design challenges the Interface Segregation Principle (ISP). ISP advocates that no client should be forced to depend on methods it does not use, suggesting that useVolume
should not need player-specific knowledge.
Why Flexibility Matters
-
Reusability: A standalone
useVolume
hook can be seamlessly reused with different media players, eliminating the need to rewrite or duplicate logic. - Separation of Concerns: Isolating volume control from player logic enhances code manageability.
- Testability: Independent hooks simplify testing by encapsulating their logic.
Designing for Reusability
Recognizing the need for better interface segregation, I proposed an interface where useVideoPlayer
and useVolume
operate independently yet communicate effectively when necessary. Here's the improved interface demonstrated through a demo:
const { isPlaying, pause, play, player, onChangeVolume } = useVideoPlayer();
const { setVolume, volume, mute, unmute } = useVolume();
Here's how the hooks are implemented:
const useVideoPlayer = () => {
const [isPlaying, setIsPlaying] = useState(false);
const [playerVolume, setPlayerVolume] = useState(0);
const videoRef = useRef();
const play = () => setIsPlaying(true);
const pause = () => setIsPlaying(false);
// This code can be improved, this is just for demo purposes
useEffect(() => {
// sets volume
videoRef.current.volume = playerVolume / 100;
// play/pause programatically
isPlaying ? videoRef.current.play() : videoRef.current.pause()
}, [playerVolume, isPlaying]);
const player = (<video ref={videoRef} width="400" controls>
<source src="http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4" type="video/mp4" />
</video>);
return {
isPlaying,
play,
pause,
player,
onChangeVolume: (volume) => setPlayerVolume(volume),
};
};
const useVolume = (initialVolume = 50) => {
const [volume, setVolume] = useState(initialVolume);
const mute = () => setVolume(0);
const unmute = () => setVolume(50);
return {
volume,
setVolume,
mute,
unmute,
};
};
And the component that uses both hooks:
function VideoPlayer() {
const { isPlaying, pause, play, player, onChangeVolume } = useVideoPlayer();
const { setVolume, volume, mute, unmute } = useVolume();
useEffect(() => {
onChangeVolume(volume);
}, [volume]);
return (
<div className='App'>
<button onClick={isPlaying ? pause : play}>
{isPlaying ? 'Pause' : 'Play'}
</button>
<input type="range" min="0" max="100" value={volume}
onChange={(e) => setVolume(parseInt(e.target.value, 10))} />
<button onClick={mute}>Mute</button>
<button onClick={unmute}>Unmute</button>
{player}
<h1>Player Volume: {volume}</h1>
</div>
);
}
This design respects ISP by allowing each hook to be consumed independently, without assuming the presence of the other. It provides a clean separation that facilitates better modularity and reusability, ensuring that components only interact with the functionalities they require.
Conclusion
The flexibility of a hook is determined by how well it can operate in different contexts. By embracing flexibility in our hook designs, we make them more reusable and adaptable, leading to a cleaner and more efficient codebase. Designing with reusability in mind is essential, whether you're building hooks for video or audio players or any other functionality. Let's write code that stands the test of time and adaptability.
Top comments (0)