Search code examples
javascriptreactjsnpmgulpbabeljs

Uncaught TypeError: Cannot call a class as a function


I have looked through all of the answers on this question:
Getting "Cannot call a class as a function" in my React Project


I am still unable to work out why the below is returning the error 'Cannot call a class as a function'. Am I missing something here in terms of the syntax?

  • I am running through Gulp, Rollup and Babel
  • All plugins for above are included
  • The selector for reactDOM.render is fine and export of the class is not needed

Code:

import React from 'react';
import ReactDOM from 'react-dom';

class App extends React.Component {

    constructor(props) {
        super(props);
    }

    render() {
        return (
            <div />
        );
    }
}

// Selector is fine, changes bg to red
document.querySelector('#app').style.background = 'red';

ReactDOM.render(<App />, document.querySelector('#app'));

Error:

enter image description here

Gulp task with Rollup and Babel:

gulp.task('js', () => {

    // Package up ES6 moduleswith stream
    const stream = plugins.rollupStream({
        input: paths.dev + '/script/app.js',
        sourcemap: true,
        format: 'iife',
        name: 'app',
        plugins: [
            plugins.rollupPluginReplace({'process.env.NODE_ENV': JSON.stringify( 'production' )}),
            plugins.rollupPluginJsx({ factory: 'React.createElement' }),
            plugins.rollupPluginCommonjs({}),           
            plugins.rollupPluginNodeResolve({ jsnext: true, main: true }),
            plugins.rollupPluginIncludepaths({ paths: [paths.dev + '/script/'] })
        ]
    })

    return stream
        .on('error', e => {
            console.error(e.stack);

            notifier.notify({
                title: 'Rollup error',
                message: e.stack
            });
            stream.emit('end');
        })
        // Error handling
        .pipe(plugins.plumber())
        // Prepare files for sourcemap
        .pipe(plugins.vinylSourceStream('app.js', paths.dev + '/script/'))
        .pipe(plugins.vinylBuffer())
        .pipe(plugins.sourcemaps.init({ loadMaps: true }))
        // Convert ES6
        .pipe(plugins.babel({ presets: ['es2015', 'react'] }))
        // Write sourcemap
        .pipe(plugins.sourcemaps.write('.'))
        .pipe(gulp.dest(paths.tmp + '/script/'))
        .pipe(plugins.browserSync.stream());
});

Codebase:

https://github.com/alexplummer/framework-react/blob/master/_dev/script/app.js


Solution

  • The issue is that your JSX is not transpiled correctly. If you look at the output in the devtools (second error from the bottom in the stack trace), you'll see that the class is called as a regular function:

    reactDom.render(App(), document.querySelector('#app'));
    //              ^^^^^
    

    You are using rollup-plugin-jsx and it seems to handle that differently. There is really no reason to use this, if you are using Babel anyway, since Babel can transpile your JSX as well. I'd assume that you have added this, because Rollup was complaining about JSX. That's because your build pipeline is not quite right. You currently bundle the code with Rollup first, then you run Babel over it.

    You can integrate Babel directly into Rollup with rollup-plugin-babel, so everything that is bundled by Rollup will automatically be transpiled by Babel. By adding this plugin you can remove rollup-plugin-jsx entirely and you also don't need to pipe it to Babel afterwards.

    These are the changes to tasks/js.js (Stack Overflow doesn't have syntax highlighting for git diffs, but you can see the highlighted version in the Gist - tasks/js.js diff):

    diff --git a/tasks/js.js b/tasks/js.js
    index 0caabc1..7c5319d 100644
    --- a/tasks/js.js
    +++ b/tasks/js.js
    @@ -70,7 +70,10 @@ gulp.task('js', () => {
                    name: 'app',
                    plugins: [
                            plugins.rollupPluginReplace({'process.env.NODE_ENV': JSON.stringify( 'development' )}),
    -                       plugins.rollupPluginJsx({ factory: 'React.createElement' }),
    +                       plugins.rollupPluginBabel({
    +                               exclude: 'node_modules/**',
    +                               presets: [['es2015', { modules: false }], 'react']
    +                       }),
                            plugins.rollupPluginCommonjs({}),
                            plugins.rollupPluginNodeResolve({ jsnext: true, main: true }),
                            plugins.rollupPluginIncludepaths({ paths: [paths.dev + '/script/'] })
    @@ -93,8 +96,6 @@ gulp.task('js', () => {
                    .pipe(plugins.vinylSourceStream('app.js', paths.dev + '/script/'))
                    .pipe(plugins.vinylBuffer())
                    .pipe(plugins.sourcemaps.init({ loadMaps: true }))
    -               // Convert ES6
    -               .pipe(plugins.babel({ presets: ['es2015', 'react'] }))
                    // Write sourcemap
                    .pipe(plugins.sourcemaps.write('.'))
                    .pipe(gulp.dest(paths.tmp + '/script/'))
    

    Note that you need to turn off module transpilation in Babel, because Rollup needs ES modules. On a side note, babel-preset-es2015 is deprecated in favour of babel-preset-env, which contains everything that the es201x presets did and more. It is a drop-in replacement for es2015, for details see Migration guide from es2015 to env.