mithril.js

Using Mithril.js, why does my onclick handler not modify the mithril component?


I am making a quiz web app. I have a class called an "answerBox", which currently just contains a button, an ID, and a Boolean. I want to call a "check Answer" function that determines if my answer is correct or not.

I assign the onclick to an anonymous function, and I send that function an object that is the same as the answerBox, but the onclick handler is not changing my answerBox object. It appears that it is creating a local copy. The code is here:

//currently just boilerplate checkAnswer function.  Eventually will involve server calls.  It is the client-side
//controller
function checkAnswer(ansBox)
{
    console.log(ansBox.IDString);
    if(ansBox.IDString == "Hello")
    {
        console.log("BEFORE: " + ansBox.IDString);
        ansBox.IDString = "Goodbye";
        console.log("AFTER: " + ansBox.IDString);
        return true;
    }
    else
    {
        ansBox.IDString = "Hello";
        return false;
    }
}

class answerBox{
    constructor(IDString){
        this.IDString = IDString;
        this.isCorrect = false;
        //to register the onclick, we need a reference to this, which we can't have unless we 
        //create it, since the button's "this" is not the same as the answerBox's "this", hence, "that".
        var that = this;
        this.checkButton = m("button",{onclick: function(){that.isCorrect = checkAnswer(that); console.log(that.isCorrect)}},"Check" + this.IDString + " " + this.isCorrect);
    }

    view()
    {
        return this.checkButton;
    }
}

I have an answerBox variable mounted to root using Mithril and it shows up just fine in the webpage. When I look at the console after clicking the button on the webpage, I get:

Hello
BEFORE: Hello
AFTER: Goodbye
true

Now, two things happen:

  1. The button does not change what it says. If the answerBox variable really had been modified, then it should have an IDString of "Goodbye", but it doesn't
  2. When I click the button again, I see the exact same console output, indicating that nothing is changing.

What am I doing wrong?


Solution

  • There's a couple of problems with the way you've written your component:

    First of all, the constructor is written to accept a single argument, IDString - but all Mithril component methods (including the constructor) receive a vnode object, containing attrs and children which are passed in via hyperscript when the component is invoked. So assuming you would invoke your component as follows:

    m(answerBox, {IDString: 'answerBox1'})
    

    ...then you would write the component to receive the IDString in the constructor as follows:

    class answerBox {
      constructor(vnode){
        this.IDString = vnode.attrs.IDString;
    

    The next problem is that an answerBox view will never change after instantiation, because its contents is only computed once, in the constructor, when this.checkButton is defined. Rather than evaluating the view and assigning it to a property only to invoke that property, simply write it in the view function directly. The component then becomes:

    class answerBox {
      constructor(vnode){
        this.IDString = vnode.attrs.IDString;
        this.isCorrect = false;
      }
    
      view(){
        var that = this;
    
        return m("button", {
          onclick: function(){
            that.isCorrect = checkAnswer(that); 
            console.log(that.isCorrect)
          }
        },
          "Check" + this.IDString + " " + this.isCorrect
        );
      }
    }
    

    The code above should produce the intended results!