Say Hello to React Browser Hooks


Full-Stack Developer

react-hooks

‘React Browser Hooks’ is an Open Source library containing several custom hooks that integrate with common browser functionality.

The Problem & Our Motivation

Often browser events and functions are directly added to components, which can:

  • Add significantly to a component’s footprint
  • Dilute the core functionality and purity of the component leading to less readable code
  • Create disjointed code where related logic ends up apart, leading to less maintainable code
  • Require the use of lifecycle events or state to manage, meaning a React class component must be used instead of a stateless functional component
  • Take some investigation for a developer to figure out (especially if there are nuances), resulting in lost time / inaccurate estimations of tasks
  • Be non-standard between browsers, meaning developers have to research cross-browser support and register multiple events / call multiple functions e.g. WebKit, Microsoft, opera and Mozilla versions of events.
  • Lead to memory leaks if the lifecycle is not managed correctly
  • Sometimes require throttling (for rapid-fire events like scrolling or mouse position to minimise the impact on performance)
  • Lead to re-inventing the wheel

We did some rudimentary research on common browser events and, from a basic search, found that there are over 150,000 modules on GitHub that add event listeners directly, tens of thousands of which are specific browser events including ‘mousemove’, ‘fullscreen’, ‘resize’ and many others.  These are typically registered in component lifecycle event ‘componentDidMount’ and deregistered or removed in the ‘componentWillUnmount’ event. Our initial research revealed that up to 25% of registered events may not be deregistered. It also highlighted that the same browser integration code is being repeated over and over, and so it’s a real case of developers re-inventing the wheel.

Internally within our projects, we have often implemented listeners directly within components, and so we felt a library of hooks would be useful to standardise and simplify the re-use of these types of browser events.

Why Hooks?

React Hooks are a new addition to React.js, that makes it much easier to create and use libraries of application logic that are made up of complete vertical slices of a component. Previous methods for separating application/presentation logic sometimes lead to ‘nesting hell’, and often developers didn’t bother creating/importing libraries for very simple functions as a result.

Check out Dan Abramov’s article on what should and shouldn’t be a hook. 

Example Before Hooks

Imagine you wanted to create a button component, for a video player, that when clicked opened the browser in fullscreen mode.  You might implement something like the next example.

import React from 'react'

class FullScreenButton extends React.Component {
 constructor(props) {
   super(props)
   this.state = {
     fullscreen: false
   }
 }

 handleFullscreenChange = (e) => {
   let fullscreen = false
   if (document.fullscreenElement ||
     document.mozFullScreenElement||
     document.webkitFullscreenElement ||
     document.msFullscreenElement ||
     document.fullscreen ||
     document.mozFullScreen ||
     document.webkitIsFullScreene ||
     document.fullScreenMode ) {
     fullscreen = true
   }
   this.setState ({fullscreen})
 }

 handleToggle = (e) => {
   const el = document.documentElement
   if(!this.state.fullscreen) {
     if (el.requestFullscreen) {
       el.requestFullscreen()
     } else if (el.mozRequestFullScreen) {
       el.mozRequestFullScreen()
     } else if (el.webkitRequestFullscreen) {
       el.webkitRequestFullscreen()
     } else if (el.msRequestFullscreen) {
       el.msRequestFullscreen()
     }
   } else {
     if (document.exitFullscreen) {
       document.exitFullscreen()
     } else if (document.mozCancelFullScreen) {
       document.mozCancelFullScreen()
     } else if (document.webkitExitFullscreen) {
       document.webkitExitFullscreen()
     } else if (document.msExitFullscreen) {
       document.msExitFullscreen()
     }
   }
 }

 componentDidMount() {
   document.addEventListener('webkitfullscreenchange', this.handleFullscreenChange, false)
   document.addEventListener('mozfullscreenchange', this.handleFullscreenChange, false)
   document.addEventListener('msfullscreenchange', this.handleFullscreenChange, false)
   document.addEventListener('MSFullscreenChange', this.handleFullscreenChange, false) //IE11
   document.addEventListener('fullscreenchange', this.handleFullscreenChange, false)
 }

 render() {
   return {
     <button onClick={this.handleToggle}>Toggle Full Screen</button>
   )
 }

 componentWillUnmount() {
   document.removeEventListener('webkitfullscreenchange', this.handleFullscreenChange)
   document.removeEventListener('mozfullscreenchange', this.handleFullscreenChange)
   document.removeEventListener('msfullscreenchange', this.handleFullscreenChange)
   document.removeEventListener('MSFullscreenChange', this.handleFullscreenChange)
   document.removeEventListener('fullscreenchange', this.handleFullscreenChange)
 }
}

export default FullScreenButton

As you can see from above, there can be a lot of events for multiple browsers and code mucking up a component that could be much purer. All of this ends up in the component before any further logic is added.

Consider the same component using a custom fullscreen hook from a library like react-browser-hooks:

import React from 'react'

import { useFullScreen } from '@nearform/react-browser-hooks'
export default function FullScreenButton () {
 const {toggle} = useFullScreen()
 return (
     <!-- amazing video player render code goes here -->
     <button onClick={toggle}>{'Toggle Full Screen'}</button>
   )
}

In this example, the entire vertical slice of full-screen functionality has been stripped from the component.  All of the logic that deals with activating full-screen mode and discovering fullscreen status, is now in its own tidy library that can be used by any future component that requires this functionality.  In this case, a button component might even be unnecessary as the hook could be used directly within the video player component, whose main element could be passed indirectly to the hook and toggle could be fired from a doubleClick event.

There are other nuances with full-screen functionality, that often developers may not consider until they have to implement it (adding unknown time to an estimated task),  e.g. is the application full screen because of the browser menu full-screen option or is it because an element was made full-screen from within an application. These are the kinds of things a developer shouldn’t have to worry about solving.  It’s something a library like react-browser-hooks should take care of.

All of these initial hooks were developed with passion, care and interest by developers who always strive to write good code.

The first version of react-browser-hooks contains hooks for various common tasks including:

  • Online/Offline hook: Determine if the browser has an Internet connection.
  • Orientation hook: Determines the angle of orientation of the screen
  • GeoLocation hook: Gets the user’s current location and keeps it updated as they move, very handy for mapping applications.
  • Mouse Position hook: Gets the current mouse coordinates
  • Scroll hook: Gets the current browser scroll left and top positions
  • Resize hook: Gets the width and height of the page
  • Full-Screen hook: Determines if the screen or an element is in full-screen mode, a second hook exist to check if the browser is full screen (e.g. F11 on menu)
  • Page Visibility hook:  If you browse away or back to a tab, this hook will let your component know (maybe hibernate some activity or set the tab title to something to entice them back).
  • Media Controls hook: Provides access to media controls like play, pause, seek and volume for controlling audio/video elements.

We can put together hooks, like in this demo using several of the hooks above to render a video. It uses media controls, mouse position, screen resize as well as fullscreen hooks together. Check the “Babel” tab to see how quickly you can create something powerful with react browser hooks. The play and pause buttons use media controls hook, the fullscreen icon in the bottom corner uses the fullscreen hook, and try set different sizes using the codepen toolbar and move the mouse around to see it respond accordingly.

See the Pen Browser hooks example: video by Donovan Hutchinson (@donovanh) on CodePen.0

 

We plan on adding more over time as demand dictates.  We also see this library as a handy base library for building some more complex libraries on top and we invite others to use it for such purposes.

Testing

Our goal is to have an extremely solid set of hooks that work seamlessly across as many browsers as possible. In terms of testing, as part of our continuous integration process, we have unit tests and coverage analysis in place.  We also use netlify to deploy our demo on every branch submitted so reviewers can test out code that is undergoing a pull request.

We appreciate feedback from the community should any errors be found in this early version of react-browser-hooks.

See our demo (which doubles as our documentation), for more information on each of these custom hooks and to see them in action.

To install the package into your project use ‘react-browser-hooks

Then import a hook and use it within a functional component.  You can literally integrate them in seconds.

If you would like to contribute to this project or raise an issue/request, please visit our GitHub project page

 

At NearForm, we have vast experience in building solutions across a broad tech stack to deliver reduced complexities and overcome common hurdles. If you are creating modern applications and leveraging web technologies, contact us to learn more about how we can help.

You might also like some of our previous blog posts on React:

Forget everything you learned about React – Hooks rocks! 

Managing React state with Render Props

Exploring React Portals

Sharing React components with Lerna

 

? Credit: PixaBay

Top