Dataset in React

When life gives you lemons, you make lemonade. And when it doesn’t, hack your way around it. Such was the case with a piece of React code where the previous programmer passed some data-* attributes as props, then onto the JSX markup and later extract them from event.target in the event handlers (or also from the event.currentTarget or from native event). Now, React isn’t ready enough for this (as of January, 2017). The dataset property which are generally accessible via the HTMLElement.dataset property aren’t handled the DOM way.

While playing around wrappers, I had to find a workaround to pass the data-* props. I wrote a little hack to extract the data-* attributes using regex and some spread syntax to pass the dataset to both the outer wrapper, and the inner component.

Example:

render() {
  // Extract the keys present in the `props`
  // Filter the keys which have `data-` as prefix
  // and insert them into `dataset` object
  const dataset = {};
  const { state, props } = this;
  Object.keys(props).filter (value => {
    if (/data-\S+/gi.test(value)) {
       dataset[value] = props[value];
    }
  });
  return (
    <Wrapper {...dataset} onChange={this.handleChange} >
      <Component {...dataset} name="component-type-1">
      </Component>
    </Wrapper>
  )
}

The advantage – you can access the dataset in your handleChange method from the event.currentTarget and event.target. Visit this MDN link to know more about dataset.

Creating a Guitar Tuner – With modern web APIs

String Frequency Scientific pitch notation
1 (E) 329.63 Hz E4
2 (B) 246.94 Hz B3
3 (G) 196.00 Hz G3
4 (D) 146.83 Hz D3
5 (A) 110.00 Hz A2
6 (E) 82.41 Hz E2

This table can be obtained from the Guitar Tuning wiki page.

So, this is how it goes:

  1. Our objective here is to generate the frequencies when requested, like when a user presses a button
  2. We make use of the Web Audio API to generate the frequencies listed above
    • An extension to it, will be using the micro phone to match the frequencies, but that won’t be covered in this part
    • Paul Lewis has an excellent app built with the above approach
  3. To use the web api, we must create an instance of the AudioContext object
    • Akin to canvas, we must instantiate an audio context object before accessing the web audio api
    • And, to generate the frequencies, we have to create an oscillator.

// create web audio api context
var audioCtx = new (window.AudioContext || window.WebkitAudioContext);

// create Oscillator node
var oscillator = audioCtx.createOscillator();

Now, we’ve to specify the type of wave. These are four natively supported types:

  • sine
  • square
  • sawtooth
  • triangle

A custom type is also available for use. We are not getting into that.

  • We’ll use the sine wave, because that audio wave is bearable.
  • We’ve to set a frequency value at which the oscillator will produce the waves. Let’s set it to E1, i.e. 329.63 Hz
  • We’ve to connect to the destination supported by the Audio Context. The output generally the standard audio interface i.e. your speakers.
  • Next, we start the oscillator.
  • Remember, the oscillator can be started once, and only once. It can be stopped, but can’t be restarted.
  • If you make live changes to the frequency or the type of the wave, the changes are reflected in the oscillator realtime. Hence, the absence of a restart functionality won’t be felt much.

Let’s create an oscillator React component (sorry, p-react, because size matters).

Now, the markup in the snippet ahead appeared gibberish. Therefore, I’ve posted a gist instead.


# oscillator.js
import { h, Component } from 'preact';
import style from './style';

const audioContext = new (window.AudioContext || window.webkitAudioContext);

export default class Oscillator extends Component {

  play() {
    this.oscillator = audioContext.createOscillator();
    this.oscillator.type = this.props.type || 'sine';
    this.oscillator.frequency.value = this.props.frequency || 329.63; //E(1) is default
    this.oscillator.connect(audioContext.destination);  
    this.oscillator.start();
  }

  stop() {
    this.oscillator.stop();
    this.oscillator = null;
  }

  render() {
    return ( /* refer the gist */ );
  }
 }

 

The reasons for creating a new instance every time you hit the start button are

  • The start method only works once per oscillator. Hence, once stopped, there is no way to restart the oscillator.
  • There is no API to suspend and later resume the oscillator.
  • The context can be suspended and resumed later but that doesn’t stop the oscillators. And, when you resume the context after firing multiple oscillators, you hear all of them buzz simultaneously.
  • Therefore, we must create a new oscillator instance then start, every time we hit start and stop-then-destroy the instance every time we hit the stop button.

Now, we’ve to make some buzzing & humming by assigning values to the props. If you can’t see the code here, then follow this gist.

  <Oscillator note="E1" frequency="329.63" type="sine" />
  <Oscillator note="B2" frequency="246.94" type="sine" />
  <Oscillator note="G3" frequency="196.00" type="sine" />
  <Oscillator note="D4" frequency="146.83" type="sine" />
  <Oscillator note="A5" frequency="110.00" type="sine" />
  <Oscillator note="E6" frequency="82.41" type="sine" />

And, we're done.

Caution

Make sure to lower your speaker volumes. If you’re using head phones, then definitely cross check 3 times if your volume is low or not. I don’t want people testing it go all Beethoven on the first day.

Hit the start/stop buttons and tune your guitar along.

Chords

In the similar fashion, we can create a chord component (possibly in the next tutorial) that creates 3 oscillators and plays all of them simultaneously to create a resonating chord.

Hint: A frequency combination for C-major are 196.00 (G), 261.63(C) and 329.63(E). And, for creating a G-major, you can use a combination of 146.83(D), 196.00(G),  and 246.94(B).

happy humming

More