Search code examples
javascriptreactjssynthesizer

this is undefined in React method


I am trying to make a simple synthesizer with React and the Web Audio API and having trouble with some methods. Here is my code:

import * as React from "react"

class Synth extends React.Component {

    constructor(props) {
        super(props);
        this.state = {value: 0.5};
    
        this.handleChange = this.handleChange.bind(this);
        this.setup = this.setup.bind(this);
        this.createKey = this.createKey.bind(this);
        this.componentDidMount = this.componentDidMount.bind(this);
        this.audioContext = null;
        this.oscList = [];
        this.mainGainNode = null;
        this.wavePicker = document.querySelector("select[name='waveform']");
        this.volumeControl = document.querySelector("input[name='volume']");

        this.noteFreq = null;
        this.customWaveform = null;
        this.sineTerms = null;
        this.cosineTerms = null;
    }
    componentDidMount(){
        this.setup();
    }

    handleChange(event) {
        this.setState({
            value: event.target.value
        });
    }

    createNoteTable(){
        let noteFreq = [];
        for (let i=0; i< 9; i++) {
            noteFreq[i] = [];
        }

        noteFreq[3]["C"] = 130.81;
        noteFreq[3]["C#"] = 138.59;
        noteFreq[3]["D"] = 146.83;
        noteFreq[3]["D#"] = 155.56;
        noteFreq[3]["E"] = 164.81;
        noteFreq[3]["F"] = 174.61;
        noteFreq[3]["F#"] = 185.00;
        noteFreq[3]["G"] = 196.00;
        noteFreq[3]["G#"] = 207.65;
        //etc...
        return noteFreq;
    }
    createKey(note, octave, freq){
        console.log("createKey() is firing");
        let keyElement = document.createElement("li");

        switch (freq) {
            case 130.81:
                keyElement.className = "white c1"
                break;
            case 146.83:
                keyElement.className = "black cs1"
                break;
            case 164.81:
                keyElement.className = "white c1"
                break;
            case 174.61:
                keyElement.className = "white d1"
                break;
            //etc...
        
            default:
                break;
        }
        keyElement.dataset["freq"] = freq;
        keyElement.dataset["note"] = note;
        keyElement.dataset["octave"] = octave;
        keyElement.addEventListener("mousedown", this.notePressed, false);
        keyElement.addEventListener("mouseup", this.noteReleased, false);
        keyElement.addEventListener("mouseover", this.notePressed, false);
        keyElement.addEventListener("mouseleave", this.noteReleased, false);

        return keyElement;
    }
    
    setup(){
        
        this.audioContext = new (window.AudioContext || window.webkitAudioContext)();

        this.noteFreq = this.createNoteTable();

        
        this.mainGainNode = this.audioContext.createGain();
        this.mainGainNode.connect(this.audioContext.destination);
        this.mainGainNode.gain.value = this.state.value;
        
        this.noteFreq.forEach(function(keys, idx) {
            let keyList = Object.entries(keys);
            let octaveElem = document.createElement("div");
            keyList.forEach(function(key){

                console.log("key[0] = " + key[0]);
                console.log("idx = " + idx);
                console.log("key[1] = " + key[1]);
                try {
                    octaveElem.appendChild(this.createKey(key[0], idx, key[1]));
                } catch(error){
                    console.log("Cannot create key... " + error);
                }
            });
        });
    
        this.sineTerms = new Float32Array([0, 0, 1, 0, 1]);
        this.cosineTerms = new Float32Array(this.sineTerms.length);
        this.customWaveform = this.audioContext.createPeriodicWave(this.cosineTerms, this.sineTerms);
    
        for (let i=0; i<9; i++) {
            this.oscList[i] = {};
        }
    }

then I have the notePressed() and noteReleased() functions but these seem to work fine.

The problem is when this.createKey() is called I get this error : TypeError: Cannot read properties of undefined (reading 'createKey')

As you can see, I tried binding pretty much every method I have to see if it would help but it didn't. Any help will be greatly appreciated.


Solution

  • Your error is probably due to the function keyword in your forEach loops. There you lose your this scope. You can fix it by using for of or an arrow function.

    this.noteFreq.forEach((keys, idx) => {
        ...
        keyList.forEach((key) => { 
            ...
        }
    }
    

    Also, your code looks more like a normal ES6 class, and not a React class. In React, you don't use document.createElement and element.appendChild. Instead, you should use the render() function to render the document.