AV's Blog

Creating a Disappearing React Component Upon User Inactivity

May 29, 2018

For our capstone project at Fullstack Academy, my group created an app we called “MyFlix” that is a clone of Plex, a media server app. We used Electron and React to create the app. In this project, I learned a lot not only on how to use Electron and use React in it, but also on how it is to work collaboratively and practice my Git skills.

One task I was assigned was to create the movie player for the app. The requirements for this component are that it should play a movie file and it should have a custom video control. Rendering the movie that can play was easy, thanks to a node package called ReactPlayer. Creating the custom video control that is able to play, pause, forward and back the video as well as increase and decrease the volume of the video was a little challenging. ReactPlayer has a prop called controls, the default video control for the ReactPlayer component, but we wanted a custom one.

This was due to the fact that I have to take over the functions for these activities (play, pause, forward, back, increase volume, decrease volume), but also I have to make sure that the control appear during user activity and disappear otherwise. For this post, I will talk about how I made the video control element or component do this.

Looking back, it would have been easier if I first looked at how I can come up with a React component that disappears after a period of inactivity. Like this:

In the video, the element “I should hide” only shows when the mouse moves and hides again when there is no activity. Also, is should be visible upon page loading, then disappears if there is no user activity (the video doesn’t clearly show that but at ~0:03, the page was reloaded). That is basically what most video controls do. (For example, check the YouTube video above.)

By making the simple React component that can do what’s happening above, I was able to incorporate this as a feature of my video controller for my project. The details on how I created this is described below.

First, I added a shouldHide state on the component. This state should change from false to true after a few seconds after page loading. I used a setTimeout function (assigned to a variable called timerId which was set to null in the constructor function) that calls a setState to change shouldHide. I set the timeout to two seconds. I defined timerId in a function called hidingTimer.

This function was then used in the componentDidMount lifecycle hook to start the timer upon page load, and when the timer ends, shouldHide is changed to true.

class Hiding extends React.Component {
  constructor() {
    super()
    this.state = {
      shouldHide: false,
    }
    this.timerId = null
  }

  hidingTimer() {
    this.timerId = setTimeout(() => {
      this.setState({ shouldHide: true })
    }, 2000)
  }

  componentDidMount() {
    this.hidingTimer()
  }
  // ...
}

Another function I created was handleMouseMove which changes shouldHide to false when the mouse moves, and starts the timer again so shouldHide would change to true when movement wanes.

    handleMouseMove() {
        clearTimeout(this.timerId); // clear the setTimeout first
        this.setState({ shouldHide: false });
        this.hidingTimer();
    }

Note that the two functions, handleMouseMove and hidingTimer were bound to this in the constructor function to make it work for the Hiding component.

Finally, in the render function, I take advantage of conditionals to change the class name of the div that contains the element that should hide. The final code for the React component looks like this:

import React from 'react'

class Hiding extends React.Component {
  constructor() {
    super()
    this.state = {
      shouldHide: false,
    }
    this.timerId = null
    this.handleMouseMove = this.handleMouseMove.bind(this)
    this.hidingTimer = this.hidingTimer.bind(this)
  }

  hidingTimer() {
    this.timerId = setTimeout(() => {
      this.setState({ shouldHide: true })
    }, 2000)
  }

  componentDidMount() {
    this.hidingTimer()
  }

  handleMouseMove() {
    clearTimeout(this.timerId)
    this.setState({
      shouldHide: false,
    })
    this.hidingTimer()
  }

  render() {
    return (
      <div>
        <div onMouseMove={this.handleMouseMove}>
          <h1>Move here</h1>
          <div className={this.state.shouldHide ? 'hidden' : 'none'}>
            <h1>I should hide</h1>
          </div>
        </div>
      </div>
    )
  }
}

This code for the component makes possible the desired characteristics of the component shown in the video above.

So, I went ahead and applied this code to the actual video control component I was working on. However, I encountered another problem, which was the occurence of memory leaks. Apparently, this is common with setTimeout functions in a React app. The recommended fix was to include a componentWillUnmount function that clears the setTimeout function. Luckilly, this worked for us, and the video control does what it’s supposed to do without the memory leaks.

componentWillUnmount() {
    clearTimeout(this.timerId)
}