3

I working on a todo-app example with Redux and Maquettejs using Typescript. I just compile typescript, and then I use browserify to bundle all the .js files (this files contains application .ts files, and the libraries [redux, maquettejs]), I get no errors when compiling and everything is ok.

The problem comes when I try to see the result on the browser I get this error.

enter image description here

At least for me it makes no sense since it's explicit defined. I'm not an expert to judge the compiled code but in case I created a mocked implementation http://jsbin.com/tenohitumi/edit?js,console,output and it works as expected. I do not really get what's happening.

Just in case this is the class "App" written with typescript.

import { h, VNodeProperties, VNode } from 'maquette';
import { Store } from 'redux';
import { TodoList } from './Todolist';

export class App {

    constructor(private store: Store<any>) {
    }

    render(): VNode {
        this.store;
        return h('div.main-component', [
            new TodoList(this.store).render()
        ]);
    }
}

// This is how I create the instance (it's in a different file)

import { createStore } from 'redux';
import { createProjector } from 'maquette';

import * as I from './interfaces/app';
import { MainReducer } from './reducers';
import { App } from './components/App';

let store = createStore(MainReducer);

let projector = createProjector();


document.addEventListener('DOMContentLoaded', function () {
  let app = new App(store);
  projector.append(document.body, app.render);
});

I would like to know if, is there anyway that something outside the anonymous function (in the bundle itself) could affect the value of "this", or prevent this to be set?

Miguel Lattuada
  • 5,327
  • 4
  • 31
  • 44
  • 4
    The value of `this` in a method is impacted based on how the method is invoked. It's something like an implicit function parameter. So when you pass `app.render` to `projector.append()`, you're only passing the method, not the object, which means the value of `this` will likely be set to its default value, which is `undefined` if you're in strict mode. –  Nov 06 '16 at 00:03
  • 1
    More info here: http://stackoverflow.com/questions/3127429/how-does-the-this-keyword-work –  Nov 06 '16 at 00:04

1 Answers1

5

As @squint answered in a comment, the render method is getting orphaned from its App instance when you pass it to projector.append. Consider replacing the original line

projector.append(document.body, app.render);

with

projector.append(document.body, () => app.render());

Alternatively, you can define render using an arrow function instead, which won't holds on to its original reference to this.

export class App {
    constructor(private store: Store<any>) {
    }

    render = (): VNode => {
        return h('div.main-component', [
            new TodoList(this.store).render()
        ]);
    }
}
Daniel Rosenwasser
  • 21,855
  • 13
  • 48
  • 61
  • 1
    He can also do: `projector.append(document.body, app.render.bind(app));` – Nitzan Tomer Nov 06 '16 at 00:19
  • @NitzanTomer it's a great approach, but it force me to declare component elements instance before use the method render, so I can bind to itself, anyways it's better than reference this with a new variable. – Miguel Lattuada Nov 06 '16 at 00:33
  • @MiguelLattuada But that's exactly what you're doing in the code in your question – Nitzan Tomer Nov 06 '16 at 00:36
  • I just changed it to be more clear, before was `new App(store).render` – Miguel Lattuada Nov 06 '16 at 00:43
  • @NitzanTomer, I'd prefer not using `bind` since it loses type information, and is slower on certain runtimes. – Daniel Rosenwasser Nov 06 '16 at 00:51
  • @DanielRosenwasser But using an arrow function for a method adds it as a property instead of method, which makes it hard to extend the class and call `super.render`. It's a tradeoff, and each option is useful for different scenarios. – Nitzan Tomer Nov 06 '16 at 01:00