Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript + React: Element type is invalid: expected a string (for built-in components)

I realize that there are several questions related to this error floating around, but as far as I can tell this is a unique situation and not related to an incorrect import statement.

I'm building a React component library with TypeScript and webpack.

My directory structure:

- src
  - index.ts
  - components
    - Button
      - Button.tsx
      - index.ts
      - Button.css
      - Button.d.css (generated by webpack plugin)
- package.json
- tsconfig.json
- webpack.config.js
- postcss.config.js

My tsconfig.json:

{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "module": "es2015",
    "target": "es5",
    "lib": ["es6", "dom"],
    "sourceMap": true,
    "allowJs": false,
    "jsx": "react",
    "moduleResolution": "node",
    "rootDir": "src",
    "outDir": "dist",
    "noImplicitReturns": true,
    "noImplicitThis": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "declaration": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "build",
    "dist",
    "scripts",
    "acceptance-tests",
    "webpack",
    "jest",
    "src/setupTests.ts",
    "**/*/*.test.ts",
    "examples"
  ]
}

My webpack.config.js:

const path = require("path");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  entry: "./src/index.ts",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "index.js"
  },
  mode: "development",
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        loader: "awesome-typescript-loader",
      },
      {
        test: /\.css$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'typings-for-css-modules-loader',
            options: {
              modules: true,
              namedExport: true,
              banner: "/* This file is generated during the webpack build. Please do not edit/remove. */",
              localIdentName: '[name]__[local]'
            }
          },
          {
            loader: 'postcss-loader',
            options: {
              config: {
                path: './postcss.config.js'
              }
            }
          }
        ]
      },
      {
        test: /\.(jpg|png|gif|svg)$/,
        use: [
          {
            loader: "file-loader",
            options: {
              name: "[name].[ext]"
            }
          }
        ]
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[id].css"
    })
  ],
  devtool: "source-map",
  resolve: {
    extensions: [".js", ".ts", ".tsx", ".css"]
  }
};

My postcss.config.js:

module.exports = {
    modules: true,
    plugins: [
        require('precss'),
        require('postcss-simple-vars'),
        require('autoprefixer'),
        require('postcss-nested'),
    ]
}

src/index.ts is simply:

import { Button } from './components/Button';

export {
  Button,
};

src/components/Button/index.ts:

import Button from './Button';

export { Button };

and src/components/Button/Button.tsx:

import * as React from 'react';
import { ReactNode } from 'react';
import { Link } from 'react-router-dom';
import * as styles from './Button.css';

export interface IButtonPropTypes {
  onClick?: React.MouseEventHandler<any>;
  label: string;
  children?: ReactNode[] | string;
  kind?: 'link' | 'action';
  style?: { [key: string]: string };
  href?: string;
  target?: string;
  className?: string;
}

export default function Button({
  onClick,
  label,
  children,
  kind = 'action',
  style = {},
  href,
  target,
  className,
}: IButtonPropTypes) {
  const text = label || children;
  const kindStyle = styles[kind] || '';
  const classes = className || '';
  if (href) {
    return (
      <Link
        className={`${style.btn} ${kindStyle} ${classes}`}
        to={href}
        target={target}
        onClick={onClick}
        style={style}
      >
        <span className={style.background} />
        <span>{text}</span>
      </Link>
    );
  }

  return (
    <button className={`${style.btn} ${kindStyle}`} onClick={onClick} style={style}>
      <div>{text}</div>
    </button>
  );
}

My dist folder after running webpack looks like:

- dist
  - index.js
  - index.js.map
  - index.d.ts
  - main.css
  - main.css.map
  - components
    - Button
      - Button.d.ts
      - index.d.ts

and dist/index.js seems to be compiled correctly by webpack. In package.json, I have:

  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": ["dist"]

After running yarn link, I import and use my Button component in a standalone app like so:

import { Button } from 'my-components';

class App extends React.Component {
  render() {
    return (
      <div className="App">
        <Button label="click me" />
      </div>
    );
  }
}

export default App;

and receive the following error:

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got:

undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

Check the render method of `App`.

If I remove the Button component, App renders without any errors.

Testing by publishing a throwaway module to npm yields the same error.

Additionally, if anyone has any suggestions as to better ways to bundle this library, I'd love to hear them as this is my first time using postcss.

like image 878
adrice727 Avatar asked Dec 15 '25 01:12

adrice727


1 Answers

As it turns out, this was due to an issue with my webpack config. Adding the following lines to output fixed the error:

  output: {
    . . . 
    library: "opentok-ux-components",
    libraryTarget: 'umd',
    umdNamedDefine: true
  },
like image 108
adrice727 Avatar answered Dec 16 '25 20:12

adrice727



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!