Search code examples
javascriptreactjsbabeljses6-class

react super, Cannot read property 'call' of undefined


i try to use and overwrite a parent method in child one but get Uncaught TypeError: Cannot read property 'call' of undefined

enter image description here

class BreadcrumbsHome extends React.Component {
  getBreadcrumbs = () => [{name: 'home', url: '/'}];

  render() {
    return <nav>
      <ol className="breadcrumb float-sm-right">
        {!!this.getBreadcrumbs && this.getBreadcrumbs().map((e, i, arr) =>
          <li className={`breadcrumb-item ${i === arr.length - 1 ? 'active' : ''}`}><a
            href={APP_CONTEXT + e.url}>{e.name}</a>/</li>
        )}
      </ol>
    </nav>
  }
}

class BreadcrumbsTypes extends BreadcrumbsHome {

  getBreadcrumbs = () => [
    ...super.getBreadcrumbs(),
    ...this.props.path
  ]

as i read about this problem. Seems to be tied with babel so i tried adding the //noprotect line

I would like to handle with ES6 and avoid such way ParentClass.prototype.myMethod.call(this, arg1, arg2, ..)

Any ideas to fix that?


Solution

  • If you want to maintain the typical virtual polymorphism expected in JavaScript prototypal inheritance, you can't use field initializer syntax, you must use the ES2015 class method syntax:

    class BreadcrumbsHome extends React.Component {
      getBreadcrumbs () {
        return [{ name: 'home', url: '/' }];
      }
    
      render() {
        return <nav>
          <ol className="breadcrumb float-sm-right">
            {this.getBreadcrumbs().map((e, i, { length }) => (
              <li
                key={`${e.name}-${e.url}`}
                className={`breadcrumb-item ${i === length - 1 ? 'active' : ''}`}
              >
                <a href={APP_CONTEXT + e.url}>{e.name}</a> /
              </li>
            ))}
          </ol>
        </nav>
      }
    }
    
    class BreadcrumbsTypes extends BreadcrumbsHome {
    
      getBreadcrumbs () {
        return [
          ...super.getBreadcrumbs(),
          ...this.props.path
        ];
      }
    
      ...
    }
    

    The problem is that field initializers create own properties on each class instance rather than binding to the prototype chain like class methods do, so all you were doing was overwriting the own property getBreadcrumbs initialized by the base class with the own property initialized by the extended class.

    You can confirm this using a debugger console:

    enter image description here vs. enter image description here

    P.S. don't forget to include a unique key property on mapped JSX components :)

    I'd also suggest using CSS to style .breadcrumb-item:last-child rather than .breadcrumb-item.active, so your map() callback can simplify the className to

    className="breadcrumb-item"
    

    You can also add the following class to your CSS

    .breadcrumb-item::after { 
      content: "/";
    }
    

    so you can omit the / in your JSX.