How to approach a React task, using HTML5 Camera as an example

Eleni Chappen
20spokes Whiteboard
4 min readApr 22, 2017

As a developer who spends half my time in React and the other half in Ruby on Rails, there are times when the Rails part of my brain balks at something I do in React.

React is a less opinionated framework than Rails in terms of how to structure your code. This is not really a fair comparison, since React is strictly a front-end framework and Rails is not. Even so, when working in React, the Rails part of my brain will ask, “What is the convention for this?” To which the React part of my brain will reply, “There is no convention (yet)! Let’s see what our coworkers are doing for it, or what people are saying about it on Medium/Twitter/Reddit/Stack Overflow… and then think really carefully about how you’d want to do it.”

This sometimes leads to situations where I find a solution in React that one could interpret as either elegant, or hacky.

For example: In a React application, I was tasked with implementing the ability to take photos at different steps of a form. For this task we used HTML5's Media Capture and Streams API, aka “HTML5 Camera.”

When starting a task that has no set convention, it’s important to start with the general rules of good code and go from there. So I knew I wanted to achieve a few things:

  1. Maintainability: Since I wasn’t that familiar with this API, I wanted to keep all of that functionality in one place so I’d only have one place to change it down the road.
  2. Flexibility: I needed to have picture-taking functionality available to all steps of the form, since the decision of where/when a photo was needed was changing frequently.
  3. Simplicity: I wanted the act of taking a picture in the form to be as simple as a single function call.

Since I wanted to keep all the API stuff in a single place, I created a React component called Camera which has a function called captureImage() that uses the API to perform the capture. I won’t show the whole thing here (like setting up the stream), but I’ll show the parts that use React refs to capture the image (all examples are in ES6):

captureImage() {    
const context = this.canvas.getContext("2d")
context.drawImage(this.videoStream, 0, 0, 800, 600)
const image = this.canvas.toDataURL('image/jpeg', 0.5)
return image
}
render() {
return (
<div>
<video
ref={(stream) => { this.videoStream = stream }}
width='800'
height='600'
style={{display: 'none'}}>
</video>
<canvas
ref={(canvas) => { this.canvas = canvas }}
width='800'
height='600'
style={{display: 'none'}}
</canvas>
</div>
)
}

The benefit of having this contained in a single component is that it’s modularized, which makes it more easily testable and maintainable. Now, all I have to do now to take a picture is include the Camera component into a container component, then define a ref on the camera instance to execute captureImage in the container:

class Container extends React.Component {

submitPhoto() {
const image = this.camera.captureImage()
doSomethingWithImage(image)
}
render() {
return(
<div>
<Camera ref={(camera) => {this.camera = camera }} />
...

Quick side-note on refs: when learning React, it’s tempting to have direct access to the DOM, since one may be more familiar with frameworks like jQuery. As a result, refs get overused by newcomers to React. It’s important to first think of the “React Way” of doing things, using props and state, before turning to refs as a solution. However, there are cases where accessing the DOM directly is unavoidable, which is why React provides this “escape hatch” (as their docs put it) through refs. In this project I considered the Media Capture API one of those situations.

Now, in order to use this camera wherever I wanted throughout the form, I could have placed a Camera component in whichever form step component needed it at the time and did something similar to the above. But what I did instead was place the camera in the component that switches/renders all form steps, then passed the camera’s captureImage function as a prop to every step:

/* a collection of form steps with class names of React components as values */const FORM_STEPS = {
enter_name: EnterName,
enter_password: EnterPassword,
submit_photo: SubmitPhoto,
...
}
class FormContainer extends React.Component {

render() {
return (
...

<Camera
ref={(camera) => { this.takePicture = camera.captureImage }}
/>
/* this.props.currentView is a string like 'enter_name' */ { React.createElement(
FORM_STEPS[this.props.currentView],
{ takePicture: this.takePicture.bind(this) }
)}
...

(Notice how this.takePicture is defined immediately in the ref callback, so I’m not exposing other parts of the camera.)

With React.createElement, I can pass any props I want as the second argument, giving all the form steps access to this.takePicture:

class SubmitPhoto extends React.Component {  state = { currentImage: null }  savePhoto() {
const image = this.props.takePicture()
this.setState({ currentImage: image })
}

Now taking pictures within any step is simple!

So is this elegant, or hacky? At least my three goals have been achieved.

--

--