> ## Content Index
> Fetch the complete content index at: https://madewithlove.com/blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# Creating a declarative oscillator component with React hooks.
- URL: https://madewithlove.com/blog/creating-a-declarative-oscillator-component-with-react-hooks/
- Published: 2019-01-04T16:10:17.000Z
- Updated: 2026-05-29T11:58:25.000Z
- Author: Geoffrey Dhuyvetters
- Tags: Engineering, Tutorials, JavaScript, React, Frontend

Most developers are familiar with the concept of imperative and declarative programming. 

**Imperative programming** is when you provide the program with the exact steps to achieve the *output* with an emphasis on *how*.

**Declarative programming** is a coding style where you focus on *what* the piece should achieve with less emphasis on the actual implementation (*how*). 

Suppose you want to create a piece of code that creates a new array of numbers multiplied by itself.  
This is how the solution in an imperative style would look like:

```javascript
const numbers = [1, 2, 3];

const results = []; // create an empty result array

for (let i = 0; i < numbers.length; i++) {
  // loop over all the original numbers
  const number = numbers[i]; // create a variable for each number
  const multiplied = number * number; // multiply it by itself

  results.push(multiplied); // add it to the results array
}

console.log(results); // log the results

```

And this is a declarative solution:  

```javascript
console.log([1, 2, 3].map(x => Math.pow(x, 2)));

// for each number in the array
// store the result of the function in a newly created array
```

As you can see, the declarative solution is a lot easier to read and reason about. It also leaves less room for errors since the implementation of the map function is handled by the language. A small note — this statement does imply that you’re used to working in a declarative codebase. It might have a learning curve at first.  

This is how you render a DOM element (div with some text) in an imperative way (one of the solutions, there are multiple):

```javascript
const name = "Geoffrey"; // create name variable

const helloWorld = document.createElement("div"); // create DOM element
helloWorld.textContent = `Hello ${name}`; // add text content

const parent = document.querySelector(".app"); // query for parent to attach
parent.appendChild(helloWorld); // append new element at correct place
```

This creates the same result in a declarative style (using React):

```javascript
import React from "react";
import ReactDOM from "react-dom";

const HelloWorld = ({ name }) => <div>Hello {name}</div>;

ReactDOM.render(
  <HelloWorld name={"Geoffrey"} />,
  document.querySelector(".app"),
);
```

Writing UI components in a declarative style is relatively easy with React, but how can we create declarative React components out of non-declarative browser APIs?

Let’s have a look at the [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web%5FAudio%5FAPI). To create an [oscillator](https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode), you need these steps:  

```javascript
const audioContext = new AudioContext(); // create AudioContext instance

const oscillator = audioContext.createOscillator(); // create oscillator from factory
oscillator.frequency.value = 300; // set frequency via AudioParam
oscillator.type = "sine"; // set oscillator type

oscillator.connect(audioContext.destination); // connect output to AudioContext destination
oscillator.start(); // start the oscillator

```

You can extract this into a factory function, but creating a reusable component would make the following possible:

```javascript
import React from "react";
import ReactDOM from "react-dom";

import { createChord, Chord, noteToFrequency } from "music-fns";

import Oscillator from "./Oscillator";

ReactDOM.render(
  <>
    {createChord("B3", Chord.MAJOR)
       .map(frequency => (
         <Oscillator
            frequency={noteToFrequency(frequency)}
            type={"sine"}
            key={frequency}
         />
    ))}
  </>,
  document.querySelector(".app"), // React requires this
);
```

This piece of code would create a [B major chord](https://en.wikipedia.org/wiki/Major%5Fchord) using the [music-fns library](https://github.com/madewithlove/music-fns) I’ve been working on — very readable, very declarative.

A while ago I saw [‘Mixed Mode React’](https://www.youtube.com/watch?v=PBpugV5l90c) by the infamous [Ken Wheeler](https://twitter.com/ken%5Fwheeler). This talk introduced the concept of creating components out of non-DOM related APIs and mixing them in projects. His talk really stuck with me but a part that felt very cumbersome was all the code located in the lifecycle methods. It makes it hard to share code between components. Back then (1 year ago, when the dinosaurs roamed the earth) classes were the only option if you wanted to use lifecycle methods, and this made it hard to share logic between components. The upcoming hooks feature introduces an entirely different way to approach this. Let’s create the component by using the technique Ken talked about while using hooks.

First, start with a functional component that doesn’t have a visual representation:  

```javascript
import React from "react";

export default () => {
  return null;
};
```

Rendering *null* is perfectly valid in React; we want our component to behave as a controller for our OscillatorNode.

On [*componentDidMount*](https://reactjs.org/docs/react-component.html#componentdidmount)**,** we want to do the initialsetup: 

- create oscillator [AudioNode](https://developer.mozilla.org/en-US/docs/Web/API/AudioNode) via [factory](https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/createOscillator)
- set initial [frequency](https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode/frequency) via [AudioParam](https://developer.mozilla.org/en-US/docs/Web/API/AudioParam)
- set initial [oscillator type](https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode/type)
- [start oscillator](https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode/start)
- [connect oscillator output](https://developer.mozilla.org/en-US/docs/Web/API/AudioNode/connect) to the [AudioContext destination](https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/destination)

On [*componentWillUnmount*](https://reactjs.org/docs/react-component.html#componentwillunmount)**,** we want to do somecleanup:

- [stop](https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode/stop) the oscillator
- [disconnect](https://developer.mozilla.org/en-US/docs/Web/API/AudioNode/disconnect) the oscillator from its output

The [*useEffect*hook](https://reactjs.org/docs/hooks-effect.html) replaces the *componentDidMount*, *componentDidUpdate* and *componentWillUnmount* lifecycles. We can ignore *componentDidUpdate* by passing an empty array as the second argument. Here’s the code:   

```javascript
import React, { useEffect } from "react";
const audioContext = new AudioContext();

export default ({ frequency = 130, type = "sine" } = {}) => {
  useEffect(() => {
    // replacement for componentDidMount

    const oscillator = audioContext.createOscillator();

    oscillator.frequency.value = frequency;
    oscillator.type = type;

    oscillator.start();
    oscillator.connect(audioContext.destination);

    return () => {
      // replacement for componentWillUnmount
      oscillator.stop();
      oscillator.disconnect();
    };
  }, []); // only trigger effect on componentDidMount and componentWillUnmount

  return null;
};
```

One issue we now have is that if we create 3 oscillators, we will have 3 AudioContext instances. One way to solve this is by using a React context to pass around a single AudioContext (a tad confusing, I must admit): 

```javascript
import React, { useEffect, useContext } from "react";
import context from "./context";

export default ({ frequency = 130, type = "sine" } = {}) => {
  const { audioContext } = useContext(context); // use React context hook

  useEffect(() => {
    const oscillator = audioContext.createOscillator();

    oscillator.frequency.value = frequency;
    oscillator.type = type;

    oscillator.start();
    oscillator.connect(audioContext.destination);

    return () => {
      oscillator.stop();
      oscillator.disconnect();
    };
  }, []);

  return null;
};
```

and*context.js* would look like this:

```javascript
import { createContext } from "react";

const audioContext = new AudioContext();
const context = createContext({ audioContext });

export default context;
```

We can now create multiple elements, they will all share the same AudioContext instance. The only part we’re missing now is a mechanism to control the [frequency](https://developer.mozilla.org/en-US/docs/Web/API/OscillatorNode/frequency) of each Oscillator.

To make this work we have to keep the actual oscillator in the state of the component; we can use the [*useState*](https://legacy.reactjs.org/docs/hooks-state.html)[hook](https://legacy.reactjs.org/docs/hooks-state.html) for this. This hook returns an array that you can deconstruct as *\[ statefulValue, updateFunction \]* and the value you provide to *useState* is the initial state value. In our example, we need to store the oscillator instance in the state:  

```javascript
import React, { useEffect, useContext, useState } from "react";
import context from "./context";

export default ({ frequency = 130, type = "sine" } = {}) => {
  const [oscillator, setOscillator] = useState(undefined); // setup state

  const { audioContext } = useContext(context);

  useEffect(() => {
    const oscillator = audioContext.createOscillator();

    oscillator.frequency.value = frequency;
    oscillator.type = type;

    oscillator.start();
    oscillator.connect(audioContext.destination);

    setOscillator(oscillator); // update state

    return () => {
      oscillator.stop();
      oscillator.disconnect();
    };
  }, []);

  return null;
};
```

The last thing to add is an *effect* that changes the oscillator frequency when the prop value changes:

```javascript
import React, { useEffect, useContext, useState } from "react";
import context from "./context";

export default ({ frequency = 130, type = "sine" } = {}) => {
  const [oscillator, setOscillator] = useState(undefined);

  const { audioContext } = useContext(context);

  useEffect(() => {
    const oscillator = audioContext.createOscillator();

    oscillator.frequency.value = frequency;
    oscillator.type = type;

    oscillator.start();
    oscillator.connect(audioContext.destination);

    setOscillator(oscillator);

    return () => {
      oscillator.stop();
      oscillator.disconnect();
    };
  }, []);

  useEffect(
    () => {
        if (oscillator) {
          oscillator.frequency.value = frequency;
        }
    },
    [frequency],
  ); // only trigger this effect when frequency changes

  return null;
};
```

Now we can use our component:

```javascript
import React from "react";
import ReactDOM from "react-dom";

import Oscillator from "./Oscillator";

const frequency = 500;

ReactDOM.render(
  <Oscillator frequency={frequency} type={"sine"} />,
  document.querySelector(".app"), // not strictly needed but React requires it.
);
```

This is just a small example using this technique. Imagine an on-screen piano rendered on the [Canvas](https://developer.mozilla.org/kab/docs/Web/API/Canvas%5FAPI) listening to [Web MIDI](https://developer.mozilla.org/en-US/docs/Web/API/MIDIAccess) input, generating sound via Web Audio, and visuals via [WebGL](https://developer.mozilla.org/en-US/docs/Web/API/WebGL%5FAPI). With everything powered by declarative React components using the same concepts we already know, the possibilities are endless.You can find the full example of this article [here](https://codesandbox.io/s/n0xn2yk97j) (further split up in reusable hooks) and a more complicated chord example [here](https://codesandbox.io/s/xo2j8321m4).