Designing React Hooks for Flexibility

Designing React Hooks for Flexibility

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.

Leave a Reply

Your email address will not be published. Required fields are marked *